Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
67 commits
Select commit Hold shift + click to select a range
8919edf
is a Valid Prop
2Ryan09 Jan 21, 2025
b99ff5f
remove prints
2Ryan09 Jan 21, 2025
8ec605c
remove test.py
2Ryan09 Jan 21, 2025
3a229eb
Allow dict id in target_components
AnnMarieW Jun 27, 2025
fc4f620
Merge branch 'dev' into fix-dcc-loading
AnnMarieW Jun 30, 2025
a7866b7
changed where id is stringified
AnnMarieW Jun 30, 2025
b696ab9
added stringify_id to init
AnnMarieW Jun 30, 2025
b6b2770
add changelog
AnnMarieW Jun 30, 2025
91626f1
fixed stringifiyId
AnnMarieW Jun 30, 2025
ade9bc8
Make #3351 acceptable only for sync version (async version has self-o…
Erraen Jul 1, 2025
5ec2100
Fix _ID_CONTENT check for async version
Erraen Jul 1, 2025
a14b0c1
Merge pull request #3353 from AnnMarieW/fix-dcc-loading
T4rk1n Jul 2, 2025
ab31c2d
Merge branch 'dev' into pr3
T4rk1n Jul 4, 2025
d9475e9
Merge pull request #3361 from Erraen/pr3
T4rk1n Jul 4, 2025
c14256e
Merge branch 'dev' into pr1
T4rk1n Jul 4, 2025
e67a1b7
Warn if slider has more than 500 marks and use default instead
T4rk1n Jul 4, 2025
c2efe9b
Merge pull request #3359 from Erraen/pr1
T4rk1n Jul 7, 2025
877ee41
Merge branch 'dev' into slider-500
T4rk1n Jul 7, 2025
35d817b
Merge pull request #3365 from plotly/slider-500
T4rk1n Jul 7, 2025
0a34a9a
Improved dash-table build time
T4rk1n Jul 7, 2025
e6a5380
Remove storybook from dash table main install
T4rk1n Jul 7, 2025
5f12f0b
Merge branch 'dev' into no-update-is-valid-prop
T4rk1n Jul 7, 2025
62db98a
Merge pull request #3127 from 2Ryan09/no-update-is-valid-prop
T4rk1n Jul 7, 2025
1215e18
Merge branch 'dev' into buildtimes
T4rk1n Jul 7, 2025
1f4337a
Merge pull request #3367 from plotly/buildtimes
T4rk1n Jul 8, 2025
4c9ac7f
feat: Expose NoUpdate type
ytausch Jul 15, 2025
bd086dc
add to changelog
ytausch Jul 15, 2025
d07e522
fix import NoUpdate
ytausch Jul 16, 2025
9827157
add obj is NoUpdate
ytausch Jul 16, 2025
00eb045
Add devtool hook
T4rk1n Jul 16, 2025
533af1f
fix layout callbacks
T4rk1n Jul 16, 2025
3b3324c
chore: Format
ytausch Jul 17, 2025
92040d7
fix tests with paths
T4rk1n Jul 17, 2025
bfca375
Merge pull request #3369 from ytausch/expose-noupdate
T4rk1n Jul 17, 2025
1042956
fix cbmo004
T4rk1n Jul 17, 2025
5f435d4
Merge branch 'dev' into devtool-hook
T4rk1n Jul 17, 2025
a8f5d8a
Update changelog
T4rk1n Jul 18, 2025
ab73036
Merge pull request #3371 from plotly/devtool-hook
T4rk1n Jul 18, 2025
3f72479
Fix layout as list and persistence
T4rk1n Jul 18, 2025
36be8a5
Fix layout as list primitives
T4rk1n Jul 21, 2025
74f482c
Fix dcc.Graph backward compatibility with dash 2.0
T4rk1n Jul 29, 2025
a018fc9
fix graph
T4rk1n Jul 29, 2025
953fc54
update changelog
T4rk1n Jul 30, 2025
0d4913d
Merge pull request #3379 from plotly/fix/backward-dcc-graph
T4rk1n Jul 30, 2025
5ea2ed2
Merge branch 'dev' into fix/layout-list-persistence
T4rk1n Jul 30, 2025
f08ced6
Update cookie to 1.0.2
T4rk1n Jul 30, 2025
30103ea
update renderer build deps
T4rk1n Jul 30, 2025
b8ae8da
Replace vulnerable request with native fetch for html scripts
T4rk1n Jul 30, 2025
cd3e2a4
update html build deps
T4rk1n Jul 30, 2025
62ccfd8
update lerna
T4rk1n Jul 30, 2025
def70be
update core deps
T4rk1n Jul 30, 2025
8117334
update styled-jsx
T4rk1n Jul 30, 2025
e9dddc1
update table deps
T4rk1n Jul 30, 2025
6deb92e
Fix no ouptut on error & use lts/iron on build windows.
T4rk1n Jul 30, 2025
8f50759
Revert "Fix no ouptut on error & use lts/iron on build windows."
T4rk1n Jul 30, 2025
a66a076
nvm use before commands
T4rk1n Jul 30, 2025
dec82f8
run windows html build separate
T4rk1n Jul 30, 2025
2b47849
Merge pull request #3380 from plotly/dep-up-32
T4rk1n Jul 30, 2025
a53fbbb
Merge branch 'dev' into fix/layout-list-persistence
T4rk1n Jul 31, 2025
05da2c5
Update changelog
T4rk1n Jul 31, 2025
e37c9d1
Merge pull request #3373 from plotly/fix/layout-list-persistence
T4rk1n Jul 31, 2025
a512b0b
fix cookie transpiling
T4rk1n Jul 31, 2025
b7fe93b
Merge pull request #3382 from plotly/fix-cookie-transpile
T4rk1n Jul 31, 2025
9ffb8b0
version 3.2.0
T4rk1n Jul 31, 2025
16243ec
Merge pull request #3383 from plotly/version-3.2.0
T4rk1n Jul 31, 2025
717f61c
Merge branch 'dev' into master-3.2.0
T4rk1n Jul 31, 2025
6cfac83
Master build 3.2.0
T4rk1n Jul 31, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -163,14 +163,17 @@ jobs:
- run:
name: npm prereqs
command: |
nvm use 18
npm ci
cd dash/dash-renderer && npm i && cd ../../
cd components/dash-html-components && npm i && npm run extract && cd ../../
- run:
name: ️️🏗️ build dash
command: |
nvm use 18
. venv/Scripts/activate
npm run private::build.jupyterlab && npm run private::build.renderer && python dash/development/update_components.py 'dash-html-components'
npm run private::build.jupyterlab && npm run private::build.renderer
cd components/dash-html-components && npm run build
no_output_timeout: 30m

test-312: &test
Expand Down
15 changes: 15 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,21 @@
All notable changes to `dash` will be documented in this file.
This project adheres to [Semantic Versioning](https://semver.org/).

## [3.2.0] - 2025-07-31

## Added
- [#3369](https://github.com/plotly/dash/pull/3369) Expose `dash.NoUpdate` type
- [#3371](https://github.com/plotly/dash/pull/3371) Add devtool hook to add components to the devtool bar ui.

## Fixed
- [#3353](https://github.com/plotly/dash/pull/3353) Support pattern-matching/dict ids in `dcc.Loading` `target_components`
- [#3371](https://github.com/plotly/dash/pull/3371) Fix allow_optional triggering a warning for not found input.
- [#3379](https://github.com/plotly/dash/pull/3379) Fix dcc.Graph backward compatibility with dash 2.0 for ddk.Graph
- [#3373](https://github.com/plotly/dash/pull/3373) Fix layout as list and persistence.

# Changed
- [#3365](https://github.com/plotly/dash/pull/3365) Warn if dcc.Slider has more than 500 marks and use default instead.

# [3.1.1] - 2025-06-29

## Fixed
Expand Down
1,733 changes: 951 additions & 782 deletions components/dash-core-components/package-lock.json

Large diffs are not rendered by default.

14 changes: 7 additions & 7 deletions components/dash-core-components/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "dash-core-components",
"version": "3.1.0",
"version": "3.2.0",
"description": "Core component suite for Dash",
"repository": {
"type": "git",
Expand Down Expand Up @@ -63,11 +63,11 @@
"uniqid": "^5.4.0"
},
"devDependencies": {
"@babel/cli": "^7.27.2",
"@babel/core": "^7.27.4",
"@babel/eslint-parser": "^7.27.5",
"@babel/cli": "^7.28.0",
"@babel/core": "^7.28.0",
"@babel/eslint-parser": "^7.28.0",
"@babel/plugin-syntax-dynamic-import": "^7.8.3",
"@babel/preset-env": "^7.27.2",
"@babel/preset-env": "^7.28.0",
"@babel/preset-react": "^7.27.1",
"@plotly/dash-component-plugins": "^1.2.3",
"@plotly/webpack-dash-dynamic-import": "^1.3.0",
Expand All @@ -85,8 +85,8 @@
"react-jsx-parser": "1.21.0",
"rimraf": "^5.0.5",
"style-loader": "^3.3.3",
"styled-jsx": "^3.4.4",
"webpack": "^5.99.9",
"styled-jsx": "^5.1.7",
"webpack": "^5.101.0",
"webpack-cli": "^5.1.4"
},
"optionalDependencies": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ function Loading({
custom_spinner,
}) {
const ctx = window.dash_component_api.useDashContext();

const loading = ctx.useSelector(
loadingSelector(ctx.componentPath, target_components),
equals
Expand Down
32 changes: 27 additions & 5 deletions components/dash-core-components/src/fragments/Graph.react.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,10 @@ import {
import PropTypes from 'prop-types';
import {graphPropTypes, graphDefaultProps} from '../components/Graph.react';

import LoadingElement from '../utils/LoadingElement';

/* global Plotly:true */

import ResizeDetector from '../utils/ResizeDetector';
import LoadingElement from '../utils/LoadingElement';

/**
* `autosize: true` causes Plotly.js to conform to the parent element size.
Expand Down Expand Up @@ -537,23 +536,46 @@ class PlotlyGraph extends Component {
}

render() {
const {className, id} = this.props;
const {className, id, loading_state} = this.props;
const style = this.getStyle();

if (window.dash_component_api) {
return (
<LoadingElement
id={id}
key={id}
className={className}
style={style}
ref={this.parentElement}
>
<ResizeDetector
onResize={this.graphResize}
targets={[this.parentElement, this.gd]}
/>
<div
ref={this.gd}
style={{height: '100%', width: '100%'}}
/>
</LoadingElement>
);
}
return (
<LoadingElement
<div
id={id}
key={id}
className={className}
style={style}
ref={this.parentElement}
data-dash-is-loading={
(loading_state && loading_state.is_loading) || undefined
}
>
<ResizeDetector
onResize={this.graphResize}
targets={[this.parentElement, this.gd]}
/>
<div ref={this.gd} style={{height: '100%', width: '100%'}} />
</LoadingElement>
</div>
);
}
}
Expand Down
30 changes: 26 additions & 4 deletions components/dash-core-components/src/fragments/Slider.react.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ import {
} from '../utils/formatSliderTooltip';
import LoadingElement from '../utils/LoadingElement';

const MAX_MARKS = 500;

const sliderProps = [
'min',
'max',
Expand Down Expand Up @@ -76,6 +78,21 @@ export default class Slider extends Component {
} = this.props;
const value = this.state.value;

// Check if marks exceed 500 limit for performance
let processedMarks = marks;
if (marks && typeof marks === 'object' && marks !== null) {
const marksCount = Object.keys(marks).length;
if (marksCount > MAX_MARKS) {
/* eslint-disable no-console */
console.error(
`dcc.Slider: Too many marks (${marksCount}) provided. ` +
`For performance reasons, marks are limited to 500. ` +
`Using auto-generated marks instead.`
);
processedMarks = undefined;
}
}

let tipProps, tipFormatter;
if (tooltip) {
/**
Expand Down Expand Up @@ -136,11 +153,16 @@ export default class Slider extends Component {
tipFormatter={tipFormatter}
style={{position: 'relative'}}
value={value}
marks={sanitizeMarks({min, max, marks, step})}
max={setUndefined(min, max, marks).max_mark}
min={setUndefined(min, max, marks).min_mark}
marks={sanitizeMarks({
min,
max,
marks: processedMarks,
step,
})}
max={setUndefined(min, max, processedMarks).max_mark}
min={setUndefined(min, max, processedMarks).min_mark}
step={
step === null && !isNil(marks)
step === null && !isNil(processedMarks)
? null
: calcStep(min, max, step)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from multiprocessing import Lock
from dash import Dash, Input, Output, dcc, html
from dash.dependencies import stringify_id
from dash.testing import wait
import time

Expand Down Expand Up @@ -414,9 +415,9 @@ def updateDiv(n_clicks):
assert dash_dcc.get_logs() == []


# multiple components, only one triggers the spinner
def test_ldcp010_loading_component_target_components(dash_dcc):

# update multiple props of same component, only targeted id/prop triggers spinner
# test that target_components id can be a dict id
def test_ldcp011_loading_component_target_components(dash_dcc):
lock = Lock()

app = Dash(__name__)
Expand All @@ -425,53 +426,61 @@ def test_ldcp010_loading_component_target_components(dash_dcc):
[
dcc.Loading(
[
html.Button(id="btn-1"),
html.Button(id={"type": "button", "index": "one"}),
html.Button(id="btn-2"),
html.Button(id="btn-3"),
],
className="loading-1",
target_components={"btn-2": "children"},
target_components={
stringify_id({"type": "button", "index": "one"}): "className"
},
)
],
id="root",
)

@app.callback(Output("btn-1", "children"), [Input("btn-2", "n_clicks")])
@app.callback(
Output({"type": "button", "index": "one"}, "children"),
[Input("btn-2", "n_clicks")],
)
def updateDiv1(n_clicks):
if n_clicks:
with lock:
return "changed 1"

return "content 1"

@app.callback(Output("btn-2", "children"), [Input("btn-1", "n_clicks")])
@app.callback(
Output({"type": "button", "index": "one"}, "className"),
[Input("btn-3", "n_clicks")],
)
def updateDiv2(n_clicks):
if n_clicks:
with lock:
return "changed 2"

return "content 2"
return "new-class"
return ""

dash_dcc.start_server(app)

dash_dcc.wait_for_text_to_equal("#btn-1", "content 1")
dash_dcc.wait_for_text_to_equal("#btn-2", "content 2")

with lock:
dash_dcc.find_element("#btn-1").click()
btn1id = "#" + stringify_id({"type": "button", "index": "one"})

dash_dcc.find_element(".loading-1 .dash-spinner")
dash_dcc.wait_for_text_to_equal("#btn-2", "")

dash_dcc.wait_for_text_to_equal("#btn-2", "changed 2")
dash_dcc.wait_for_text_to_equal(btn1id, "content 1")

with lock:
dash_dcc.find_element("#btn-2").click()
spinners = dash_dcc.find_elements(".loading-1 .dash-spinner")
dash_dcc.wait_for_text_to_equal("#btn-1", "")

dash_dcc.wait_for_text_to_equal("#btn-1", "changed 1")
spinners = dash_dcc.find_elements(".loading-1 .dash-spinner")
dash_dcc.wait_for_text_to_equal(btn1id, "")
dash_dcc.wait_for_text_to_equal(btn1id, "changed 1")
assert spinners == []

with lock:
dash_dcc.find_element("#btn-3").click()

dash_dcc.find_element(".loading-1 .dash-spinner")
dash_dcc.wait_for_text_to_equal(btn1id, "")

dash_dcc.wait_for_class_to_equal(btn1id, "new-class")

assert dash_dcc.get_logs() == []


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -616,3 +616,50 @@ def test_sls016_sliders_format_tooltips(dash_dcc):
dash_dcc.percy_snapshot("sliders-format-tooltips")

assert dash_dcc.get_logs() == []


def test_slsl017_marks_limit_500(dash_dcc):
"""Test that slider works with exactly 500 marks"""
app = Dash(__name__)
marks_500 = {str(i): f"Mark {i}" for i in range(500)}
app.layout = html.Div(
[
dcc.Slider(id="slider", min=0, max=499, marks=marks_500, value=250),
html.Div(id="output"),
]
)

@app.callback(Output("output", "children"), [Input("slider", "value")])
def update_output(value):
return f"Selected: {value}"

dash_dcc.start_server(app)
dash_dcc.wait_for_text_to_equal("#output", "Selected: 250")

# No warnings should be logged for 500 marks
assert dash_dcc.get_logs() == []


def test_slsl018_marks_limit_exceeded(dash_dcc):
"""Test behavior when marks exceed 500 limit"""
app = Dash(__name__)
marks_501 = {str(i): f"Mark {i}" for i in range(501)}
app.layout = html.Div(
[
dcc.Slider(id="slider", min=0, max=500, marks=marks_501, value=250),
html.Div(id="output"),
]
)

@app.callback(Output("output", "children"), [Input("slider", "value")])
def update_output(value):
return f"Selected: {value}"

dash_dcc.start_server(app)
dash_dcc.wait_for_text_to_equal("#output", "Selected: 250")

# Check that warning is logged
logs = dash_dcc.get_logs()
assert len(logs) > 0
warning_found = any("Too many marks" in log["message"] for log in logs)
assert warning_found, "Expected warning about too many marks not found in logs"
Loading
Loading