Skip to content

Commit fe52f2c

Browse files
authored
Merge branch 'dev' into dev
2 parents 1c02511 + adad025 commit fe52f2c

42 files changed

Lines changed: 4376 additions & 1160 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.circleci/config.yml

Lines changed: 0 additions & 507 deletions
This file was deleted.

.github/workflows/testing.yml

Lines changed: 683 additions & 112 deletions
Large diffs are not rendered by default.

.nvmrc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
lts/iron
1+
24

.test_durations

Lines changed: 333 additions & 0 deletions
Large diffs are not rendered by default.

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,16 @@ This project adheres to [Semantic Versioning](https://semver.org/).
77
## Added
88
- [#3669](https://github.com/plotly/dash/pull/3669) Selection for DataTable cleared with custom action settings
99
- [#3680](https://github.com/plotly/dash/pull/3680) Added `search_order` prop to `Dropdown` to allow users to preserve original option order during search
10+
- Added `csrf_token_name` and `csrf_header_name` config options to allow configuring the CSRF cookie and header names. Fixes [#729](https://github.com/plotly/dash/issues/729)
1011

1112
## Added
1213
- [#3523](https://github.com/plotly/dash/pull/3523) Fall back to background callback function names if source cannot be found
1314

1415
## Fixed
1516
- [#3690](https://github.com/plotly/dash/pull/3690) Fixes Input when min or max is set to None
1617
- [#3723](https://github.com/plotly/dash/pull/3723) Fix misaligned `dcc.Slider` marks when some labels are empty strings
18+
- [#3740](https://github.com/plotly/dash/pull/3740) Fix cannot tab into dropdowns in Safari
19+
- [#2462](https://github.com/plotly/dash/issues/2462) Allow `MATCH` in `Input`/`State` when the callback's `Output` has no wildcards (fixed-id Output, no Output, or `ALL`-only wildcard Output). `ALLSMALLER` still requires a corresponding `MATCH` in an Output.
1720

1821
## [4.1.0] - 2026-03-23
1922

components/dash-core-components/.test_durations

Lines changed: 428 additions & 0 deletions
Large diffs are not rendered by default.

components/dash-core-components/src/components/css/dropdown.css

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,12 @@
5555
cursor: not-allowed;
5656
}
5757

58+
.dash-dropdown-focus-target {
59+
position: absolute;
60+
opacity: 0;
61+
pointer-events: none;
62+
}
63+
5864
.dash-dropdown-value {
5965
max-width: 100%;
6066
text-align: left;

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

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -313,6 +313,7 @@ const Dropdown = (props: DropdownProps) => {
313313
const relevantKeys = [
314314
'ArrowDown',
315315
'ArrowUp',
316+
'Tab',
316317
'PageDown',
317318
'PageUp',
318319
'Home',
@@ -342,6 +343,19 @@ const Dropdown = (props: DropdownProps) => {
342343
let nextIndex: number;
343344

344345
switch (e.key) {
346+
case 'Tab': {
347+
// Trap Tab inside the popover so Safari (which
348+
// skips non-text inputs) can navigate options.
349+
const next = current + (e.shiftKey ? -1 : 1);
350+
if (next < minIndex) {
351+
nextIndex = maxIndex;
352+
} else if (next > maxIndex) {
353+
nextIndex = minIndex;
354+
} else {
355+
nextIndex = next;
356+
}
357+
break;
358+
}
345359
case 'ArrowDown':
346360
nextIndex = current < maxIndex ? current + 1 : minIndex;
347361
break;
@@ -408,12 +422,37 @@ const Dropdown = (props: DropdownProps) => {
408422

409423
const popover = (
410424
<Popover.Root open={isOpen} onOpenChange={handleOpenChange}>
425+
{/* Safari skips <button> in the Tab order; this hidden
426+
input receives Tab focus and delegates to the button. */}
427+
<input
428+
className="dash-dropdown-focus-target"
429+
tabIndex={disabled ? -1 : 0}
430+
readOnly
431+
aria-hidden="true"
432+
onFocus={e => {
433+
if (e.relatedTarget !== dropdownContainerRef.current) {
434+
e.currentTarget.tabIndex = -1;
435+
dropdownContainerRef.current?.focus();
436+
}
437+
}}
438+
onClick={() => {
439+
dropdownContainerRef.current?.click();
440+
}}
441+
/>
411442
<Popover.Trigger asChild>
412443
<button
413444
id={id}
414445
ref={dropdownContainerRef}
415446
disabled={disabled}
416447
type="button"
448+
tabIndex={-1}
449+
onBlur={e => {
450+
const dummyInput =
451+
e.currentTarget.previousElementSibling;
452+
if (dummyInput instanceof HTMLElement) {
453+
dummyInput.tabIndex = 0;
454+
}
455+
}}
417456
onKeyDown={e => {
418457
if (['ArrowDown', 'Enter'].includes(e.key)) {
419458
e.preventDefault();

components/dash-core-components/tests/integration/calendar/test_date_picker_single.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,9 @@ def test_dtps012_initial_visible_month(dash_dcc):
142142

143143
# Check that calendar shows January 2010 (initial_visible_month), not June 2020 (date)
144144
month_dropdown = dash_dcc.find_element(".dash-datepicker-controls .dash-dropdown")
145-
year_input = dash_dcc.find_element(".dash-datepicker-controls input")
145+
year_input = dash_dcc.find_element(
146+
".dash-datepicker-controls input:not([aria-hidden])"
147+
)
146148

147149
assert "January" in month_dropdown.text, "Calendar should show January"
148150
assert year_input.get_attribute("value") == "2010", "Calendar should show year 2010"

components/dash-core-components/tests/integration/calendar/test_portal.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,9 @@ def click_everything_in_datepicker(datepicker_id, dash_dcc):
2828
)
2929
)
3030

31-
interactive_elements.extend(popover.find_elements(By.CSS_SELECTOR, "input"))
31+
interactive_elements.extend(
32+
popover.find_elements(By.CSS_SELECTOR, "input:not([aria-hidden])")
33+
)
3234

3335
buttons = reversed(
3436
popover.find_elements(By.CSS_SELECTOR, "button")

0 commit comments

Comments
 (0)