Skip to content

Commit 27c7f7f

Browse files
authored
Merge pull request #3811 from plotly/bugfix/3425-datepicker-bothdates
Bugfix: datepicker `updatemode="bothdates"` not always respected
2 parents a28a458 + 1dc3c86 commit 27c7f7f

4 files changed

Lines changed: 53 additions & 10 deletions

File tree

‎CHANGELOG.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ This project adheres to [Semantic Versioning](https://semver.org/).
1212
- [#3819](https://github.com/plotly/dash/pull/3819) Fix `RuntimeError: No active request in context` when a non-Dash path falls through to the FastAPI catch-all route. Fixes [#3812](https://github.com/plotly/dash/issues/3812).
1313
- [#3838](https://github.com/plotly/dash/pull/3838) Replace `mcp` dependency with inline types.
1414
- [#3824](https://github.com/plotly/dash/pull/3824) Fix `dash.testing` `ThreadedRunner.stop()` hanging at teardown for Quart apps. Fixes [#3823](https://github.com/plotly/dash/issues/3823).
15+
- [#3425](https://github.com/plotly/dash/issues/3425) dcc.DatePicker: Fix `updatemode="bothdates"` not always respected
1516

1617
## Changed
1718
- Drop support for Python 3.8 (end-of-life since October 2024). The minimum supported version is now Python 3.9.

‎components/dash-core-components/src/fragments/DatePickerRange.tsx‎

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -167,8 +167,14 @@ const DatePickerRange = ({
167167
start_date: dateAsStr(internalStartDate),
168168
end_date: dateAsStr(internalEndDate),
169169
});
170-
} else if (endChanged && !internalEndDate) {
171-
// End date was cleared (user started a new range).
170+
} else if (
171+
updatemode === 'singledate' &&
172+
endChanged &&
173+
!internalEndDate
174+
) {
175+
// End date was cleared (user started a new range). Under
176+
// 'bothdates' we wait for a complete range before updating props,
177+
// so this partial update is only sent in 'singledate' mode.
172178
setProps({
173179
start_date: dateAsStr(internalStartDate) ?? null,
174180
end_date: null,
@@ -358,7 +364,7 @@ const DatePickerRange = ({
358364

359365
return (
360366
<div className="dash-datepicker" ref={containerRef}>
361-
<ResizeDetector onResize={handleResize} targets={[containerRef]}/>
367+
<ResizeDetector onResize={handleResize} targets={[containerRef]} />
362368
<Popover.Root
363369
open={!disabled && isCalendarOpen}
364370
onOpenChange={disabled ? undefined : setIsCalendarOpen}
@@ -388,9 +394,7 @@ const DatePickerRange = ({
388394
id={start_date_id || accessibleId}
389395
inputClassName="dash-datepicker-input dash-datepicker-start-date"
390396
value={startInputValue}
391-
onChange={e =>
392-
setStartInputValue(e.target?.value)
393-
}
397+
onChange={e => setStartInputValue(e.target?.value)}
394398
onKeyDown={handleStartInputKeyDown}
395399
onFocus={() => {
396400
if (isCalendarOpen) {
@@ -412,9 +416,7 @@ const DatePickerRange = ({
412416
id={end_date_id || accessibleId + '-end-date'}
413417
inputClassName="dash-datepicker-input dash-datepicker-end-date"
414418
value={endInputValue}
415-
onChange={e =>
416-
setEndInputValue(e.target?.value)
417-
}
419+
onChange={e => setEndInputValue(e.target?.value)}
418420
onKeyDown={handleEndInputKeyDown}
419421
onFocus={() => {
420422
if (isCalendarOpen) {

‎components/dash-core-components/tests/integration/calendar/test_date_picker_range.py‎

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -534,3 +534,43 @@ def display_dates(start_date, end_date):
534534
), "End input should display 2021-06-30"
535535

536536
assert dash_dcc.get_logs() == []
537+
538+
539+
def test_dtpr031_bothdates_no_partial_update_on_new_start(dash_dcc):
540+
"""Bug #3425: with updatemode='bothdates', picking a new start date (which
541+
clears the end date) must not fire a partial update for the incomplete range."""
542+
app = Dash(__name__)
543+
app.layout = html.Div(
544+
[
545+
dcc.DatePickerRange(
546+
id="dpr",
547+
min_date_allowed=datetime(2021, 1, 1),
548+
max_date_allowed=datetime(2021, 1, 31),
549+
initial_visible_month=datetime(2021, 1, 1),
550+
minimum_nights=0,
551+
updatemode="bothdates",
552+
display_format="MM/DD/YYYY",
553+
),
554+
html.Div(id="output"),
555+
]
556+
)
557+
558+
@app.callback(
559+
Output("output", "children"),
560+
Input("dpr", "start_date"),
561+
Input("dpr", "end_date"),
562+
prevent_initial_call=True,
563+
)
564+
def display_dates(start_date, end_date):
565+
return f"Start: {start_date}, End: {end_date}"
566+
567+
dash_dcc.start_server(app)
568+
569+
dash_dcc.select_date_range("dpr", day_range=(2, 11))
570+
dash_dcc.wait_for_text_to_equal("#output", "Start: 2021-01-02, End: 2021-01-11")
571+
572+
dash_dcc.select_date_range("dpr", day_range=(4,))
573+
time.sleep(0.5)
574+
assert dash_dcc.find_element("#output").text == "Start: 2021-01-02, End: 2021-01-11"
575+
576+
assert dash_dcc.get_logs() == []

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,7 @@ def test_dppt006_fullscreen_portal_close_button_keyboard(dash_dcc):
138138
assert popover.is_displayed()
139139

140140
action = ActionChains(dash_dcc.driver)
141-
action.move_to_element_with_offset(popover, 10, 10).click().perform()
141+
action.move_by_offset(10, 10).click().perform()
142142
sleep(0.2)
143143

144144
popover = dash_dcc.find_element(".dash-datepicker-content")

0 commit comments

Comments
 (0)