From 09e550488bf1c993b56d2fea55f04ea4915397d2 Mon Sep 17 00:00:00 2001 From: Adrian Borrmann Date: Tue, 23 Sep 2025 14:58:23 -0600 Subject: [PATCH 01/16] fix bug and add test for slider steps --- .../src/components/RangeSlider.tsx | 6 +- .../src/components/Slider.tsx | 13 +++- .../integration/sliders/test_sliders_step.py | 65 +++++++++++++++++++ 3 files changed, 82 insertions(+), 2 deletions(-) create mode 100644 components/dash-core-components/tests/integration/sliders/test_sliders_step.py diff --git a/components/dash-core-components/src/components/RangeSlider.tsx b/components/dash-core-components/src/components/RangeSlider.tsx index b1d8175bf8..0fb241c004 100644 --- a/components/dash-core-components/src/components/RangeSlider.tsx +++ b/components/dash-core-components/src/components/RangeSlider.tsx @@ -24,7 +24,11 @@ export default function RangeSlider({ // Some considerations for the default value of `step`: // If the range consists of integers, default to a value of `1` // Otherwise, leave it undefined - if (Number.isInteger(props.min) && Number.isInteger(props.max)) { + if ( + typeof step === 'undefined' && + Number.isInteger(props.min) && + Number.isInteger(props.max) + ) { step = 1; } diff --git a/components/dash-core-components/src/components/Slider.tsx b/components/dash-core-components/src/components/Slider.tsx index 7ba53f9b30..a1b4dfd368 100644 --- a/components/dash-core-components/src/components/Slider.tsx +++ b/components/dash-core-components/src/components/Slider.tsx @@ -22,7 +22,7 @@ export default function Slider({ persistence_type = PersistenceTypes.local, // eslint-disable-next-line no-magic-numbers verticalHeight = 400, - step = 1, + step = undefined, setProps, value, drag_value, @@ -31,6 +31,17 @@ export default function Slider({ // This is actually a wrapper around a RangeSlider. // We'll modify key `Slider` props to be compatible with a Range Slider. + // Some considerations for the default value of `step`: + // If the range consists of integers, default to a value of `1` + // Otherwise, leave it undefined + if ( + typeof step === 'undefined' && + Number.isInteger(props.min) && + Number.isInteger(props.max) + ) { + step = 1; + } + const mappedValue: RangeSliderProps['value'] = useMemo(() => { return typeof value === 'number' ? [value] : value; }, [value]); diff --git a/components/dash-core-components/tests/integration/sliders/test_sliders_step.py b/components/dash-core-components/tests/integration/sliders/test_sliders_step.py new file mode 100644 index 0000000000..8839049377 --- /dev/null +++ b/components/dash-core-components/tests/integration/sliders/test_sliders_step.py @@ -0,0 +1,65 @@ +import pytest +from dash import Dash, Input, Output, dcc, html +from humanfriendly import parse_size + +test_cases = [ + {"step": 2, "min": 0, "max": 10, "value": 6}, + {"step": 3, "min": 0, "max": 100, "value": 33}, + {"step": 0.05, "min": 0, "max": 1, "value": 0.5}, + {"step": 1_000_000, "min": 1e9, "max": 1e10, "value": 1e10}, +] + + +def slider_value_divisible_by_step(slider_args, slider_value) -> bool: + if type(slider_value) is str: + slider_value = float(slider_value.split()[-1]) + + if slider_value == slider_args["min"] or slider_value == slider_args["max"]: + return True + + step = slider_args["step"] + remainder = slider_value % step + + # For float equality, we check if the remainder is close to 0 or close to step + return remainder < 1e-10 or abs(remainder - step) < 1e-10 + + +@pytest.mark.parametrize("test_case", test_cases) +def test_slst001_step_params(dash_dcc, test_case): + + app = Dash(__name__) + app.layout = html.Div( + [ + dcc.Slider(id="slider", **test_case), + html.Div(id="out"), + ] + ) + + @app.callback(Output("out", "children"), [Input("slider", "value")]) + def update_output(value): + return f"{value}" + + dash_dcc.start_server(app) + dash_dcc.driver.set_window_size(800, 600) + + slider = dash_dcc.find_element("#slider") + marks = dash_dcc.find_elements(".dash-slider-mark") + + # Expect to find some amount of marks in between the first and last mark + assert len(marks) > 2 + + # Every mark must be divisible by the given `step`. + for mark in marks: + value = parse_size(mark.text) + assert slider_value_divisible_by_step(test_case, value) + + # Perform multiple clicks along the slider track. After every click, the + # resulting slider value must be divisible by the step + i = 0 + while i < 1: + dash_dcc.click_at_coord_fractions(slider, i, 0.25) + value = dash_dcc.find_element("#out").text + assert slider_value_divisible_by_step(test_case, value) + i += 0.05 + + assert dash_dcc.get_logs() == [] From 9166dad9c890096c7ea96417b1a2381e2d01797f Mon Sep 17 00:00:00 2001 From: Adrian Borrmann Date: Wed, 24 Sep 2025 09:07:20 -0600 Subject: [PATCH 02/16] support localizations in dropdown component --- .../src/components/Dropdown.tsx | 19 ++- .../src/fragments/Dropdown.tsx | 20 ++-- components/dash-core-components/src/types.ts | 13 ++ .../integration/dropdown/test_localization.py | 113 ++++++++++++++++++ 4 files changed, 156 insertions(+), 9 deletions(-) create mode 100644 components/dash-core-components/tests/integration/dropdown/test_localization.py diff --git a/components/dash-core-components/src/components/Dropdown.tsx b/components/dash-core-components/src/components/Dropdown.tsx index dc076e025c..89e85f6b30 100644 --- a/components/dash-core-components/src/components/Dropdown.tsx +++ b/components/dash-core-components/src/components/Dropdown.tsx @@ -1,9 +1,19 @@ -import React, {Component, lazy, Suspense} from 'react'; +import React, {lazy, Suspense} from 'react'; import {DropdownProps, PersistedProps, PersistenceTypes} from '../types'; import dropdown from '../utils/LazyLoader/dropdown'; const RealDropdown = lazy(dropdown); +const defaultLocalizations: DropdownProps['localizations'] = { + select_all: 'Select All', + deselect_all: 'Deselect All', + selected_count: '{num_selected} selected', + search: 'Search', + clear_search: 'Clear search', + clear_selection: 'Clear selection', + no_options_found: 'No options found', +}; + /** * Dropdown is an interactive dropdown element for selecting one or more * items. @@ -19,6 +29,7 @@ export default function Dropdown({ disabled = false, multi = false, searchable = true, + localizations = defaultLocalizations, // eslint-disable-next-line no-magic-numbers optionHeight = 36, // eslint-disable-next-line no-magic-numbers @@ -30,11 +41,17 @@ export default function Dropdown({ persistence_type = PersistenceTypes.local, ...props }: DropdownProps) { + localizations = { + ...defaultLocalizations, + ...localizations, + }; + return ( { closeOnSelect, clearable, disabled, + localizations, maxHeight, multi, options, @@ -459,7 +460,10 @@ const Dropdown = (props: DropdownProps) => { )} {sanitizedValues.length > 1 && ( - {sanitizedValues.length} selected + {localizations?.selected_count?.replace( + '{num_selected}', + `${sanitizedValues.length}` + )} )} {clearable && !disabled && !!sanitizedValues.length && ( @@ -469,8 +473,8 @@ const Dropdown = (props: DropdownProps) => { e.preventDefault(); handleClear(); }} - title="Clear selection" - aria-label="Clear selection" + title={localizations?.clear_selection} + aria-label={localizations?.clear_selection} > @@ -497,7 +501,7 @@ const Dropdown = (props: DropdownProps) => { @@ -510,7 +514,7 @@ const Dropdown = (props: DropdownProps) => { type="button" className="dash-dropdown-clear" onClick={handleClearSearch} - aria-label="Clear search" + aria-label={localizations?.clear_search} > @@ -524,7 +528,7 @@ const Dropdown = (props: DropdownProps) => { className="dash-dropdown-action-button" onClick={handleSelectAll} > - Select All + {localizations?.select_all} {canDeselectAll && ( )} @@ -562,7 +566,7 @@ const Dropdown = (props: DropdownProps) => { {search_value && displayOptions.length === 0 && ( - No options found + {localizations?.no_options_found} )} diff --git a/components/dash-core-components/src/types.ts b/components/dash-core-components/src/types.ts index 7e51d4b68f..df1a39f2f7 100644 --- a/components/dash-core-components/src/types.ts +++ b/components/dash-core-components/src/types.ts @@ -467,6 +467,19 @@ export interface DropdownProps { */ id?: string; + /** + * Translations for customizing text contained within this component. + */ + localizations?: { + select_all?: string; + deselect_all?: string; + selected_count?: string; + search?: string; + clear_search?: string; + clear_selection?: string; + no_options_found?: string; + }; + /** * Dash-assigned callback that gets fired when the input changes */ diff --git a/components/dash-core-components/tests/integration/dropdown/test_localization.py b/components/dash-core-components/tests/integration/dropdown/test_localization.py new file mode 100644 index 0000000000..e946543e64 --- /dev/null +++ b/components/dash-core-components/tests/integration/dropdown/test_localization.py @@ -0,0 +1,113 @@ +from dash import Dash +from dash.dcc import Dropdown +from dash.html import Div + + +def test_ddlo001_translations(dash_duo): + app = Dash(__name__) + app.layout = Div( + [ + Dropdown( + id="dropdown", + options=[1, 2, 3], + multi=True, + localizations={ + "select_all": "Sélectionner tout", + "deselect_all": "Désélectionner tout", + "selected_count": "{num_selected} sélections", + "search": "Rechercher", + "clear_search": "Annuler", + "clear_selection": "Effacer les sélections", + "no_options_found": "Aucun d'options", + }, + ), + ] + ) + + dash_duo.start_server(app) + + dash_duo.find_element("#dropdown").click() + dash_duo.wait_for_contains_text( + "#dropdown .dash-dropdown-action-button:first-child", "Sélectionner tout" + ) + dash_duo.wait_for_contains_text( + "#dropdown .dash-dropdown-action-button:last-child", "Désélectionner tout" + ) + + search_input = dash_duo.find_element("#dropdown .dash-dropdown-search") + assert search_input.accessible_name == "Rechercher" + + search_input.send_keys(1) + assert ( + dash_duo.find_element("#dropdown .dash-dropdown-clear").accessible_name + == "Annuler" + ) + + dash_duo.find_element("#dropdown .dash-dropdown-action-button:first-child").click() + + search_input.send_keys(9) + assert ( + dash_duo.find_element("#dropdown .dash-dropdown-option").text + == "Aucun d'options" + ) + + assert ( + dash_duo.find_element( + "#dropdown .dash-dropdown-trigger .dash-dropdown-clear" + ).accessible_name + == "Effacer les sélections" + ) + + assert dash_duo.get_logs() == [] + + +def test_ddlo002_partial_translations(dash_duo): + app = Dash(__name__) + app.layout = Div( + [ + Dropdown( + id="dropdown", + options=[1, 2, 3], + multi=True, + localizations={ + "search": "Lookup", + }, + ), + ] + ) + + dash_duo.start_server(app) + + dash_duo.find_element("#dropdown").click() + dash_duo.wait_for_contains_text( + "#dropdown .dash-dropdown-action-button:first-child", "Select All" + ) + dash_duo.wait_for_contains_text( + "#dropdown .dash-dropdown-action-button:last-child", "Deselect All" + ) + + search_input = dash_duo.find_element("#dropdown .dash-dropdown-search") + assert search_input.accessible_name == "Lookup" + + search_input.send_keys(1) + assert ( + dash_duo.find_element("#dropdown .dash-dropdown-clear").accessible_name + == "Clear search" + ) + + dash_duo.find_element("#dropdown .dash-dropdown-action-button:first-child").click() + + search_input.send_keys(9) + assert ( + dash_duo.find_element("#dropdown .dash-dropdown-option").text + == "No options found" + ) + + assert ( + dash_duo.find_element( + "#dropdown .dash-dropdown-trigger .dash-dropdown-clear" + ).accessible_name + == "Clear selection" + ) + + assert dash_duo.get_logs() == [] From 47ce558441494cc306fc4208e0a07ec9c54e31ac Mon Sep 17 00:00:00 2001 From: Adrian Borrmann Date: Wed, 24 Sep 2025 10:41:12 -0600 Subject: [PATCH 03/16] Fix responsive option labels in dropdown --- .../dash-core-components/src/components/Dropdown.tsx | 3 +-- .../src/components/css/dropdown.css | 11 ++++------- components/dash-core-components/src/types.ts | 2 +- 3 files changed, 6 insertions(+), 10 deletions(-) diff --git a/components/dash-core-components/src/components/Dropdown.tsx b/components/dash-core-components/src/components/Dropdown.tsx index 89e85f6b30..8e2eb9c8cb 100644 --- a/components/dash-core-components/src/components/Dropdown.tsx +++ b/components/dash-core-components/src/components/Dropdown.tsx @@ -30,8 +30,7 @@ export default function Dropdown({ multi = false, searchable = true, localizations = defaultLocalizations, - // eslint-disable-next-line no-magic-numbers - optionHeight = 36, + optionHeight = 'auto', // eslint-disable-next-line no-magic-numbers maxHeight = 200, closeOnSelect = !multi, diff --git a/components/dash-core-components/src/components/css/dropdown.css b/components/dash-core-components/src/components/css/dropdown.css index 9e1f4b2705..cad53927f9 100644 --- a/components/dash-core-components/src/components/css/dropdown.css +++ b/components/dash-core-components/src/components/css/dropdown.css @@ -61,8 +61,9 @@ .dash-dropdown-content { background: var(--Dash-Fill-Inverse-Strong); - min-width: fit-content; - width: var(--radix-popover-trigger-width); + width: fit-content; + min-width: var(--radix-popover-trigger-width); + max-width: 100vw; overflow-y: auto; z-index: 50; box-shadow: 0px 10px 38px -10px rgba(22, 23, 24, 0.35), @@ -190,7 +191,7 @@ } .dash-dropdown-option { - padding: var(--Dash-Spacing) calc(var(--Dash-Spacing) * 3); + padding: calc(var(--Dash-Spacing) * 2) calc(var(--Dash-Spacing) * 3); background: var(--Dash-Fill-Inverse-strong); box-shadow: 0 -1px 0 0 var(--Dash-Fill-Disabled) inset; color: var(--Dash-Text-Strong); @@ -212,10 +213,6 @@ cursor: not-allowed; } -.dash-dropdown-option-text { - white-space: pre; -} - .dash-dropdown-option-checkbox { display: inline-block; margin: 0 calc(var(--Dash-Spacing) * 2) 0 0; diff --git a/components/dash-core-components/src/types.ts b/components/dash-core-components/src/types.ts index df1a39f2f7..6c32e48d3a 100644 --- a/components/dash-core-components/src/types.ts +++ b/components/dash-core-components/src/types.ts @@ -443,7 +443,7 @@ export interface DropdownProps { /** * height of each option. Can be increased when label lengths would wrap around */ - optionHeight?: number; + optionHeight?: 'auto' | number; /** * height of the options dropdown. From bcf9b107e8c749e9b1f53d8e7eb74d63443ee467 Mon Sep 17 00:00:00 2001 From: Adrian Borrmann Date: Wed, 24 Sep 2025 14:57:18 -0600 Subject: [PATCH 04/16] Fix accessibility issues in dropdown --- .../src/components/css/dropdown.css | 19 +- .../src/fragments/Dropdown.tsx | 221 +++++++++--------- .../tests/integration/dropdown/test_a11y.py | 128 ++++++++++ 3 files changed, 250 insertions(+), 118 deletions(-) create mode 100644 components/dash-core-components/tests/integration/dropdown/test_a11y.py diff --git a/components/dash-core-components/src/components/css/dropdown.css b/components/dash-core-components/src/components/css/dropdown.css index cad53927f9..1181864ed2 100644 --- a/components/dash-core-components/src/components/css/dropdown.css +++ b/components/dash-core-components/src/components/css/dropdown.css @@ -1,6 +1,14 @@ .dash-dropdown { + display: block; box-sizing: border-box; margin: calc(var(--Dash-Spacing) * 2) 0; + padding: 0; + background: inherit; + border: none; + width: 100%; + cursor: pointer; + font-size: inherit; + overflow: hidden; } .dash-dropdown-grid-container { @@ -19,7 +27,7 @@ grid-template-columns: auto 1fr auto auto; } -.dash-dropdown-trigger, +.dash-dropdown, .dash-dropdown-content { border-radius: var(--Dash-Spacing); border: 1px solid var(--Dash-Stroke-Strong); @@ -28,17 +36,12 @@ } .dash-dropdown-trigger { - background: inherit; - padding: 6px 12px; - width: 100%; + padding: 0 12px; min-height: 32px; height: 100%; - cursor: pointer; - font-size: inherit; - overflow: hidden; } -.dash-dropdown-trigger:disabled { +.dash-dropdown:disabled { opacity: 0.6; cursor: not-allowed; } diff --git a/components/dash-core-components/src/fragments/Dropdown.tsx b/components/dash-core-components/src/fragments/Dropdown.tsx index bdfe208383..e9d5ea4550 100644 --- a/components/dash-core-components/src/fragments/Dropdown.tsx +++ b/components/dash-core-components/src/fragments/Dropdown.tsx @@ -105,7 +105,7 @@ const Dropdown = (props: DropdownProps) => { DetailedDropdownOption[] >([]); const persistentOptions = useRef([]); - const dropdownContainerRef = useRef(null); + const dropdownContainerRef = useRef(null); const ctx = window.dash_component_api.useDashContext(); const loading = ctx.useLoading(); @@ -428,26 +428,26 @@ const Dropdown = (props: DropdownProps) => { ); return ( -
- - - - - - - e.preventDefault()} - onKeyDown={handleKeyDown} - style={{ - maxHeight: maxHeight ? `${maxHeight}px` : 'auto', - }} - > - {searchable && ( -
- - - onInputChange(e.target.value) - } - autoFocus - /> - {search_value && ( - - )} -
- )} - {multi && ( -
+ + + + + + e.preventDefault()} + onKeyDown={handleKeyDown} + style={{ + maxHeight: maxHeight ? `${maxHeight}px` : 'auto', + }} + > + {searchable && ( +
+ + onInputChange(e.target.value)} + autoFocus + /> + {search_value && ( + + )} +
+ )} + {multi && ( +
+ + {canDeselectAll && ( - {canDeselectAll && ( - - )} -
- )} - {isOpen && ( -
- {displayOptions.map((option, i) => { - const isSelected = multi - ? sanitizedValues.includes(option.value) - : value === option.value; - - return ( - - ); - })} - {search_value && - displayOptions.length === 0 && ( - - {localizations?.no_options_found} - - )} -
- )} -
-
- -
+ )} +
+ )} + {isOpen && ( +
+ {displayOptions.map((option, i) => { + const isSelected = multi + ? sanitizedValues.includes(option.value) + : value === option.value; + + return ( + + ); + })} + {search_value && displayOptions.length === 0 && ( + + {localizations?.no_options_found} + + )} +
+ )} + + + ); }; diff --git a/components/dash-core-components/tests/integration/dropdown/test_a11y.py b/components/dash-core-components/tests/integration/dropdown/test_a11y.py new file mode 100644 index 0000000000..21d9f7fefb --- /dev/null +++ b/components/dash-core-components/tests/integration/dropdown/test_a11y.py @@ -0,0 +1,128 @@ +import pytest +from dash import Dash +from dash.dcc import Dropdown +from dash.html import Div, Label, P +from selenium.common.exceptions import TimeoutException +from selenium.webdriver.common.keys import Keys +from selenium.webdriver.common.action_chains import ActionChains +from time import sleep + + +def test_a11y001_label_focuses_dropdown(dash_duo): + app = Dash(__name__) + app.layout = Label( + [ + P("Click me", id="label"), + Dropdown( + id="dropdown", + options=[1, 2, 3], + multi=True, + placeholder="Testing label that wraps a dropdown can trigger the dropdown", + ), + ], + ) + + dash_duo.start_server(app) + + dash_duo.wait_for_element("#dropdown") + + with pytest.raises(TimeoutException): + dash_duo.wait_for_element("#dropdown .dash-dropdown-options", timeout=0.25) + + dash_duo.find_element("#label").click() + dash_duo.wait_for_element("#dropdown .dash-dropdown-options") + + assert dash_duo.get_logs() == [] + + +def test_a11y002_label_with_htmlFor_can_focus_dropdown(dash_duo): + app = Dash(__name__) + app.layout = Div( + [ + Label("Click me", htmlFor="dropdown", id="label"), + Dropdown( + id="dropdown", + options=[1, 2, 3], + multi=True, + placeholder="Testing label with `htmlFor` triggers the dropdown", + ), + ], + ) + + dash_duo.start_server(app) + + dash_duo.wait_for_element("#dropdown") + + with pytest.raises(TimeoutException): + dash_duo.wait_for_element("#dropdown .dash-dropdown-options", timeout=0.25) + + dash_duo.find_element("#label").click() + dash_duo.wait_for_element("#dropdown .dash-dropdown-options") + + assert dash_duo.get_logs() == [] + + +def test_a11y003_keyboard_navigation(dash_duo): + def send_keys(key): + actions = ActionChains(dash_duo.driver) + actions.send_keys(key) + actions.perform() + + app = Dash(__name__) + app.layout = Div( + [ + Dropdown( + id="dropdown", + options=[i for i in range(0, 100)], + multi=True, + placeholder="Testing keyboard navigation", + ), + ], + ) + + dash_duo.start_server(app) + + dropdown = dash_duo.find_element("#dropdown") + dropdown.click() + dash_duo.wait_for_element("#dropdown .dash-dropdown-options") + + send_keys( + Keys.ESCAPE + ) # Expecting focus to remain on the dropdown after escaping out + with pytest.raises(TimeoutException): + dash_duo.wait_for_element("#dropdown .dash-dropdown-options", timeout=0.25) + + send_keys(Keys.ARROW_DOWN) # Expecting the dropdown to open up + dash_duo.wait_for_element("#dropdown .dash-dropdown-search") + + num_elements = len(dash_duo.find_elements(".dash-dropdown-option")) + assert num_elements == 100 + + send_keys(1) # Expecting to be typing into the searh bar + num_elements = len(dash_duo.find_elements(".dash-dropdown-option")) + assert num_elements == 19 + + send_keys(Keys.ARROW_DOWN) # Expecting to be navigating through the options + send_keys(Keys.SPACE) # Expecting to be selecting + assert dash_duo.find_element(".dash-dropdown-value").text == "1" + + send_keys(Keys.ARROW_DOWN) # Expecting to be navigating through the options + send_keys(Keys.SPACE) # Expecting to be selecting + assert dash_duo.find_element(".dash-dropdown-value").text == "1, 10" + + send_keys(Keys.SPACE) # Expecting to be de-selecting + assert dash_duo.find_element(".dash-dropdown-value").text == "1" + + send_keys(Keys.ARROW_UP) + send_keys(Keys.ARROW_UP) + send_keys(Keys.ARROW_UP) # Expecting to wrap over to the last item + send_keys(Keys.SPACE) + assert dash_duo.find_element(".dash-dropdown-value").text == "1, 91" + + send_keys( + Keys.ESCAPE + ) # Expecting focus to remain on the dropdown after escaping out + sleep(0.25) + assert dash_duo.find_element(".dash-dropdown-value").text == "1, 91" + + assert dash_duo.get_logs() == [] From 0b77015ff22f607336e145599d517f7c9e57daea Mon Sep 17 00:00:00 2001 From: Adrian Borrmann Date: Wed, 24 Sep 2025 14:57:50 -0600 Subject: [PATCH 05/16] Update changelog --- CHANGELOG.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7264663f93..8da1bd5d36 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,18 @@ All notable changes to `dash` will be documented in this file. This project adheres to [Semantic Versioning](https://semver.org/). +## UNRELEASED + +## Changed + +- Dropdown API changes + * default value of optionHeight is now 'auto' which supports text wrapping of lengthy text on small screens; you can still specify a numeric pixel height if desired + * new `localizations` prop to customize strings used within the component + * default value for closeOnSelect is now `True` for single-select dropdowns and `False` for multi-select + +- Slider API changes + * default value of `step` is now only set to `1` if the `min` and `max` props are both integers. Otherwise, it will be dynamically computed according to the available space for the slider + ## [4.0.0rc1] - 2025-09-22 ## Added From e16cec085aa1d7cbff263cf189e2bf33fda37093 Mon Sep 17 00:00:00 2001 From: Adrian Borrmann Date: Thu, 25 Sep 2025 09:46:00 -0600 Subject: [PATCH 06/16] Fix input focus state --- .../src/components/css/input.css | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/components/dash-core-components/src/components/css/input.css b/components/dash-core-components/src/components/css/input.css index ef4e444c1e..1fb786c064 100644 --- a/components/dash-core-components/src/components/css/input.css +++ b/components/dash-core-components/src/components/css/input.css @@ -15,6 +15,14 @@ overflow: hidden; } +.dash-input-container:focus-within { + outline: 2px solid var(--Dash-Fill-Interactive-Strong); +} + +.dash-input-container input:focus { + outline: none; +} + .dash-input-container:has(input[type='range']) { background: inherit; } @@ -37,6 +45,7 @@ /* Hide native steppers for number inputs */ .dash-input-element[type='number'] { -moz-appearance: textfield; + border-radius: 0; } .dash-input-element[type='number']::-webkit-outer-spin-button, @@ -70,10 +79,11 @@ cursor: pointer; font-size: 16px; font-weight: bold; - color: var(--Dash-Text-Primary); + color: var(--Dash -Text-Primary); } -.dash-input-stepper:hover { +.dash-input-stepper:hover, +.dash-input-stepper:focus { background: var(--Dash-Fill-Primary-Hover); } @@ -115,7 +125,3 @@ input.dash-input-element:invalid { outline: solid red; } - -input.dash-input-element:valid { - outline: none black; -} From 043cb5a26723f778c71e8d9c3ef3be7ac9d3fa96 Mon Sep 17 00:00:00 2001 From: Adrian Borrmann Date: Wed, 1 Oct 2025 15:34:28 -0600 Subject: [PATCH 07/16] fix further accessibility issues with inputs & dropdown --- .../src/components/css/dropdown.css | 8 ++- .../src/components/css/input.css | 12 +++- .../src/components/css/optionslist.css | 6 +- .../src/fragments/Dropdown.tsx | 22 ++++-- .../tests/integration/dropdown/test_a11y.py | 14 ++-- .../dropdown/test_clearable_false.py | 4 +- .../dropdown/test_dynamic_options.py | 6 +- .../integration/dropdown/test_localization.py | 38 ++++------ .../dropdown/test_remove_option.py | 4 +- .../tests/integration/dropdown/test_styles.py | 16 +++-- .../integration/dropdown/test_visibility.py | 4 +- .../tests/integration/input/test_a11y.py | 69 +++++++++++++++++++ dash/testing/browser.py | 2 +- .../renderer/test_children_reorder.py | 12 +--- .../renderer/test_component_as_prop.py | 2 +- 15 files changed, 150 insertions(+), 69 deletions(-) create mode 100644 components/dash-core-components/tests/integration/input/test_a11y.py diff --git a/components/dash-core-components/src/components/css/dropdown.css b/components/dash-core-components/src/components/css/dropdown.css index 08fddb8774..fc2def54c5 100644 --- a/components/dash-core-components/src/components/css/dropdown.css +++ b/components/dash-core-components/src/components/css/dropdown.css @@ -9,6 +9,8 @@ cursor: pointer; font-size: inherit; overflow: hidden; + accent-color: var(--Dash-Fill-Interactive-Strong); + outline-color: var(--Dash-Fill-Interactive-Strong); } .dash-dropdown-grid-container { @@ -66,9 +68,9 @@ background: var(--Dash-Fill-Inverse-Strong); width: fit-content; min-width: var(--radix-popover-trigger-width); - max-width: 100vw; + max-width: 98vw; overflow-y: auto; - z-index: 50; + z-index: 500; box-shadow: 0px 10px 38px -10px rgba(22, 23, 24, 0.35), 0px 10px 20px -15px rgba(22, 23, 24, 0.2); } @@ -179,6 +181,8 @@ text-decoration: none; color: var(--Dash-Text-Disabled); white-space: nowrap; + accent-color: var(--Dash-Fill-Interactive-Strong); + outline-color: var(--Dash-Fill-Interactive-Strong); } .dash-dropdown-action-button:hover { diff --git a/components/dash-core-components/src/components/css/input.css b/components/dash-core-components/src/components/css/input.css index 1fb786c064..6ca5d898fe 100644 --- a/components/dash-core-components/src/components/css/input.css +++ b/components/dash-core-components/src/components/css/input.css @@ -16,7 +16,12 @@ } .dash-input-container:focus-within { - outline: 2px solid var(--Dash-Fill-Interactive-Strong); + outline: 1px solid var(--Dash-Fill-Interactive-Strong); +} + +.dash-input-container:has(.dash-input-element:disabled) { + opacity: 0.6; + cursor: not-allowed; } .dash-input-container input:focus { @@ -40,6 +45,11 @@ box-sizing: border-box; z-index: 1; order: 2; + accent-color: var(--Dash-Fill-Interactive-Strong); +} + +.dash-input-element:disabled { + cursor: not-allowed; } /* Hide native steppers for number inputs */ diff --git a/components/dash-core-components/src/components/css/optionslist.css b/components/dash-core-components/src/components/css/optionslist.css index cc4bb4db0a..ba285604fa 100644 --- a/components/dash-core-components/src/components/css/optionslist.css +++ b/components/dash-core-components/src/components/css/optionslist.css @@ -29,10 +29,6 @@ margin: 0 calc(var(--Dash-Spacing) * 2) 0 0; box-sizing: border-box; border: 1px solid var(--Dash-Stroke-Strong); -} - -.dash-options-list-option-checkbox:hover, -.dash-options-list-option-checkbox:focus, -.dash-options-list-option-checkbox:checked { accent-color: var(--Dash-Fill-Interactive-Strong); + outline-color: var(--Dash-Fill-Interactive-Strong); } diff --git a/components/dash-core-components/src/fragments/Dropdown.tsx b/components/dash-core-components/src/fragments/Dropdown.tsx index ccb882331f..22e698833b 100644 --- a/components/dash-core-components/src/fragments/Dropdown.tsx +++ b/components/dash-core-components/src/fragments/Dropdown.tsx @@ -19,6 +19,7 @@ import '../components/css/dropdown.css'; import isEqual from 'react-fast-compare'; import {DetailedOption, DropdownProps, OptionValue} from '../types'; import {OptionsList, OptionLabel} from '../utils/optionRendering'; +import uuid from 'uniqid'; const Dropdown = (props: DropdownProps) => { const { @@ -344,6 +345,8 @@ const Dropdown = (props: DropdownProps) => { [filteredOptions, sanitizedValues] ); + const accessibleId = id ?? uuid(); + return ( @@ -359,24 +362,33 @@ const Dropdown = (props: DropdownProps) => { }} className={`dash-dropdown ${className ?? ''}`} style={style} - aria-label={props.placeholder} + aria-labelledby={`${accessibleId}-value-count ${accessibleId}-value`} aria-haspopup="listbox" aria-expanded={isOpen} data-dash-is-loading={loading || undefined} > {displayValue.length > 0 && ( - + {displayValue} )} {displayValue.length === 0 && ( - + {props.placeholder} )} {sanitizedValues.length > 1 && ( - + {localizations?.selected_count?.replace( '{num_selected}', `${sanitizedValues.length}` @@ -402,7 +414,7 @@ const Dropdown = (props: DropdownProps) => { - + Date: Thu, 2 Oct 2025 12:21:00 -0600 Subject: [PATCH 08/16] move slider className to top of component --- .../dash-core-components/src/fragments/RangeSlider.tsx | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/components/dash-core-components/src/fragments/RangeSlider.tsx b/components/dash-core-components/src/fragments/RangeSlider.tsx index 0e8164630d..01d25c5325 100644 --- a/components/dash-core-components/src/fragments/RangeSlider.tsx +++ b/components/dash-core-components/src/fragments/RangeSlider.tsx @@ -193,14 +193,12 @@ export default function RangeSlider(props: RangeSliderProps) { } }; + const classNames = ['dash-slider-container', className].filter(Boolean); + return ( {loadingProps => ( -
+
{showInputs && value.length === 2 && !vertical && ( Date: Thu, 2 Oct 2025 12:53:04 -0600 Subject: [PATCH 09/16] fix tests --- .../integration/misc/test_dcc_components_as_props.py | 12 ++++++------ .../tests/integration/misc/test_persistence.py | 8 ++++---- .../tests/integration/misc/test_platter.py | 3 +-- 3 files changed, 11 insertions(+), 12 deletions(-) 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 df9191ae03..5298328e80 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 @@ -49,13 +49,13 @@ def test_mdcap001_dcc_components_as_props(dash_dcc): dash_dcc.wait_for_text_to_equal("#radio-items p", "off") 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") + dash_dcc.wait_for_text_to_equal(".dash-dropdown-content h4", "h4") + dash_dcc.wait_for_text_to_equal(".dash-dropdown-content h6", "h6") - search_input = dash_dcc.find_element("#dropdown .dash-dropdown-search") + search_input = dash_dcc.find_element(".dash-dropdown-content .dash-dropdown-search") search_input.send_keys("4") sleep(0.25) - options = dash_dcc.find_elements("#dropdown .dash-dropdown-option") + options = dash_dcc.find_elements(".dash-dropdown-content .dash-dropdown-option") wait.until(lambda: len(options) == 1, 1) wait.until(lambda: options[0].text == "h4", 1) @@ -64,11 +64,11 @@ def test_mdcap001_dcc_components_as_props(dash_dcc): dash_dcc.find_element("#indexed-search").click() def search_indexed(value, length, texts): - search = dash_dcc.find_element("#indexed-search .dash-dropdown-search") + search = dash_dcc.find_element(".dash-dropdown-content .dash-dropdown-search") dash_dcc.clear_input(search) search.send_keys(value) sleep(0.25) - opts = dash_dcc.find_elements("#indexed-search .dash-dropdown-option") + opts = dash_dcc.find_elements(".dash-dropdown-content .dash-dropdown-option") assert len(opts) == length assert [o.text for o in opts] == texts diff --git a/components/dash-core-components/tests/integration/misc/test_persistence.py b/components/dash-core-components/tests/integration/misc/test_persistence.py index abc9a88d5f..4088686a2f 100644 --- a/components/dash-core-components/tests/integration/misc/test_persistence.py +++ b/components/dash-core-components/tests/integration/misc/test_persistence.py @@ -131,18 +131,18 @@ def make_output(*args): dash_dcc.select_date_single("datepickersingle", day="20") dash_dcc.find_element("#dropdownsingle").click() - dash_dcc.find_element("#dropdownsingle .dash-dropdown-search").send_keys( + dash_dcc.find_element(".dash-dropdown-content .dash-dropdown-search").send_keys( "one" + Keys.ENTER ) sleep(0.2) - dash_dcc.find_element("#dropdownsingle .dash-dropdown-option").click() + dash_dcc.find_element(".dash-dropdown-content .dash-dropdown-option").click() dash_dcc.find_element("#dropdownmulti").click() - dash_dcc.find_element("#dropdownmulti .dash-dropdown-search").send_keys( + dash_dcc.find_element(".dash-dropdown-content .dash-dropdown-search").send_keys( "six" + Keys.ENTER ) sleep(0.2) - dash_dcc.find_element("#dropdownmulti .dash-dropdown-option").click() + dash_dcc.find_element(".dash-dropdown-content .dash-dropdown-option").click() dash_dcc.find_element("#input").send_keys(" maybe") diff --git a/components/dash-core-components/tests/integration/misc/test_platter.py b/components/dash-core-components/tests/integration/misc/test_platter.py index a5a49230d6..4689c7ce04 100644 --- a/components/dash-core-components/tests/integration/misc/test_platter.py +++ b/components/dash-core-components/tests/integration/misc/test_platter.py @@ -6,7 +6,6 @@ def test_mspl001_dcc_components_platter(platter_app, dash_dcc): - dash_dcc.start_server(platter_app) dash_dcc.wait_for_element("#waitfor") @@ -17,7 +16,7 @@ def test_mspl001_dcc_components_platter(platter_app, dash_dcc): dash_dcc.percy_snapshot("gallery") dash_dcc.find_element("#dropdown").click() - dash_dcc.find_element("#dropdown .dash-dropdown-search").send_keys("北") + dash_dcc.find_element(".dash-dropdown-content .dash-dropdown-search").send_keys("北") dash_dcc.percy_snapshot("gallery - chinese character") text_input = dash_dcc.find_element("#textinput") From 7f5c5c31fb3c7adaa2fb755e19529b124a7f560f Mon Sep 17 00:00:00 2001 From: Adrian Borrmann Date: Thu, 2 Oct 2025 12:53:18 -0600 Subject: [PATCH 10/16] Add CSS variables for shadows --- .../dash-core-components/src/components/css/Dropdown.css | 8 -------- .../dash-core-components/src/components/css/dcc.css | 2 ++ .../dash-core-components/src/components/css/dropdown.css | 4 ++-- .../dash-core-components/src/components/css/sliders.css | 6 +++--- 4 files changed, 7 insertions(+), 13 deletions(-) delete mode 100644 components/dash-core-components/src/components/css/Dropdown.css diff --git a/components/dash-core-components/src/components/css/Dropdown.css b/components/dash-core-components/src/components/css/Dropdown.css deleted file mode 100644 index ffba1990f1..0000000000 --- a/components/dash-core-components/src/components/css/Dropdown.css +++ /dev/null @@ -1,8 +0,0 @@ -.dash-dropdown .Select-menu-outer { - z-index: 1000; - max-height: none; -} - -.dash-dropdown .Select-menu { - max-height: none; -} diff --git a/components/dash-core-components/src/components/css/dcc.css b/components/dash-core-components/src/components/css/dcc.css index fc03ab4424..66579edab0 100644 --- a/components/dash-core-components/src/components/css/dcc.css +++ b/components/dash-core-components/src/components/css/dcc.css @@ -11,4 +11,6 @@ --Dash-Fill-Primary-Hover: rgba(0, 18, 77, 0.04); --Dash-Fill-Primary-Active: rgba(0, 18, 77, 0.08); --Dash-Fill-Disabled: rgba(0, 24, 102, 0.1); + --Dash-Shading-Strong: rgba(22, 23, 24, 0.35); + --Dash-Shading-Weak: rgba(22, 23, 24, 0.2); } diff --git a/components/dash-core-components/src/components/css/dropdown.css b/components/dash-core-components/src/components/css/dropdown.css index fc2def54c5..a95dcc0bb6 100644 --- a/components/dash-core-components/src/components/css/dropdown.css +++ b/components/dash-core-components/src/components/css/dropdown.css @@ -71,8 +71,8 @@ max-width: 98vw; overflow-y: auto; z-index: 500; - box-shadow: 0px 10px 38px -10px rgba(22, 23, 24, 0.35), - 0px 10px 20px -15px rgba(22, 23, 24, 0.2); + box-shadow: 0px 10px 38px -10px var(--Dash-Shading-Strong), + 0px 10px 20px -15px var(--Dash-Shading-Weak); } .dash-dropdown-value-count, diff --git a/components/dash-core-components/src/components/css/sliders.css b/components/dash-core-components/src/components/css/sliders.css index 0c570f7d4c..ebe809340d 100644 --- a/components/dash-core-components/src/components/css/sliders.css +++ b/components/dash-core-components/src/components/css/sliders.css @@ -58,7 +58,7 @@ background-color: var(--Dash-Fill-Interactive-Strong); border: 2px solid var(--Dash-Fill-Inverse-Strong); border-radius: 50%; - box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1); + box-shadow: 0 1px 3px 0 var(--Dash-Shading-Weak); cursor: pointer; outline: none; transition: all 0.15s ease-in-out; @@ -67,7 +67,7 @@ .dash-slider-thumb:focus, .dash-slider-thumb:hover { transform: scale(1.125); /* Scale to make 16px thumb appear as 18px */ - box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1); + box-shadow: 0 4px 6px -1px var(--Dash-Shading-Weak); } .dash-slider-thumb:focus .dash-slider-tooltip, @@ -125,7 +125,7 @@ padding: calc(var(--Dash-Spacing) * 3); font-size: 12px; line-height: 1; - box-shadow: 0 0 8px rgba(0, 0, 0, 0.2); + box-shadow: 0 0 8px var(--Dash-Shading-Strong); background-color: var(--Dash-Fill-Inverse-Strong); user-select: none; z-index: 1000; From 292ece8627962b5d3925e2a83ac8a4d8c9c5e773 Mon Sep 17 00:00:00 2001 From: Adrian Borrmann Date: Thu, 2 Oct 2025 13:01:50 -0600 Subject: [PATCH 11/16] rename localizations to labels --- .../src/components/Dropdown.tsx | 12 ++++++------ .../src/fragments/Dropdown.tsx | 18 +++++++++--------- components/dash-core-components/src/types.ts | 2 +- 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/components/dash-core-components/src/components/Dropdown.tsx b/components/dash-core-components/src/components/Dropdown.tsx index 8e2eb9c8cb..f434c04ed2 100644 --- a/components/dash-core-components/src/components/Dropdown.tsx +++ b/components/dash-core-components/src/components/Dropdown.tsx @@ -4,7 +4,7 @@ import dropdown from '../utils/LazyLoader/dropdown'; const RealDropdown = lazy(dropdown); -const defaultLocalizations: DropdownProps['localizations'] = { +const defaultLabels: DropdownProps['labels'] = { select_all: 'Select All', deselect_all: 'Deselect All', selected_count: '{num_selected} selected', @@ -29,7 +29,7 @@ export default function Dropdown({ disabled = false, multi = false, searchable = true, - localizations = defaultLocalizations, + labels = defaultLabels, optionHeight = 'auto', // eslint-disable-next-line no-magic-numbers maxHeight = 200, @@ -40,9 +40,9 @@ export default function Dropdown({ persistence_type = PersistenceTypes.local, ...props }: DropdownProps) { - localizations = { - ...defaultLocalizations, - ...localizations, + labels = { + ...defaultLabels, + ...labels, }; return ( @@ -50,7 +50,7 @@ export default function Dropdown({ { closeOnSelect, clearable, disabled, - localizations, + labels, maxHeight, multi, options, @@ -389,7 +389,7 @@ const Dropdown = (props: DropdownProps) => { id={accessibleId + '-value-count'} className="dash-dropdown-value-count" > - {localizations?.selected_count?.replace( + {labels?.selected_count?.replace( '{num_selected}', `${sanitizedValues.length}` )} @@ -402,8 +402,8 @@ const Dropdown = (props: DropdownProps) => { e.preventDefault(); handleClear(); }} - title={localizations?.clear_selection} - aria-label={localizations?.clear_selection} + title={labels?.clear_selection} + aria-label={labels?.clear_selection} > @@ -431,7 +431,7 @@ const Dropdown = (props: DropdownProps) => { onInputChange(e.target.value)} @@ -442,7 +442,7 @@ const Dropdown = (props: DropdownProps) => { type="button" className="dash-dropdown-clear" onClick={handleClearSearch} - aria-label={localizations?.clear_search} + aria-label={labels?.clear_search} > @@ -456,7 +456,7 @@ const Dropdown = (props: DropdownProps) => { className="dash-dropdown-action-button" onClick={handleSelectAll} > - {localizations?.select_all} + {labels?.select_all} {canDeselectAll && ( )}
@@ -488,7 +488,7 @@ const Dropdown = (props: DropdownProps) => { {isOpen && search_value && !displayOptions.length && (
- {localizations?.no_options_found} + {labels?.no_options_found}
)} diff --git a/components/dash-core-components/src/types.ts b/components/dash-core-components/src/types.ts index 86de3ed070..5078882d2d 100644 --- a/components/dash-core-components/src/types.ts +++ b/components/dash-core-components/src/types.ts @@ -418,7 +418,7 @@ export interface DropdownProps extends BaseComponentProps { /** * Translations for customizing text contained within this component. */ - localizations?: { + labels?: { select_all?: string; deselect_all?: string; selected_count?: string; From a2c28b7127f74d382aecfa577c78b169bb7656ca Mon Sep 17 00:00:00 2001 From: Adrian Borrmann Date: Thu, 2 Oct 2025 13:16:54 -0600 Subject: [PATCH 12/16] rename test spec --- .../tests/integration/dropdown/test_localization.py | 4 ++-- .../integration/input/{test_a11y.py => test_a11y_input.py} | 0 2 files changed, 2 insertions(+), 2 deletions(-) rename components/dash-core-components/tests/integration/input/{test_a11y.py => test_a11y_input.py} (100%) diff --git a/components/dash-core-components/tests/integration/dropdown/test_localization.py b/components/dash-core-components/tests/integration/dropdown/test_localization.py index 0fad52b256..e93c53bdc9 100644 --- a/components/dash-core-components/tests/integration/dropdown/test_localization.py +++ b/components/dash-core-components/tests/integration/dropdown/test_localization.py @@ -11,7 +11,7 @@ def test_ddlo001_translations(dash_duo): id="dropdown", options=[1, 2, 3], multi=True, - localizations={ + labels={ "select_all": "Sélectionner tout", "deselect_all": "Désélectionner tout", "selected_count": "{num_selected} sélections", @@ -63,7 +63,7 @@ def test_ddlo002_partial_translations(dash_duo): id="dropdown", options=[1, 2, 3], multi=True, - localizations={ + labels={ "search": "Lookup", }, ), diff --git a/components/dash-core-components/tests/integration/input/test_a11y.py b/components/dash-core-components/tests/integration/input/test_a11y_input.py similarity index 100% rename from components/dash-core-components/tests/integration/input/test_a11y.py rename to components/dash-core-components/tests/integration/input/test_a11y_input.py From 571633953f2b15b0ca98e5e9f5c83ab61369d58c Mon Sep 17 00:00:00 2001 From: Adrian Borrmann Date: Fri, 3 Oct 2025 08:47:35 -0600 Subject: [PATCH 13/16] Update components/dash-core-components/src/types.ts Co-authored-by: Philippe Duval --- components/dash-core-components/src/types.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/dash-core-components/src/types.ts b/components/dash-core-components/src/types.ts index 5078882d2d..7bf9da8476 100644 --- a/components/dash-core-components/src/types.ts +++ b/components/dash-core-components/src/types.ts @@ -416,7 +416,7 @@ export interface DropdownProps extends BaseComponentProps { style?: React.CSSProperties; /** - * Translations for customizing text contained within this component. + * Text for customizing the labels rendered by this component. */ labels?: { select_all?: string; From 50ed7c49e2078d8277eb822052b148bc086f9ddb Mon Sep 17 00:00:00 2001 From: Adrian Borrmann Date: Fri, 3 Oct 2025 09:48:21 -0600 Subject: [PATCH 14/16] rename prop in changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8da1bd5d36..40ccec00a5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,7 +8,7 @@ This project adheres to [Semantic Versioning](https://semver.org/). - Dropdown API changes * default value of optionHeight is now 'auto' which supports text wrapping of lengthy text on small screens; you can still specify a numeric pixel height if desired - * new `localizations` prop to customize strings used within the component + * new `labels` prop to customize strings used within the component * default value for closeOnSelect is now `True` for single-select dropdowns and `False` for multi-select - Slider API changes From c50492991574e80afc1e8c9b012b10d1d6a11544 Mon Sep 17 00:00:00 2001 From: Adrian Borrmann Date: Fri, 3 Oct 2025 10:09:52 -0600 Subject: [PATCH 15/16] empty commit for ci From afb1f5c579ae6d64ab5d2afff9804a12ab39ce65 Mon Sep 17 00:00:00 2001 From: Adrian Borrmann Date: Fri, 3 Oct 2025 10:29:14 -0600 Subject: [PATCH 16/16] fix flaky test --- .../integration/dropdown/test_localization.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/components/dash-core-components/tests/integration/dropdown/test_localization.py b/components/dash-core-components/tests/integration/dropdown/test_localization.py index e93c53bdc9..e4be2b3491 100644 --- a/components/dash-core-components/tests/integration/dropdown/test_localization.py +++ b/components/dash-core-components/tests/integration/dropdown/test_localization.py @@ -34,15 +34,16 @@ def test_ddlo001_translations(dash_duo): ".dash-dropdown-action-button:last-child", "Désélectionner tout" ) - search_input = dash_duo.find_element(".dash-dropdown-search") - assert search_input.accessible_name == "Rechercher" + assert ( + dash_duo.find_element(".dash-dropdown-search").accessible_name == "Rechercher" + ) - search_input.send_keys(1) + dash_duo.find_element(".dash-dropdown-search").send_keys(1) assert dash_duo.find_element(".dash-dropdown-clear").accessible_name == "Annuler" dash_duo.find_element(".dash-dropdown-action-button:first-child").click() - search_input.send_keys(9) + dash_duo.find_element(".dash-dropdown-search").send_keys(9) assert dash_duo.find_element(".dash-dropdown-option").text == "Aucun d'options" assert ( @@ -80,17 +81,16 @@ def test_ddlo002_partial_translations(dash_duo): ".dash-dropdown-action-button:last-child", "Deselect All" ) - search_input = dash_duo.find_element(".dash-dropdown-search") - assert search_input.accessible_name == "Lookup" + assert dash_duo.find_element(".dash-dropdown-search").accessible_name == "Lookup" - search_input.send_keys(1) + dash_duo.find_element(".dash-dropdown-search").send_keys(1) assert ( dash_duo.find_element(".dash-dropdown-clear").accessible_name == "Clear search" ) dash_duo.find_element(".dash-dropdown-action-button:first-child").click() - search_input.send_keys(9) + dash_duo.find_element(".dash-dropdown-search").send_keys(9) assert dash_duo.find_element(".dash-dropdown-option").text == "No options found" assert (