Skip to content

Commit 674624b

Browse files
authored
Merge pull request #3571 from plotly/bugfix/rc6-feedback
DCC Redesign: rc6 feedback
2 parents df7fb1b + 5b9beeb commit 674624b

File tree

12 files changed

+190
-15
lines changed

12 files changed

+190
-15
lines changed

CHANGELOG.md

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,12 @@
22
All notable changes to `dash` will be documented in this file.
33
This project adheres to [Semantic Versioning](https://semver.org/).
44

5+
## UNRELEASED
6+
7+
## Added
8+
- Add a prop to sliders, `allow_direct_input`, that can be used to disable the inputs rendered with sliders.
9+
- Improve CSS styles in calendar when looking at selected dates outside the current calendar month (`show_outside_days=True`)
10+
511
## [4.0.0rc6] - 2026-01-07
612

713
## Added
@@ -13,7 +19,7 @@ This project adheres to [Semantic Versioning](https://semver.org/).
1319
## [4.0.0rc5] - 2025-12-16
1420

1521
## Added
16-
- New prop in `dcc.Upload` allows users to recursively upload entire folders at once
22+
- [#3464](https://github.com/plotly/dash/issues/3464) Add folder upload functionality to `dcc.Upload` component. When `multiple=True`, users can now select and upload entire folders in addition to individual files. The folder hierarchy is preserved in filenames (e.g., `folder/subfolder/file.txt`). Files within folders are filtered according to the `accept` prop. Folder support is available in Chrome, Edge, and Opera; other browsers gracefully fall back to file-only mode. The uploaded files use the same output API as multiple file uploads.
1723

1824
## Changed
1925
- Bugfixes for feedback received in `rc4`
@@ -84,7 +90,6 @@ This project adheres to [Semantic Versioning](https://semver.org/).
8490
## [3.3.0] - 2025-11-12
8591

8692
## Added
87-
- [#3464](https://github.com/plotly/dash/issues/3464) Add folder upload functionality to `dcc.Upload` component. When `multiple=True`, users can now select and upload entire folders in addition to individual files. The folder hierarchy is preserved in filenames (e.g., `folder/subfolder/file.txt`). Files within folders are filtered according to the `accept` prop. Folder support is available in Chrome, Edge, and Opera; other browsers gracefully fall back to file-only mode. The uploaded files use the same output API as multiple file uploads.
8893
- [#3395](https://github.com/plotly/dash/pull/3396) Add position argument to hooks.devtool
8994
- [#3403](https://github.com/plotly/dash/pull/3403) Add app_context to get_app, allowing to get the current app in routes.
9095
- [#3407](https://github.com/plotly/dash/pull/3407) Add `hidden` to callback arguments, hiding the callback from appearing in the devtool callback graph.

components/dash-core-components/src/components/RangeSlider.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ export default function RangeSlider({
1919
// eslint-disable-next-line no-magic-numbers
2020
verticalHeight = 400,
2121
step = undefined,
22+
allow_direct_input = true,
2223
...props
2324
}: RangeSliderProps) {
2425
// Some considerations for the default value of `step`:
@@ -38,6 +39,7 @@ export default function RangeSlider({
3839
updatemode={updatemode}
3940
verticalHeight={verticalHeight}
4041
step={step}
42+
allow_direct_input={allow_direct_input}
4143
{...props}
4244
/>
4345
</Suspense>

components/dash-core-components/src/components/Slider.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ export default function Slider({
2323
// eslint-disable-next-line no-magic-numbers
2424
verticalHeight = 400,
2525
step = undefined,
26+
allow_direct_input = true,
2627
setProps,
2728
value,
2829
drag_value,
@@ -77,6 +78,7 @@ export default function Slider({
7778
updatemode={updatemode}
7879
verticalHeight={verticalHeight}
7980
step={step}
81+
allow_direct_input={allow_direct_input}
8082
value={mappedValue}
8183
drag_value={mappedDragValue}
8284
setProps={mappedSetProps}

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

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,17 +41,34 @@
4141
position: relative;
4242
}
4343

44-
.dash-datepicker-calendar td.dash-datepicker-calendar-date-highlighted {
44+
/* Highlighted dates (i.e. dates within a selected range) get highlight colours */
45+
.dash-datepicker-calendar
46+
td.dash-datepicker-calendar-date-highlighted:not(
47+
.dash-datepicker-calendar-date-outside
48+
) {
4549
background-color: var(--Dash-Fill-Interactive-Weak);
4650
color: var(--Dash-Fill-Interactive-Strong);
4751
}
4852

49-
.dash-datepicker-calendar td.dash-datepicker-calendar-date-selected {
53+
/* Outside days get highlighted colours only on hover */
54+
.dash-datepicker-calendar
55+
td.dash-datepicker-calendar-date-highlighted.dash-datepicker-calendar-date-outside:hover {
56+
background-color: var(--Dash-Fill-Interactive-Weak);
57+
color: var(--Dash-Fill-Interactive-Strong);
58+
}
59+
60+
/* Selected dates (start & end) get accented colours */
61+
.dash-datepicker-calendar
62+
td.dash-datepicker-calendar-date-selected:not(
63+
.dash-datepicker-calendar-date-outside
64+
) {
5065
background-color: var(--Dash-Fill-Interactive-Strong);
5166
color: var(--Dash-Fill-Inverse-Strong);
5267
}
5368

54-
.dash-datepicker-calendar td.dash-datepicker-calendar-date-selected {
69+
/* Outside days, when selected, get accented colours only when active (being clicked) */
70+
.dash-datepicker-calendar
71+
td.dash-datepicker-calendar-date-outside.dash-datepicker-calendar-date-selected:active {
5572
background-color: var(--Dash-Fill-Interactive-Strong);
5673
color: var(--Dash-Fill-Inverse-Strong);
5774
}

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

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@
1111
position: relative;
1212
accent-color: var(--Dash-Fill-Interactive-Strong);
1313
outline-color: var(--Dash-Fill-Interactive-Strong);
14+
font-family: inherit;
15+
font-size: inherit;
16+
color: inherit;
1417
}
1518

1619
.dash-datepicker-input-wrapper {
@@ -146,8 +149,7 @@
146149
overscroll-behavior: contain;
147150
}
148151

149-
.dash-datepicker
150-
[data-radix-popper-content-wrapper]:has(.dash-datepicker-portal) {
152+
[data-radix-popper-content-wrapper]:has(.dash-datepicker-portal) {
151153
transform: none !important;
152154
}
153155

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

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import {
1919
isSameDay,
2020
strAsDate,
2121
} from '../utils/calendar/helpers';
22+
import {captureCSSForPortal} from '../utils/calendar/cssVariables';
2223
import '../components/css/datepickers.css';
2324

2425
const DatePickerRange = ({
@@ -106,6 +107,11 @@ const DatePickerRange = ({
106107
const calendarRef = useRef<CalendarHandle>(null);
107108
const hasPortal = with_portal || with_full_screen_portal;
108109

110+
// Capture CSS variables for portal mode
111+
const portalStyle = useMemo(() => {
112+
return hasPortal ? captureCSSForPortal(containerRef) : undefined;
113+
}, [hasPortal, isCalendarOpen]);
114+
109115
useEffect(() => {
110116
setInternalStartDate(strAsDate(start_date));
111117
}, [start_date]);
@@ -382,7 +388,9 @@ const DatePickerRange = ({
382388
</div>
383389
</Popover.Trigger>
384390

385-
<Popover.Portal container={containerRef.current}>
391+
<Popover.Portal
392+
container={hasPortal ? undefined : containerRef.current}
393+
>
386394
<Popover.Content
387395
className={`dash-datepicker-content${
388396
hasPortal ? ' dash-datepicker-portal' : ''
@@ -391,6 +399,7 @@ const DatePickerRange = ({
391399
? ' dash-datepicker-fullscreen'
392400
: ''
393401
}`}
402+
style={portalStyle}
394403
align={hasPortal ? 'center' : 'start'}
395404
sideOffset={hasPortal ? 0 : 5}
396405
avoidCollisions={!hasPortal}

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

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {
1313
isSameDay,
1414
strAsDate,
1515
} from '../utils/calendar/helpers';
16+
import {captureCSSForPortal} from '../utils/calendar/cssVariables';
1617
import '../components/css/datepickers.css';
1718

1819
const DatePickerSingle = ({
@@ -65,6 +66,11 @@ const DatePickerSingle = ({
6566
const calendarRef = useRef<CalendarHandle>(null);
6667
const hasPortal = with_portal || with_full_screen_portal;
6768

69+
// Capture CSS variables for portal mode
70+
const portalStyle = useMemo(() => {
71+
return hasPortal ? captureCSSForPortal(containerRef) : undefined;
72+
}, [hasPortal, isCalendarOpen]);
73+
6874
useEffect(() => {
6975
setInternalDate(strAsDate(date));
7076
}, [date]);
@@ -201,7 +207,9 @@ const DatePickerSingle = ({
201207
</div>
202208
</Popover.Trigger>
203209

204-
<Popover.Portal container={containerRef.current}>
210+
<Popover.Portal
211+
container={hasPortal ? undefined : containerRef.current}
212+
>
205213
<Popover.Content
206214
className={`dash-datepicker-content${
207215
hasPortal ? ' dash-datepicker-portal' : ''
@@ -210,6 +218,7 @@ const DatePickerSingle = ({
210218
? ' dash-datepicker-fullscreen'
211219
: ''
212220
}`}
221+
style={portalStyle}
213222
align={hasPortal ? 'center' : 'start'}
214223
sideOffset={hasPortal ? 0 : 5}
215224
avoidCollisions={!hasPortal}

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ export default function RangeSlider(props: RangeSliderProps) {
4040
pushable,
4141
count,
4242
reverse,
43+
allow_direct_input = true,
4344
} = props;
4445

4546
// For range slider, we expect an array of values
@@ -263,6 +264,7 @@ export default function RangeSlider(props: RangeSliderProps) {
263264

264265
// Determine if inputs should be rendered at all (CSS will handle responsive visibility)
265266
const shouldShowInputs =
267+
allow_direct_input !== false && // Not disabled by allow_direct_input
266268
step !== null && // Not disabled by step=None
267269
value.length <= 2 && // Only for single or range sliders
268270
!vertical; // Only for horizontal sliders

components/dash-core-components/src/types.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -499,6 +499,12 @@ export interface SliderProps extends BaseDccProps<SliderProps> {
499499
* The height, in px, of the slider if it is vertical.
500500
*/
501501
verticalHeight?: number;
502+
503+
/**
504+
* If false, the input elements for directly entering values will be hidden.
505+
* Only the slider will be visible and it will occupy 100% width of the container.
506+
*/
507+
allow_direct_input?: boolean;
502508
}
503509

504510
export interface RangeSliderProps extends BaseDccProps<RangeSliderProps> {
@@ -604,6 +610,12 @@ export interface RangeSliderProps extends BaseDccProps<RangeSliderProps> {
604610
* The height, in px, of the slider if it is vertical.
605611
*/
606612
verticalHeight?: number;
613+
614+
/**
615+
* If false, the input elements for directly entering values will be hidden.
616+
* Only the slider will be visible and it will occupy 100% width of the container.
617+
*/
618+
allow_direct_input?: boolean;
607619
}
608620

609621
export type OptionValue = string | number | boolean;
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
/**
2+
* Captures CSS variables and key inherited properties from a container element for use in portaled content.
3+
* When content is portaled outside its normal DOM hierarchy (e.g., to document.body),
4+
* it loses access to CSS variables defined on parent elements and inherited properties.
5+
* This function extracts those so they can be applied as inline styles.
6+
*/
7+
export function captureCSSForPortal(
8+
containerRef: React.RefObject<HTMLElement>,
9+
prefix = '--Dash-'
10+
): Record<string, string> {
11+
if (typeof window === 'undefined') {
12+
return {};
13+
}
14+
15+
const element = containerRef.current || document.documentElement;
16+
const computedStyle = window.getComputedStyle(element);
17+
const styles: Record<string, string> = {};
18+
19+
// Capture CSS variables (custom properties starting with prefix)
20+
for (let i = 0; i < computedStyle.length; i++) {
21+
const prop = computedStyle[i];
22+
if (prop.startsWith(prefix)) {
23+
styles[prop] = computedStyle.getPropertyValue(prop);
24+
}
25+
}
26+
27+
// Capture key inherited properties
28+
const inheritedProps = ['font-family', 'font-size', 'color'];
29+
inheritedProps.forEach(prop => {
30+
const value = computedStyle.getPropertyValue(prop);
31+
if (value) {
32+
// Convert hyphenated CSS property names to camelCase for React
33+
const camelCaseProp = prop.replace(/-([a-z])/g, (_, letter) =>
34+
letter.toUpperCase()
35+
);
36+
styles[camelCaseProp] = value;
37+
}
38+
});
39+
40+
return styles;
41+
}

0 commit comments

Comments
 (0)