diff --git a/components/dash-core-components/src/components/Dropdown.react.js b/components/dash-core-components/src/components/Dropdown.react.js index 37111fc338..28382e83a6 100644 --- a/components/dash-core-components/src/components/Dropdown.react.js +++ b/components/dash-core-components/src/components/Dropdown.react.js @@ -118,6 +118,12 @@ Dropdown.propTypes = { */ multi: PropTypes.bool, + /** + * If false and multi=true, then selecting a value will leave the menu open + * to select more values + */ + close_on_select: PropTypes.bool, + /** * Whether or not the dropdown is "clearable", that is, whether or * not a small "x" appears on the right of the dropdown that removes @@ -229,6 +235,7 @@ Dropdown.defaultProps = { clearable: true, disabled: false, multi: false, + close_on_select: true, searchable: true, optionHeight: 35, maxHeight: 200, diff --git a/components/dash-core-components/src/fragments/Dropdown.react.js b/components/dash-core-components/src/fragments/Dropdown.react.js index 0464fb8ff4..ca9f1982f1 100644 --- a/components/dash-core-components/src/fragments/Dropdown.react.js +++ b/components/dash-core-components/src/fragments/Dropdown.react.js @@ -24,6 +24,7 @@ const TOKENIZER = { const RDProps = [ 'multi', + 'close_on_select', 'clearable', 'searchable', 'search_value', @@ -41,6 +42,7 @@ const Dropdown = props => { clearable, searchable, multi, + close_on_select, options, setProps, style, @@ -155,6 +157,7 @@ const Dropdown = props => { filterOptions={filterOptions} options={sanitizedOptions} value={value} + closeOnSelect={multi ? close_on_select : true} onChange={onChange} onInputChange={onInputChange} backspaceRemoves={clearable} diff --git a/components/dash-core-components/tests/integration/dropdown/test_close_on_select.py b/components/dash-core-components/tests/integration/dropdown/test_close_on_select.py new file mode 100644 index 0000000000..d24862b6b1 --- /dev/null +++ b/components/dash-core-components/tests/integration/dropdown/test_close_on_select.py @@ -0,0 +1,113 @@ +import json + +from dash import Dash, Input, Output, dcc, html +from selenium.webdriver.common.keys import Keys + + +def test_ddcos001_multi_stay_open(dash_dcc): + app = Dash(__name__) + app.layout = html.Div( + [ + dcc.Dropdown( + id="multi-dropdown", + options=[ + {"label": "New York City", "value": "NYC"}, + {"label": "Montreal", "value": "MTL"}, + {"label": "San Francisco", "value": "SF"}, + ], + multi=True, + close_on_select=False, + ), + html.Div(id="dropdown-value", style={"height": "10px", "width": "10px"}), + ] + ) + + @app.callback( + Output("dropdown-value", "children"), + [Input("multi-dropdown", "value")], + ) + def update_value(val): + return json.dumps([v for v in val]) + + dash_dcc.start_server(app) + dropdown = dash_dcc.find_element("#multi-dropdown") + dropdown.click() + outer_menu = dash_dcc.find_element("#multi-dropdown .Select-menu-outer") + outer_menu.click() + dash_dcc.wait_for_contains_class("#multi-dropdown .Select", "is-open") + outer_menu.click() + dash_dcc.wait_for_contains_class("#multi-dropdown .Select", "is-open") + dash_dcc.find_element("body").send_keys(Keys.ESCAPE) + + dash_dcc.wait_for_text_to_equal("#dropdown-value", '["MTL", "SF"]') + + +def test_ddcos002_multi_close(dash_dcc): + app = Dash(__name__) + app.layout = html.Div( + [ + dcc.Dropdown( + id="multi-dropdown", + options=[ + {"label": "New York City", "value": "NYC"}, + {"label": "Montreal", "value": "MTL"}, + {"label": "San Francisco", "value": "SF"}, + ], + multi=True, + close_on_select=True, + ), + html.Div(id="dropdown-value", style={"height": "10px", "width": "10px"}), + ] + ) + + @app.callback( + Output("dropdown-value", "children"), + [Input("multi-dropdown", "value")], + ) + def update_value(val): + return json.dumps([v for v in val]) + + dash_dcc.start_server(app) + dropdown = dash_dcc.find_element("#multi-dropdown") + dropdown.click() + outer_menu = dash_dcc.find_element("#multi-dropdown .Select-menu-outer") + outer_menu.click() + dash_dcc.wait_for_no_elements("#multi-dropdown .Select-menu-outer") + dash_dcc.find_element("body").send_keys(Keys.ESCAPE) + + dash_dcc.wait_for_text_to_equal("#dropdown-value", '["MTL"]') + + +def test_ddcos003_single_open(dash_dcc): + app = Dash(__name__) + app.layout = html.Div( + [ + dcc.Dropdown( + id="multi-dropdown", + options=[ + {"label": "New York City", "value": "NYC"}, + {"label": "Montreal", "value": "MTL"}, + {"label": "San Francisco", "value": "SF"}, + ], + close_on_select=True, + ), + html.Div(id="dropdown-value", style={"height": "10px", "width": "10px"}), + ] + ) + + @app.callback( + Output("dropdown-value", "children"), + [Input("multi-dropdown", "value")], + ) + def update_value(val): + return json.dumps(val) + + dash_dcc.start_server(app) + dropdown = dash_dcc.find_element("#multi-dropdown") + dropdown.click() + outer_menu = dash_dcc.find_element("#multi-dropdown .Select-menu-outer") + outer_menu.click() + dash_dcc.wait_for_no_elements("#multi-dropdown .Select-menu-outer") + dash_dcc.find_element("body").send_keys(Keys.ESCAPE) + + dash_dcc.wait_for_text_to_equal("#dropdown-value", '"MTL"')