Skip to content

Commit 6076bb8

Browse files
authored
Merge branch 'dev' into fix-multi-select-dropdown-with-components-as-labels
2 parents 422f311 + 91e05f4 commit 6076bb8

File tree

2 files changed

+84
-0
lines changed

2 files changed

+84
-0
lines changed

components/dash-core-components/src/fragments/Dropdown.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -275,6 +275,12 @@ const Dropdown = (props: DropdownProps) => {
275275
return;
276276
}
277277

278+
// Don't steal focus from the search input during search-driven
279+
// re-renders (displayOptions changes while the user is typing).
280+
if (document.activeElement === searchInputRef.current) {
281+
return;
282+
}
283+
278284
requestAnimationFrame(() => {
279285
if (!multi) {
280286
const selectedValue = sanitizedValues[0];

components/dash-core-components/tests/integration/dropdown/test_search_value.py

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
from time import sleep
2+
3+
from selenium.webdriver.common.action_chains import ActionChains
4+
from selenium.webdriver.common.keys import Keys
5+
16
from dash import Dash, Input, Output, dcc, html
27

38

@@ -29,3 +34,76 @@ def update_output(search_value):
2934
dash_duo.wait_for_text_to_equal("#output", 'search_value="x"')
3035

3136
assert dash_duo.get_logs() == []
37+
38+
39+
def test_ddsv002_search_filter_and_scroll(dash_duo):
40+
"""Search filters a virtualized dropdown, backspace restores all options,
41+
then scroll to the bottom and select the last item."""
42+
app = Dash(__name__)
43+
options = [
44+
{"label": f"Option {i + 1}", "value": f"opt_{i + 1}"} for i in range(100)
45+
]
46+
app.layout = html.Div(
47+
[
48+
dcc.Dropdown(id="dropdown", options=options, value="opt_1"),
49+
html.Div(id="output"),
50+
]
51+
)
52+
53+
@app.callback(Output("output", "children"), Input("dropdown", "value"))
54+
def update_output(value):
55+
return f"value={value}"
56+
57+
dash_duo.start_server(app)
58+
dash_duo.wait_for_text_to_equal("#output", "value=opt_1")
59+
60+
# Open the dropdown by clicking it
61+
dash_duo.find_element("#dropdown").click()
62+
dash_duo.wait_for_element(".dash-dropdown-options")
63+
64+
# Click the search field to focus it
65+
search = dash_duo.find_element(".dash-dropdown-search")
66+
search.click()
67+
68+
# Use ActionChains to type into the currently focused element,
69+
# which will fail if focus is stolen from the search field.
70+
def send_key(key):
71+
ActionChains(dash_duo.driver).send_keys(key).perform()
72+
73+
# Type "100" one character at a time to filter down to "Option 100"
74+
send_key("1")
75+
sleep(0.2)
76+
send_key("0")
77+
sleep(0.2)
78+
send_key("0")
79+
sleep(0.2)
80+
81+
# Should have exactly one option visible: "Option 100"
82+
visible_options = dash_duo.find_elements(".dash-dropdown-option")
83+
assert len(visible_options) == 1
84+
assert "Option 100" in visible_options[0].text
85+
86+
# Backspace three times to clear the search and restore all options
87+
send_key(Keys.BACKSPACE)
88+
sleep(0.2)
89+
send_key(Keys.BACKSPACE)
90+
sleep(0.2)
91+
send_key(Keys.BACKSPACE)
92+
sleep(0.2)
93+
94+
# Scroll to the bottom of the options list
95+
options_container = dash_duo.find_element(".dash-dropdown-options")
96+
dash_duo.driver.execute_script(
97+
"arguments[0].querySelector('.dash-options-list-virtualized').scrollTop = "
98+
"arguments[0].querySelector('.dash-options-list-virtualized').scrollHeight",
99+
options_container,
100+
)
101+
sleep(0.3)
102+
103+
# Find and click the last option (Option 100)
104+
all_options = dash_duo.find_elements(".dash-dropdown-option")
105+
last_option = all_options[-1]
106+
assert "Option 100" in last_option.text
107+
last_option.click()
108+
109+
dash_duo.wait_for_text_to_equal("#output", "value=opt_100")

0 commit comments

Comments
 (0)