diff --git a/CHANGELOG.md b/CHANGELOG.md index dc83e4ee46..116809d6bd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,10 @@ This project adheres to [Semantic Versioning](https://semver.org/). ### Added - [#2109](https://github.com/plotly/dash/pull/2109) Add `maxHeight` to Dropdown options menu. +- [#2039](https://github.com/plotly/dash/pull/2039) Long callback changes: + - Add `background=False` to `dash.callback` to use instead of `app.long_callback`. + - Add previous `app.long_callback` arguments to `dash.callback` (`interval`, `running`, `cancel`, `progress`, `progress_default`, `cache_args_to_ignore`, `manager`) +- [#2110](https://github.com/plotly/dash/pull/2110) Add `search` prop to `dcc.Dropdown` options, allowing to search the dropdown options with something other than the label or value. ### Fixed - [#2126](https://github.com/plotly/dash/pull/2126) Fix bug where it was not possible to redirect from root when using pages. @@ -20,12 +24,7 @@ This project adheres to [Semantic Versioning](https://semver.org/). - Fix [#1974](https://github.com/plotly/dash/issues/1974) returning `no_update` or raising `PreventUpdate` not supported with celery. - Fix use of the callback context in celery long callbacks. - Fix support of pattern matching for long callbacks. - -### Added - -- [#2039](https://github.com/plotly/dash/pull/2039) Long callback changes: - - Add `background=False` to `dash.callback` to use instead of `app.long_callback`. - - Add previous `app.long_callback` arguments to `dash.callback` (`interval`, `running`, `cancel`, `progress`, `progress_default`, `cache_args_to_ignore`, `manager`) +- [#2110](https://github.com/plotly/dash/pull/2110) Fix `dcc.Dropdown` search with component as prop for option label. ## Changed diff --git a/components/dash-core-components/src/components/Dropdown.react.js b/components/dash-core-components/src/components/Dropdown.react.js index 074da19c9b..37111fc338 100644 --- a/components/dash-core-components/src/components/Dropdown.react.js +++ b/components/dash-core-components/src/components/Dropdown.react.js @@ -80,6 +80,14 @@ Dropdown.propTypes = { * see https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/title */ title: PropTypes.string, + + /** + * Optional search value for the option, to use if the label + * is a component or provide a custom search value different + * from the label. If no search value and the label is a + * component, the `value` will be used for search. + */ + search: PropTypes.string, }) ), ]), diff --git a/components/dash-core-components/src/fragments/Dropdown.react.js b/components/dash-core-components/src/fragments/Dropdown.react.js index 7b872089ec..8da5d0bd78 100644 --- a/components/dash-core-components/src/fragments/Dropdown.react.js +++ b/components/dash-core-components/src/fragments/Dropdown.react.js @@ -48,12 +48,39 @@ const Dropdown = props => { } = props; const [optionsCheck, setOptionsCheck] = useState(null); const [sanitizedOptions, filterOptions] = useMemo(() => { - const sanitized = sanitizeOptions(options); + let sanitized = sanitizeOptions(options); + + const indexes = ['strValue']; + let hasElement = false, + hasSearch = false; + sanitized = Array.isArray(sanitized) + ? sanitized.map(option => { + if (option.search) { + hasSearch = true; + } + if (React.isValidElement(option.label)) { + hasElement = true; + } + return { + ...option, + strValue: String(option.value), + }; + }) + : sanitized; + + if (!hasElement) { + indexes.push('label'); + } + if (hasSearch) { + indexes.push('search'); + } + return [ sanitized, createFilterOptions({ options: sanitized, tokenizer: TOKENIZER, + indexes, }), ]; }, [options]); diff --git a/components/dash-core-components/src/utils/optionTypes.js b/components/dash-core-components/src/utils/optionTypes.js index 4af9208adc..ca092ff12e 100644 --- a/components/dash-core-components/src/utils/optionTypes.js +++ b/components/dash-core-components/src/utils/optionTypes.js @@ -1,9 +1,10 @@ +import React from 'react'; import {type} from 'ramda'; export const sanitizeOptions = options => { if (type(options) === 'Object') { return Object.entries(options).map(([value, label]) => ({ - label: String(label), + label: React.isValidElement(label) ? label : String(label), value, })); } diff --git a/components/dash-core-components/tests/integration/misc/test_dcc_components_as_props.py b/components/dash-core-components/tests/integration/misc/test_dcc_components_as_props.py index 1b2e0f1c25..9c357d7dab 100644 --- a/components/dash-core-components/tests/integration/misc/test_dcc_components_as_props.py +++ b/components/dash-core-components/tests/integration/misc/test_dcc_components_as_props.py @@ -27,6 +27,13 @@ def test_mdcap001_dcc_components_as_props(dash_dcc): ], id="dropdown", ), + dcc.Dropdown( + [ + {"label": "one", "value": 1, "search": "uno"}, + {"label": "two", "value": 2, "search": "dos"}, + ], + id="indexed-search", + ), ] ) @@ -41,3 +48,26 @@ def test_mdcap001_dcc_components_as_props(dash_dcc): dash_dcc.find_element("#dropdown").click() dash_dcc.wait_for_text_to_equal("#dropdown h4", "h4") dash_dcc.wait_for_text_to_equal("#dropdown h6", "h6") + + search_input = dash_dcc.find_element("#dropdown input") + search_input.send_keys("4") + options = dash_dcc.find_elements("#dropdown .VirtualizedSelectOption") + + assert len(options) == 1 + assert options[0].text == "h4" + + def search_indexed(value, length, texts): + search = dash_dcc.find_element("#indexed-search input") + dash_dcc.clear_input(search) + search.send_keys(value) + opts = dash_dcc.find_elements("#indexed-search .VirtualizedSelectOption") + + assert len(opts) == length + assert [o.text for o in opts] == texts + + search_indexed("o", 2, ["one", "two"]) + search_indexed("1", 1, ["one"]) + search_indexed("uno", 1, ["one"]) + # FIXME clear_input doesnt work well when the input is focused. (miss the o) + dash_dcc.clear_input("#indexed-search input") + search_indexed("dos", 1, ["two"])