dash-startup-loading-plugin 0.2.0__tar.gz → 0.3.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {dash_startup_loading_plugin-0.2.0/src/dash_startup_loading_plugin.egg-info → dash_startup_loading_plugin-0.3.0}/PKG-INFO +124 -136
- {dash_startup_loading_plugin-0.2.0 → dash_startup_loading_plugin-0.3.0}/README.md +119 -133
- {dash_startup_loading_plugin-0.2.0 → dash_startup_loading_plugin-0.3.0}/pyproject.toml +6 -3
- {dash_startup_loading_plugin-0.2.0 → dash_startup_loading_plugin-0.3.0}/src/dash_startup_loading_plugin/__init__.py +6 -2
- {dash_startup_loading_plugin-0.2.0 → dash_startup_loading_plugin-0.3.0}/src/dash_startup_loading_plugin/plugin.py +56 -14
- {dash_startup_loading_plugin-0.2.0 → dash_startup_loading_plugin-0.3.0/src/dash_startup_loading_plugin.egg-info}/PKG-INFO +124 -136
- dash_startup_loading_plugin-0.3.0/src/dash_startup_loading_plugin.egg-info/requires.txt +7 -0
- {dash_startup_loading_plugin-0.2.0 → dash_startup_loading_plugin-0.3.0}/tests/test_plugin.py +44 -1
- dash_startup_loading_plugin-0.2.0/src/dash_startup_loading_plugin.egg-info/requires.txt +0 -4
- {dash_startup_loading_plugin-0.2.0 → dash_startup_loading_plugin-0.3.0}/LICENSE +0 -0
- {dash_startup_loading_plugin-0.2.0 → dash_startup_loading_plugin-0.3.0}/setup.cfg +0 -0
- {dash_startup_loading_plugin-0.2.0 → dash_startup_loading_plugin-0.3.0}/src/dash_startup_loading_plugin/resources/startup-loading.css +0 -0
- {dash_startup_loading_plugin-0.2.0 → dash_startup_loading_plugin-0.3.0}/src/dash_startup_loading_plugin/resources/startup-loading.js +0 -0
- {dash_startup_loading_plugin-0.2.0 → dash_startup_loading_plugin-0.3.0}/src/dash_startup_loading_plugin/resources/startup-theme.js +0 -0
- {dash_startup_loading_plugin-0.2.0 → dash_startup_loading_plugin-0.3.0}/src/dash_startup_loading_plugin.egg-info/SOURCES.txt +0 -0
- {dash_startup_loading_plugin-0.2.0 → dash_startup_loading_plugin-0.3.0}/src/dash_startup_loading_plugin.egg-info/dependency_links.txt +0 -0
- {dash_startup_loading_plugin-0.2.0 → dash_startup_loading_plugin-0.3.0}/src/dash_startup_loading_plugin.egg-info/entry_points.txt +0 -0
- {dash_startup_loading_plugin-0.2.0 → dash_startup_loading_plugin-0.3.0}/src/dash_startup_loading_plugin.egg-info/top_level.txt +0 -0
|
@@ -1,17 +1,19 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: dash-startup-loading-plugin
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: A configurable full-screen startup loading overlay for Dash apps, packaged as a Dash Hooks plugin.
|
|
5
5
|
Author-email: Ethan Zhang <ethan.zhang2016@gmail.com>
|
|
6
6
|
License-Expression: MIT
|
|
7
|
-
Keywords: dash,plotly,loading,plugin
|
|
7
|
+
Keywords: dash,plotly,loading,plugin
|
|
8
8
|
Classifier: Framework :: Dash
|
|
9
9
|
Requires-Python: >=3.9
|
|
10
10
|
Description-Content-Type: text/markdown
|
|
11
11
|
License-File: LICENSE
|
|
12
|
-
Requires-Dist: dash
|
|
12
|
+
Requires-Dist: dash==4.4.1
|
|
13
13
|
Provides-Extra: test
|
|
14
14
|
Requires-Dist: pytest>=8; extra == "test"
|
|
15
|
+
Provides-Extra: mantine
|
|
16
|
+
Requires-Dist: dash-mantine-components>=2.6.0; extra == "mantine"
|
|
15
17
|
Dynamic: license-file
|
|
16
18
|
|
|
17
19
|
# dash-startup-loading-plugin
|
|
@@ -51,7 +53,7 @@ discovery, resource hooks, and the index hook described in the official
|
|
|
51
53
|
## Installation
|
|
52
54
|
|
|
53
55
|
```bash
|
|
54
|
-
pip install "dash-startup-loading-plugin>=0.
|
|
56
|
+
pip install "dash-startup-loading-plugin>=0.3.0"
|
|
55
57
|
```
|
|
56
58
|
|
|
57
59
|
The package declares the following entry point:
|
|
@@ -66,6 +68,16 @@ package therefore enables the default startup overlay for Dash applications in
|
|
|
66
68
|
that Python environment; an explicit import is only needed when changing its
|
|
67
69
|
configuration or using its Python API.
|
|
68
70
|
|
|
71
|
+
## What's new in 0.3.0
|
|
72
|
+
|
|
73
|
+
- Adds `configure_antd()` with matching `#ffffff` and `#121212` loading
|
|
74
|
+
backgrounds plus explicit light/dark overrides.
|
|
75
|
+
- Adds `configure_mantine()` with theme defaults sourced from Dash Mantine
|
|
76
|
+
Components and its pre-render color-scheme hook.
|
|
77
|
+
- Updates the Dash Ant Design example for the structured
|
|
78
|
+
`ConfigProvider.theme.algorithm` API.
|
|
79
|
+
- Adds a Dash Mantine Components example with persisted theme switching.
|
|
80
|
+
|
|
69
81
|
## What's new in 0.2.0
|
|
70
82
|
|
|
71
83
|
- Resolves the loading background before first paint from a persisted
|
|
@@ -104,25 +116,33 @@ from dash import Dash, html
|
|
|
104
116
|
from dash_startup_loading_plugin import configure
|
|
105
117
|
|
|
106
118
|
configure(
|
|
107
|
-
required_selectors=["#
|
|
119
|
+
required_selectors=["#usage-header", "#usage-sidebar-menu"],
|
|
108
120
|
pending_selector="[data-dac-async-placeholder]",
|
|
109
121
|
timeout_ms=6000,
|
|
110
|
-
minimum_display_ms=200,
|
|
111
122
|
fade_duration_ms=160,
|
|
112
|
-
color="#1677ff",
|
|
113
|
-
dark_color="#4096ff",
|
|
114
123
|
)
|
|
115
124
|
|
|
116
125
|
app = Dash(__name__)
|
|
117
126
|
app.layout = html.Div(
|
|
118
127
|
[
|
|
119
|
-
html.Header("Dashboard", id="
|
|
120
|
-
html.Nav("Navigation", id="sidebar-menu"),
|
|
128
|
+
html.Header("Dashboard", id="usage-header"),
|
|
129
|
+
html.Nav("Navigation", id="usage-sidebar-menu"),
|
|
121
130
|
]
|
|
122
131
|
)
|
|
123
132
|
```
|
|
124
133
|
|
|
125
|
-
|
|
134
|
+
Runnable examples:
|
|
135
|
+
|
|
136
|
+
- [`examples/basic.py`](examples/basic.py): Dash Ant Design theme persistence.
|
|
137
|
+
- [`examples/mantine_theme.py`](examples/mantine_theme.py):
|
|
138
|
+
Dash Mantine Components with a persisted light/dark theme switch.
|
|
139
|
+
|
|
140
|
+
Run the Mantine example with:
|
|
141
|
+
|
|
142
|
+
```bash
|
|
143
|
+
pip install -e ".[mantine]"
|
|
144
|
+
python examples/mantine_theme.py
|
|
145
|
+
```
|
|
126
146
|
|
|
127
147
|
## Readiness behavior
|
|
128
148
|
|
|
@@ -192,29 +212,105 @@ parsed. This isolates the overlay styling without changing classes or data
|
|
|
192
212
|
attributes owned by Tailwind, Dash Mantine Components, or the application.
|
|
193
213
|
The stylesheet also adjusts its animation for `prefers-reduced-motion`.
|
|
194
214
|
|
|
215
|
+
### Dash Mantine Components
|
|
216
|
+
|
|
217
|
+
Dash Mantine Components 2.6 and later includes `ColorSchemeToggle`, which
|
|
218
|
+
switches the active color scheme without a callback and persists it in
|
|
219
|
+
`mantine-color-scheme-value`. The startup plugin reads that value before Dash
|
|
220
|
+
and React mount. To make the loading transition seamless, use Mantine's current
|
|
221
|
+
default dark body color for the overlay:
|
|
222
|
+
|
|
223
|
+
```python
|
|
224
|
+
import dash_mantine_components as dmc
|
|
225
|
+
from dash_startup_loading_plugin import configure_mantine
|
|
226
|
+
|
|
227
|
+
configure_mantine()
|
|
228
|
+
|
|
229
|
+
app.layout = dmc.MantineProvider(
|
|
230
|
+
[
|
|
231
|
+
dmc.ColorSchemeToggle(
|
|
232
|
+
lightIcon=dmc.Text("☀"),
|
|
233
|
+
darkIcon=dmc.Text("☾"),
|
|
234
|
+
),
|
|
235
|
+
dmc.Text("Application content", id="app-ready"),
|
|
236
|
+
],
|
|
237
|
+
defaultColorScheme="auto",
|
|
238
|
+
)
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
See
|
|
242
|
+
[`examples/mantine_theme.py`](examples/mantine_theme.py)
|
|
243
|
+
for the complete runnable example.
|
|
244
|
+
|
|
245
|
+
`configure_mantine()` keeps DMC optional. When used, it registers DMC's
|
|
246
|
+
pre-render color-scheme hook, disables the Dash Ant Design placeholder check,
|
|
247
|
+
and derives the light and dark loading backgrounds from DMC's active default
|
|
248
|
+
theme. Any explicit configuration still takes precedence:
|
|
249
|
+
|
|
250
|
+
```python
|
|
251
|
+
configure_mantine(dark_background="#202020")
|
|
252
|
+
```
|
|
253
|
+
|
|
195
254
|
### Dash Ant Design persistence
|
|
196
255
|
|
|
197
|
-
`dash_antd_components.ConfigProvider`
|
|
198
|
-
|
|
256
|
+
Current `dash_antd_components.ConfigProvider.theme` accepts a structured theme
|
|
257
|
+
configuration whose `algorithm` is `"default"`, `"dark"`, `"compact"`, or an
|
|
258
|
+
array of those values. Persist a user-facing system/light/dark selector, resolve
|
|
259
|
+
the system preference in a clientside callback, and store the semantic choice
|
|
260
|
+
in the small conventional `localStorage["theme"]` value that the startup plugin
|
|
261
|
+
reads before Dash mounts:
|
|
199
262
|
|
|
200
263
|
```python
|
|
201
264
|
import dash_antd_components as dac
|
|
202
|
-
from
|
|
265
|
+
from dash import Input, Output, clientside_callback
|
|
266
|
+
from dash_startup_loading_plugin import configure_antd
|
|
203
267
|
|
|
204
|
-
|
|
268
|
+
configure_antd()
|
|
205
269
|
|
|
206
270
|
provider = dac.ConfigProvider(
|
|
207
|
-
|
|
271
|
+
[
|
|
272
|
+
app_content,
|
|
273
|
+
dac.Segmented(
|
|
274
|
+
id="theme-mode",
|
|
275
|
+
options=["system", "light", "dark"],
|
|
276
|
+
value="system",
|
|
277
|
+
persistence=True,
|
|
278
|
+
persisted_props=["value"],
|
|
279
|
+
persistence_type="local",
|
|
280
|
+
),
|
|
281
|
+
],
|
|
208
282
|
id="theme-provider",
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
283
|
+
theme={"algorithm": "default"},
|
|
284
|
+
)
|
|
285
|
+
|
|
286
|
+
clientside_callback(
|
|
287
|
+
"""function(mode) {
|
|
288
|
+
localStorage.setItem("theme", JSON.stringify(mode));
|
|
289
|
+
const dark = mode === "dark" || (
|
|
290
|
+
mode === "system"
|
|
291
|
+
&& window.matchMedia("(prefers-color-scheme: dark)").matches
|
|
292
|
+
);
|
|
293
|
+
return {algorithm: dark ? "dark" : "default"};
|
|
294
|
+
}""",
|
|
295
|
+
Output("theme-provider", "theme"),
|
|
296
|
+
Input("theme-mode", "value"),
|
|
297
|
+
)
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
The selector owns the semantic `"system"` state; `ConfigProvider` receives only
|
|
301
|
+
the algorithms allowed by its current API. `configure_antd()` uses `#ffffff`
|
|
302
|
+
and `#121212`, matching the component library's default light and dark
|
|
303
|
+
backgrounds. If the application customizes either background, pass the same
|
|
304
|
+
values explicitly:
|
|
305
|
+
|
|
306
|
+
```python
|
|
307
|
+
configure_antd(
|
|
308
|
+
background="#f5f5f5",
|
|
309
|
+
dark_background="#202020",
|
|
212
310
|
)
|
|
213
311
|
```
|
|
214
312
|
|
|
215
|
-
|
|
216
|
-
the UI edit, `ConfigProvider` restores it after mount, and the startup plugin
|
|
217
|
-
uses it for the loading background before mount. See
|
|
313
|
+
See
|
|
218
314
|
[`examples/basic.py`](examples/basic.py) for the complete switcher.
|
|
219
315
|
|
|
220
316
|
### Custom loader markup
|
|
@@ -242,12 +338,18 @@ configuration. Never populate it with user input.
|
|
|
242
338
|
from dash_startup_loading_plugin import (
|
|
243
339
|
StartupLoadingConfig,
|
|
244
340
|
configure,
|
|
341
|
+
configure_antd,
|
|
342
|
+
configure_mantine,
|
|
245
343
|
get_config,
|
|
246
344
|
reset_config,
|
|
247
345
|
)
|
|
248
346
|
```
|
|
249
347
|
|
|
250
348
|
- `configure(**changes)` validates and applies a partial configuration update.
|
|
349
|
+
- `configure_antd(**changes)` matches the loading backgrounds to Dash Ant
|
|
350
|
+
Design's light and dark themes and accepts explicit overrides.
|
|
351
|
+
- `configure_mantine(**changes)` applies DMC theme defaults and registers DMC's
|
|
352
|
+
pre-render color-scheme hook.
|
|
251
353
|
- `get_config()` returns the current immutable configuration.
|
|
252
354
|
- `reset_config()` restores all defaults. It is primarily useful in tests.
|
|
253
355
|
- `StartupLoadingConfig` is the frozen dataclass containing all options.
|
|
@@ -345,120 +447,6 @@ This is expected with automatic `dash_hooks` discovery. Use a dedicated virtual
|
|
|
345
447
|
environment, uninstall the package where it is not wanted, or call
|
|
346
448
|
`configure(enabled=False)` before constructing those Dash apps.
|
|
347
449
|
|
|
348
|
-
## Development
|
|
349
|
-
|
|
350
|
-
Clone the repository, then install the project and test dependencies:
|
|
351
|
-
|
|
352
|
-
```bash
|
|
353
|
-
uv sync --extra test
|
|
354
|
-
```
|
|
355
|
-
|
|
356
|
-
Run the tests and example:
|
|
357
|
-
|
|
358
|
-
```bash
|
|
359
|
-
uv run pytest
|
|
360
|
-
uv run --with dash-ant-design python examples/basic.py
|
|
361
|
-
```
|
|
362
|
-
|
|
363
|
-
The example uses `dash_antd_components.ConfigProvider` to demonstrate native
|
|
364
|
-
theme persistence, so `dash-ant-design` is an example-only dependency rather
|
|
365
|
-
than a runtime dependency of the plugin.
|
|
366
|
-
|
|
367
|
-
Build the source distribution and wheel:
|
|
368
|
-
|
|
369
|
-
```bash
|
|
370
|
-
uv build
|
|
371
|
-
```
|
|
372
|
-
|
|
373
|
-
## Publishing to PyPI
|
|
374
|
-
|
|
375
|
-
PyPI does not allow replacing an existing distribution file. Confirm that the
|
|
376
|
-
version in `pyproject.toml` is new before building, then create the artifacts
|
|
377
|
-
from a clean `dist/` directory:
|
|
378
|
-
|
|
379
|
-
```bash
|
|
380
|
-
rm -rf build dist src/*.egg-info
|
|
381
|
-
uv lock
|
|
382
|
-
uv run --extra test pytest
|
|
383
|
-
uvx ruff check src tests examples
|
|
384
|
-
uv build
|
|
385
|
-
uvx twine check dist/*
|
|
386
|
-
```
|
|
387
|
-
|
|
388
|
-
Inspect the resulting files before upload:
|
|
389
|
-
|
|
390
|
-
```bash
|
|
391
|
-
ls -lh dist/
|
|
392
|
-
unzip -l dist/dash_startup_loading_plugin-0.2.0-py3-none-any.whl
|
|
393
|
-
tar -tzf dist/dash_startup_loading_plugin-0.2.0.tar.gz
|
|
394
|
-
uv publish --dry-run dist/*
|
|
395
|
-
```
|
|
396
|
-
|
|
397
|
-
### TestPyPI
|
|
398
|
-
|
|
399
|
-
Create a TestPyPI API token, then upload to its legacy upload endpoint:
|
|
400
|
-
|
|
401
|
-
```bash
|
|
402
|
-
export UV_PUBLISH_TOKEN="pypi-your-test-token"
|
|
403
|
-
uv publish \
|
|
404
|
-
--publish-url https://test.pypi.org/legacy/ \
|
|
405
|
-
--check-url https://test.pypi.org/simple/ \
|
|
406
|
-
dist/*
|
|
407
|
-
unset UV_PUBLISH_TOKEN
|
|
408
|
-
```
|
|
409
|
-
|
|
410
|
-
Test installation in an isolated environment:
|
|
411
|
-
|
|
412
|
-
```bash
|
|
413
|
-
uv run --isolated \
|
|
414
|
-
--index-url https://test.pypi.org/simple/ \
|
|
415
|
-
--extra-index-url https://pypi.org/simple/ \
|
|
416
|
-
--with dash-startup-loading-plugin==0.2.0 \
|
|
417
|
-
python -c "import dash_startup_loading_plugin; print('import ok')"
|
|
418
|
-
```
|
|
419
|
-
|
|
420
|
-
The extra PyPI index is needed because TestPyPI may not contain the plugin's
|
|
421
|
-
runtime dependencies.
|
|
422
|
-
|
|
423
|
-
### PyPI with an API token
|
|
424
|
-
|
|
425
|
-
Create a project-scoped PyPI token and publish the checked artifacts:
|
|
426
|
-
|
|
427
|
-
```bash
|
|
428
|
-
export UV_PUBLISH_TOKEN="pypi-your-project-token"
|
|
429
|
-
uv publish dist/*
|
|
430
|
-
unset UV_PUBLISH_TOKEN
|
|
431
|
-
```
|
|
432
|
-
|
|
433
|
-
Do not place tokens in `pyproject.toml`, shell history, source control, or CI
|
|
434
|
-
logs.
|
|
435
|
-
|
|
436
|
-
### PyPI Trusted Publishing
|
|
437
|
-
|
|
438
|
-
For CI releases, configure the repository and workflow as a Trusted Publisher
|
|
439
|
-
in the PyPI project settings. The publishing job needs the
|
|
440
|
-
`id-token: write` permission and can upload without a long-lived token:
|
|
441
|
-
|
|
442
|
-
```yaml
|
|
443
|
-
permissions:
|
|
444
|
-
contents: read
|
|
445
|
-
id-token: write
|
|
446
|
-
|
|
447
|
-
steps:
|
|
448
|
-
- uses: actions/checkout@v4
|
|
449
|
-
- uses: astral-sh/setup-uv@v8
|
|
450
|
-
- run: uv build
|
|
451
|
-
- run: uv publish --trusted-publishing always
|
|
452
|
-
```
|
|
453
|
-
|
|
454
|
-
After publishing, verify the release:
|
|
455
|
-
|
|
456
|
-
```bash
|
|
457
|
-
uv run --isolated \
|
|
458
|
-
--with dash-startup-loading-plugin==0.2.0 \
|
|
459
|
-
python -c "import dash_startup_loading_plugin; print('import ok')"
|
|
460
|
-
```
|
|
461
|
-
|
|
462
450
|
## License
|
|
463
451
|
|
|
464
452
|
MIT. See [`LICENSE`](LICENSE).
|
|
@@ -35,7 +35,7 @@ discovery, resource hooks, and the index hook described in the official
|
|
|
35
35
|
## Installation
|
|
36
36
|
|
|
37
37
|
```bash
|
|
38
|
-
pip install "dash-startup-loading-plugin>=0.
|
|
38
|
+
pip install "dash-startup-loading-plugin>=0.3.0"
|
|
39
39
|
```
|
|
40
40
|
|
|
41
41
|
The package declares the following entry point:
|
|
@@ -50,6 +50,16 @@ package therefore enables the default startup overlay for Dash applications in
|
|
|
50
50
|
that Python environment; an explicit import is only needed when changing its
|
|
51
51
|
configuration or using its Python API.
|
|
52
52
|
|
|
53
|
+
## What's new in 0.3.0
|
|
54
|
+
|
|
55
|
+
- Adds `configure_antd()` with matching `#ffffff` and `#121212` loading
|
|
56
|
+
backgrounds plus explicit light/dark overrides.
|
|
57
|
+
- Adds `configure_mantine()` with theme defaults sourced from Dash Mantine
|
|
58
|
+
Components and its pre-render color-scheme hook.
|
|
59
|
+
- Updates the Dash Ant Design example for the structured
|
|
60
|
+
`ConfigProvider.theme.algorithm` API.
|
|
61
|
+
- Adds a Dash Mantine Components example with persisted theme switching.
|
|
62
|
+
|
|
53
63
|
## What's new in 0.2.0
|
|
54
64
|
|
|
55
65
|
- Resolves the loading background before first paint from a persisted
|
|
@@ -88,25 +98,33 @@ from dash import Dash, html
|
|
|
88
98
|
from dash_startup_loading_plugin import configure
|
|
89
99
|
|
|
90
100
|
configure(
|
|
91
|
-
required_selectors=["#
|
|
101
|
+
required_selectors=["#usage-header", "#usage-sidebar-menu"],
|
|
92
102
|
pending_selector="[data-dac-async-placeholder]",
|
|
93
103
|
timeout_ms=6000,
|
|
94
|
-
minimum_display_ms=200,
|
|
95
104
|
fade_duration_ms=160,
|
|
96
|
-
color="#1677ff",
|
|
97
|
-
dark_color="#4096ff",
|
|
98
105
|
)
|
|
99
106
|
|
|
100
107
|
app = Dash(__name__)
|
|
101
108
|
app.layout = html.Div(
|
|
102
109
|
[
|
|
103
|
-
html.Header("Dashboard", id="
|
|
104
|
-
html.Nav("Navigation", id="sidebar-menu"),
|
|
110
|
+
html.Header("Dashboard", id="usage-header"),
|
|
111
|
+
html.Nav("Navigation", id="usage-sidebar-menu"),
|
|
105
112
|
]
|
|
106
113
|
)
|
|
107
114
|
```
|
|
108
115
|
|
|
109
|
-
|
|
116
|
+
Runnable examples:
|
|
117
|
+
|
|
118
|
+
- [`examples/basic.py`](examples/basic.py): Dash Ant Design theme persistence.
|
|
119
|
+
- [`examples/mantine_theme.py`](examples/mantine_theme.py):
|
|
120
|
+
Dash Mantine Components with a persisted light/dark theme switch.
|
|
121
|
+
|
|
122
|
+
Run the Mantine example with:
|
|
123
|
+
|
|
124
|
+
```bash
|
|
125
|
+
pip install -e ".[mantine]"
|
|
126
|
+
python examples/mantine_theme.py
|
|
127
|
+
```
|
|
110
128
|
|
|
111
129
|
## Readiness behavior
|
|
112
130
|
|
|
@@ -176,29 +194,105 @@ parsed. This isolates the overlay styling without changing classes or data
|
|
|
176
194
|
attributes owned by Tailwind, Dash Mantine Components, or the application.
|
|
177
195
|
The stylesheet also adjusts its animation for `prefers-reduced-motion`.
|
|
178
196
|
|
|
197
|
+
### Dash Mantine Components
|
|
198
|
+
|
|
199
|
+
Dash Mantine Components 2.6 and later includes `ColorSchemeToggle`, which
|
|
200
|
+
switches the active color scheme without a callback and persists it in
|
|
201
|
+
`mantine-color-scheme-value`. The startup plugin reads that value before Dash
|
|
202
|
+
and React mount. To make the loading transition seamless, use Mantine's current
|
|
203
|
+
default dark body color for the overlay:
|
|
204
|
+
|
|
205
|
+
```python
|
|
206
|
+
import dash_mantine_components as dmc
|
|
207
|
+
from dash_startup_loading_plugin import configure_mantine
|
|
208
|
+
|
|
209
|
+
configure_mantine()
|
|
210
|
+
|
|
211
|
+
app.layout = dmc.MantineProvider(
|
|
212
|
+
[
|
|
213
|
+
dmc.ColorSchemeToggle(
|
|
214
|
+
lightIcon=dmc.Text("☀"),
|
|
215
|
+
darkIcon=dmc.Text("☾"),
|
|
216
|
+
),
|
|
217
|
+
dmc.Text("Application content", id="app-ready"),
|
|
218
|
+
],
|
|
219
|
+
defaultColorScheme="auto",
|
|
220
|
+
)
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
See
|
|
224
|
+
[`examples/mantine_theme.py`](examples/mantine_theme.py)
|
|
225
|
+
for the complete runnable example.
|
|
226
|
+
|
|
227
|
+
`configure_mantine()` keeps DMC optional. When used, it registers DMC's
|
|
228
|
+
pre-render color-scheme hook, disables the Dash Ant Design placeholder check,
|
|
229
|
+
and derives the light and dark loading backgrounds from DMC's active default
|
|
230
|
+
theme. Any explicit configuration still takes precedence:
|
|
231
|
+
|
|
232
|
+
```python
|
|
233
|
+
configure_mantine(dark_background="#202020")
|
|
234
|
+
```
|
|
235
|
+
|
|
179
236
|
### Dash Ant Design persistence
|
|
180
237
|
|
|
181
|
-
`dash_antd_components.ConfigProvider`
|
|
182
|
-
|
|
238
|
+
Current `dash_antd_components.ConfigProvider.theme` accepts a structured theme
|
|
239
|
+
configuration whose `algorithm` is `"default"`, `"dark"`, `"compact"`, or an
|
|
240
|
+
array of those values. Persist a user-facing system/light/dark selector, resolve
|
|
241
|
+
the system preference in a clientside callback, and store the semantic choice
|
|
242
|
+
in the small conventional `localStorage["theme"]` value that the startup plugin
|
|
243
|
+
reads before Dash mounts:
|
|
183
244
|
|
|
184
245
|
```python
|
|
185
246
|
import dash_antd_components as dac
|
|
186
|
-
from
|
|
247
|
+
from dash import Input, Output, clientside_callback
|
|
248
|
+
from dash_startup_loading_plugin import configure_antd
|
|
187
249
|
|
|
188
|
-
|
|
250
|
+
configure_antd()
|
|
189
251
|
|
|
190
252
|
provider = dac.ConfigProvider(
|
|
191
|
-
|
|
253
|
+
[
|
|
254
|
+
app_content,
|
|
255
|
+
dac.Segmented(
|
|
256
|
+
id="theme-mode",
|
|
257
|
+
options=["system", "light", "dark"],
|
|
258
|
+
value="system",
|
|
259
|
+
persistence=True,
|
|
260
|
+
persisted_props=["value"],
|
|
261
|
+
persistence_type="local",
|
|
262
|
+
),
|
|
263
|
+
],
|
|
192
264
|
id="theme-provider",
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
265
|
+
theme={"algorithm": "default"},
|
|
266
|
+
)
|
|
267
|
+
|
|
268
|
+
clientside_callback(
|
|
269
|
+
"""function(mode) {
|
|
270
|
+
localStorage.setItem("theme", JSON.stringify(mode));
|
|
271
|
+
const dark = mode === "dark" || (
|
|
272
|
+
mode === "system"
|
|
273
|
+
&& window.matchMedia("(prefers-color-scheme: dark)").matches
|
|
274
|
+
);
|
|
275
|
+
return {algorithm: dark ? "dark" : "default"};
|
|
276
|
+
}""",
|
|
277
|
+
Output("theme-provider", "theme"),
|
|
278
|
+
Input("theme-mode", "value"),
|
|
279
|
+
)
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
The selector owns the semantic `"system"` state; `ConfigProvider` receives only
|
|
283
|
+
the algorithms allowed by its current API. `configure_antd()` uses `#ffffff`
|
|
284
|
+
and `#121212`, matching the component library's default light and dark
|
|
285
|
+
backgrounds. If the application customizes either background, pass the same
|
|
286
|
+
values explicitly:
|
|
287
|
+
|
|
288
|
+
```python
|
|
289
|
+
configure_antd(
|
|
290
|
+
background="#f5f5f5",
|
|
291
|
+
dark_background="#202020",
|
|
196
292
|
)
|
|
197
293
|
```
|
|
198
294
|
|
|
199
|
-
|
|
200
|
-
the UI edit, `ConfigProvider` restores it after mount, and the startup plugin
|
|
201
|
-
uses it for the loading background before mount. See
|
|
295
|
+
See
|
|
202
296
|
[`examples/basic.py`](examples/basic.py) for the complete switcher.
|
|
203
297
|
|
|
204
298
|
### Custom loader markup
|
|
@@ -226,12 +320,18 @@ configuration. Never populate it with user input.
|
|
|
226
320
|
from dash_startup_loading_plugin import (
|
|
227
321
|
StartupLoadingConfig,
|
|
228
322
|
configure,
|
|
323
|
+
configure_antd,
|
|
324
|
+
configure_mantine,
|
|
229
325
|
get_config,
|
|
230
326
|
reset_config,
|
|
231
327
|
)
|
|
232
328
|
```
|
|
233
329
|
|
|
234
330
|
- `configure(**changes)` validates and applies a partial configuration update.
|
|
331
|
+
- `configure_antd(**changes)` matches the loading backgrounds to Dash Ant
|
|
332
|
+
Design's light and dark themes and accepts explicit overrides.
|
|
333
|
+
- `configure_mantine(**changes)` applies DMC theme defaults and registers DMC's
|
|
334
|
+
pre-render color-scheme hook.
|
|
235
335
|
- `get_config()` returns the current immutable configuration.
|
|
236
336
|
- `reset_config()` restores all defaults. It is primarily useful in tests.
|
|
237
337
|
- `StartupLoadingConfig` is the frozen dataclass containing all options.
|
|
@@ -329,120 +429,6 @@ This is expected with automatic `dash_hooks` discovery. Use a dedicated virtual
|
|
|
329
429
|
environment, uninstall the package where it is not wanted, or call
|
|
330
430
|
`configure(enabled=False)` before constructing those Dash apps.
|
|
331
431
|
|
|
332
|
-
## Development
|
|
333
|
-
|
|
334
|
-
Clone the repository, then install the project and test dependencies:
|
|
335
|
-
|
|
336
|
-
```bash
|
|
337
|
-
uv sync --extra test
|
|
338
|
-
```
|
|
339
|
-
|
|
340
|
-
Run the tests and example:
|
|
341
|
-
|
|
342
|
-
```bash
|
|
343
|
-
uv run pytest
|
|
344
|
-
uv run --with dash-ant-design python examples/basic.py
|
|
345
|
-
```
|
|
346
|
-
|
|
347
|
-
The example uses `dash_antd_components.ConfigProvider` to demonstrate native
|
|
348
|
-
theme persistence, so `dash-ant-design` is an example-only dependency rather
|
|
349
|
-
than a runtime dependency of the plugin.
|
|
350
|
-
|
|
351
|
-
Build the source distribution and wheel:
|
|
352
|
-
|
|
353
|
-
```bash
|
|
354
|
-
uv build
|
|
355
|
-
```
|
|
356
|
-
|
|
357
|
-
## Publishing to PyPI
|
|
358
|
-
|
|
359
|
-
PyPI does not allow replacing an existing distribution file. Confirm that the
|
|
360
|
-
version in `pyproject.toml` is new before building, then create the artifacts
|
|
361
|
-
from a clean `dist/` directory:
|
|
362
|
-
|
|
363
|
-
```bash
|
|
364
|
-
rm -rf build dist src/*.egg-info
|
|
365
|
-
uv lock
|
|
366
|
-
uv run --extra test pytest
|
|
367
|
-
uvx ruff check src tests examples
|
|
368
|
-
uv build
|
|
369
|
-
uvx twine check dist/*
|
|
370
|
-
```
|
|
371
|
-
|
|
372
|
-
Inspect the resulting files before upload:
|
|
373
|
-
|
|
374
|
-
```bash
|
|
375
|
-
ls -lh dist/
|
|
376
|
-
unzip -l dist/dash_startup_loading_plugin-0.2.0-py3-none-any.whl
|
|
377
|
-
tar -tzf dist/dash_startup_loading_plugin-0.2.0.tar.gz
|
|
378
|
-
uv publish --dry-run dist/*
|
|
379
|
-
```
|
|
380
|
-
|
|
381
|
-
### TestPyPI
|
|
382
|
-
|
|
383
|
-
Create a TestPyPI API token, then upload to its legacy upload endpoint:
|
|
384
|
-
|
|
385
|
-
```bash
|
|
386
|
-
export UV_PUBLISH_TOKEN="pypi-your-test-token"
|
|
387
|
-
uv publish \
|
|
388
|
-
--publish-url https://test.pypi.org/legacy/ \
|
|
389
|
-
--check-url https://test.pypi.org/simple/ \
|
|
390
|
-
dist/*
|
|
391
|
-
unset UV_PUBLISH_TOKEN
|
|
392
|
-
```
|
|
393
|
-
|
|
394
|
-
Test installation in an isolated environment:
|
|
395
|
-
|
|
396
|
-
```bash
|
|
397
|
-
uv run --isolated \
|
|
398
|
-
--index-url https://test.pypi.org/simple/ \
|
|
399
|
-
--extra-index-url https://pypi.org/simple/ \
|
|
400
|
-
--with dash-startup-loading-plugin==0.2.0 \
|
|
401
|
-
python -c "import dash_startup_loading_plugin; print('import ok')"
|
|
402
|
-
```
|
|
403
|
-
|
|
404
|
-
The extra PyPI index is needed because TestPyPI may not contain the plugin's
|
|
405
|
-
runtime dependencies.
|
|
406
|
-
|
|
407
|
-
### PyPI with an API token
|
|
408
|
-
|
|
409
|
-
Create a project-scoped PyPI token and publish the checked artifacts:
|
|
410
|
-
|
|
411
|
-
```bash
|
|
412
|
-
export UV_PUBLISH_TOKEN="pypi-your-project-token"
|
|
413
|
-
uv publish dist/*
|
|
414
|
-
unset UV_PUBLISH_TOKEN
|
|
415
|
-
```
|
|
416
|
-
|
|
417
|
-
Do not place tokens in `pyproject.toml`, shell history, source control, or CI
|
|
418
|
-
logs.
|
|
419
|
-
|
|
420
|
-
### PyPI Trusted Publishing
|
|
421
|
-
|
|
422
|
-
For CI releases, configure the repository and workflow as a Trusted Publisher
|
|
423
|
-
in the PyPI project settings. The publishing job needs the
|
|
424
|
-
`id-token: write` permission and can upload without a long-lived token:
|
|
425
|
-
|
|
426
|
-
```yaml
|
|
427
|
-
permissions:
|
|
428
|
-
contents: read
|
|
429
|
-
id-token: write
|
|
430
|
-
|
|
431
|
-
steps:
|
|
432
|
-
- uses: actions/checkout@v4
|
|
433
|
-
- uses: astral-sh/setup-uv@v8
|
|
434
|
-
- run: uv build
|
|
435
|
-
- run: uv publish --trusted-publishing always
|
|
436
|
-
```
|
|
437
|
-
|
|
438
|
-
After publishing, verify the release:
|
|
439
|
-
|
|
440
|
-
```bash
|
|
441
|
-
uv run --isolated \
|
|
442
|
-
--with dash-startup-loading-plugin==0.2.0 \
|
|
443
|
-
python -c "import dash_startup_loading_plugin; print('import ok')"
|
|
444
|
-
```
|
|
445
|
-
|
|
446
432
|
## License
|
|
447
433
|
|
|
448
434
|
MIT. See [`LICENSE`](LICENSE).
|
|
@@ -4,25 +4,28 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "dash-startup-loading-plugin"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.3.0"
|
|
8
8
|
description = "A configurable full-screen startup loading overlay for Dash apps, packaged as a Dash Hooks plugin."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.9"
|
|
11
11
|
authors = [
|
|
12
12
|
{ name = "Ethan Zhang", email = "ethan.zhang2016@gmail.com" }
|
|
13
13
|
]
|
|
14
|
-
keywords = ["dash", "plotly", "loading", "plugin"
|
|
14
|
+
keywords = ["dash", "plotly", "loading", "plugin"]
|
|
15
15
|
classifiers = ["Framework :: Dash"]
|
|
16
16
|
license = "MIT"
|
|
17
17
|
license-files = ["LICENSE"]
|
|
18
18
|
dependencies = [
|
|
19
|
-
"dash
|
|
19
|
+
"dash==4.4.1",
|
|
20
20
|
]
|
|
21
21
|
|
|
22
22
|
[project.optional-dependencies]
|
|
23
23
|
test = [
|
|
24
24
|
"pytest>=8",
|
|
25
25
|
]
|
|
26
|
+
mantine = [
|
|
27
|
+
"dash-mantine-components>=2.6.0",
|
|
28
|
+
]
|
|
26
29
|
|
|
27
30
|
[project.entry-points."dash_hooks"]
|
|
28
31
|
dash_startup_loading_plugin = "dash_startup_loading_plugin"
|
|
@@ -5,11 +5,13 @@ from importlib.metadata import PackageNotFoundError, version
|
|
|
5
5
|
try:
|
|
6
6
|
__version__ = version("dash-startup-loading-plugin")
|
|
7
7
|
except PackageNotFoundError: # pragma: no cover - source tree fallback
|
|
8
|
-
__version__ = "0.
|
|
8
|
+
__version__ = "0.3.0"
|
|
9
9
|
|
|
10
|
-
from .plugin import (
|
|
10
|
+
from .plugin import (
|
|
11
11
|
StartupLoadingConfig,
|
|
12
12
|
configure,
|
|
13
|
+
configure_antd,
|
|
14
|
+
configure_mantine,
|
|
13
15
|
get_config,
|
|
14
16
|
reset_config,
|
|
15
17
|
)
|
|
@@ -18,6 +20,8 @@ __all__ = [
|
|
|
18
20
|
"StartupLoadingConfig",
|
|
19
21
|
"__version__",
|
|
20
22
|
"configure",
|
|
23
|
+
"configure_antd",
|
|
24
|
+
"configure_mantine",
|
|
21
25
|
"get_config",
|
|
22
26
|
"reset_config",
|
|
23
27
|
]
|
|
@@ -4,12 +4,13 @@ from __future__ import annotations
|
|
|
4
4
|
|
|
5
5
|
import json
|
|
6
6
|
import re
|
|
7
|
+
from collections.abc import Iterable
|
|
7
8
|
from dataclasses import asdict, dataclass, fields, replace
|
|
8
9
|
from functools import lru_cache
|
|
9
10
|
from html import escape
|
|
10
11
|
from importlib.resources import files
|
|
11
12
|
from threading import RLock
|
|
12
|
-
from typing import Any
|
|
13
|
+
from typing import Any
|
|
13
14
|
|
|
14
15
|
from dash import hooks
|
|
15
16
|
|
|
@@ -17,6 +18,9 @@ _OVERLAY_MARKER = "data-dash-startup-loading"
|
|
|
17
18
|
_BODY_PATTERN = re.compile(r"<body(?:\s[^>]*)?>", flags=re.IGNORECASE)
|
|
18
19
|
_HEAD_END_PATTERN = re.compile(r"</head\s*>", flags=re.IGNORECASE)
|
|
19
20
|
_CONFIG_LOCK = RLock()
|
|
21
|
+
_ANTD_LIGHT_BACKGROUND = "#ffffff"
|
|
22
|
+
_ANTD_DARK_BACKGROUND = "#121212"
|
|
23
|
+
_mantine_prerender_registered = False
|
|
20
24
|
|
|
21
25
|
|
|
22
26
|
@dataclass(frozen=True)
|
|
@@ -78,8 +82,7 @@ def _validate(config: StartupLoadingConfig) -> StartupLoadingConfig:
|
|
|
78
82
|
if config.theme_mode not in {"auto", "light", "dark"}:
|
|
79
83
|
raise ValueError("theme_mode must be 'auto', 'light', or 'dark'")
|
|
80
84
|
if config.dash_theme_component_id is not None and (
|
|
81
|
-
not isinstance(config.dash_theme_component_id, str)
|
|
82
|
-
or not config.dash_theme_component_id.strip()
|
|
85
|
+
not isinstance(config.dash_theme_component_id, str) or not config.dash_theme_component_id.strip()
|
|
83
86
|
):
|
|
84
87
|
raise ValueError("dash_theme_component_id must be None or a non-empty string")
|
|
85
88
|
for name in ("minimum_display_ms", "fade_duration_ms", "spinner_size_px", "spinner_stroke_px"):
|
|
@@ -109,6 +112,54 @@ def configure(**changes: Any) -> StartupLoadingConfig:
|
|
|
109
112
|
return _config
|
|
110
113
|
|
|
111
114
|
|
|
115
|
+
def configure_mantine(**changes: Any) -> StartupLoadingConfig:
|
|
116
|
+
"""Configure seamless startup colors for Dash Mantine Components.
|
|
117
|
+
|
|
118
|
+
Dash Mantine Components remains an optional dependency. This helper reads
|
|
119
|
+
its active default theme and registers its pre-render color-scheme hook.
|
|
120
|
+
Explicit keyword arguments override all integration defaults.
|
|
121
|
+
"""
|
|
122
|
+
|
|
123
|
+
try:
|
|
124
|
+
import dash_mantine_components as dmc
|
|
125
|
+
except ImportError as error:
|
|
126
|
+
raise RuntimeError(
|
|
127
|
+
"configure_mantine requires dash-mantine-components>=2.6.0"
|
|
128
|
+
) from error
|
|
129
|
+
|
|
130
|
+
pre_render_color_scheme = getattr(dmc, "pre_render_color_scheme", None)
|
|
131
|
+
if pre_render_color_scheme is None:
|
|
132
|
+
raise RuntimeError(
|
|
133
|
+
"configure_mantine requires dash-mantine-components>=2.6.0"
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
theme = dmc.DEFAULT_THEME
|
|
137
|
+
changes.setdefault("background", theme["white"])
|
|
138
|
+
changes.setdefault("dark_background", theme["colors"]["dark"][7])
|
|
139
|
+
changes.setdefault("pending_selector", None)
|
|
140
|
+
|
|
141
|
+
global _mantine_prerender_registered
|
|
142
|
+
with _CONFIG_LOCK:
|
|
143
|
+
if not _mantine_prerender_registered:
|
|
144
|
+
pre_render_color_scheme()
|
|
145
|
+
_mantine_prerender_registered = True
|
|
146
|
+
|
|
147
|
+
return configure(**changes)
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def configure_antd(**changes: Any) -> StartupLoadingConfig:
|
|
151
|
+
"""Configure startup colors for Dash Ant Design.
|
|
152
|
+
|
|
153
|
+
Explicit keyword arguments override the integration defaults, including
|
|
154
|
+
``background`` and ``dark_background`` when the application's Ant Design
|
|
155
|
+
theme customizes its light or dark background token.
|
|
156
|
+
"""
|
|
157
|
+
|
|
158
|
+
changes.setdefault("background", _ANTD_LIGHT_BACKGROUND)
|
|
159
|
+
changes.setdefault("dark_background", _ANTD_DARK_BACKGROUND)
|
|
160
|
+
return configure(**changes)
|
|
161
|
+
|
|
162
|
+
|
|
112
163
|
def get_config() -> StartupLoadingConfig:
|
|
113
164
|
"""Return the active immutable configuration."""
|
|
114
165
|
|
|
@@ -181,12 +232,7 @@ def _overlay_html(config: StartupLoadingConfig) -> str:
|
|
|
181
232
|
|
|
182
233
|
@lru_cache(maxsize=2)
|
|
183
234
|
def _resource_text(name: str) -> str:
|
|
184
|
-
return (
|
|
185
|
-
files("dash_startup_loading_plugin")
|
|
186
|
-
.joinpath("resources", name)
|
|
187
|
-
.read_text(encoding="utf-8")
|
|
188
|
-
.strip()
|
|
189
|
-
)
|
|
235
|
+
return files("dash_startup_loading_plugin").joinpath("resources", name).read_text(encoding="utf-8").strip()
|
|
190
236
|
|
|
191
237
|
|
|
192
238
|
def _inline_head_resources(app_index: str, config: StartupLoadingConfig) -> str:
|
|
@@ -218,11 +264,7 @@ def _inject_overlay(app_index: str) -> str:
|
|
|
218
264
|
body_match = _BODY_PATTERN.search(app_index)
|
|
219
265
|
assert body_match is not None
|
|
220
266
|
position = body_match.end()
|
|
221
|
-
script = (
|
|
222
|
-
'<script data-dash-startup-loading-resource="script">'
|
|
223
|
-
f"{_resource_text('startup-loading.js')}"
|
|
224
|
-
"</script>"
|
|
225
|
-
)
|
|
267
|
+
script = f'<script data-dash-startup-loading-resource="script">{_resource_text("startup-loading.js")}</script>'
|
|
226
268
|
return app_index[:position] + _overlay_html(config) + script + app_index[position:]
|
|
227
269
|
|
|
228
270
|
|
|
@@ -1,17 +1,19 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: dash-startup-loading-plugin
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: A configurable full-screen startup loading overlay for Dash apps, packaged as a Dash Hooks plugin.
|
|
5
5
|
Author-email: Ethan Zhang <ethan.zhang2016@gmail.com>
|
|
6
6
|
License-Expression: MIT
|
|
7
|
-
Keywords: dash,plotly,loading,plugin
|
|
7
|
+
Keywords: dash,plotly,loading,plugin
|
|
8
8
|
Classifier: Framework :: Dash
|
|
9
9
|
Requires-Python: >=3.9
|
|
10
10
|
Description-Content-Type: text/markdown
|
|
11
11
|
License-File: LICENSE
|
|
12
|
-
Requires-Dist: dash
|
|
12
|
+
Requires-Dist: dash==4.4.1
|
|
13
13
|
Provides-Extra: test
|
|
14
14
|
Requires-Dist: pytest>=8; extra == "test"
|
|
15
|
+
Provides-Extra: mantine
|
|
16
|
+
Requires-Dist: dash-mantine-components>=2.6.0; extra == "mantine"
|
|
15
17
|
Dynamic: license-file
|
|
16
18
|
|
|
17
19
|
# dash-startup-loading-plugin
|
|
@@ -51,7 +53,7 @@ discovery, resource hooks, and the index hook described in the official
|
|
|
51
53
|
## Installation
|
|
52
54
|
|
|
53
55
|
```bash
|
|
54
|
-
pip install "dash-startup-loading-plugin>=0.
|
|
56
|
+
pip install "dash-startup-loading-plugin>=0.3.0"
|
|
55
57
|
```
|
|
56
58
|
|
|
57
59
|
The package declares the following entry point:
|
|
@@ -66,6 +68,16 @@ package therefore enables the default startup overlay for Dash applications in
|
|
|
66
68
|
that Python environment; an explicit import is only needed when changing its
|
|
67
69
|
configuration or using its Python API.
|
|
68
70
|
|
|
71
|
+
## What's new in 0.3.0
|
|
72
|
+
|
|
73
|
+
- Adds `configure_antd()` with matching `#ffffff` and `#121212` loading
|
|
74
|
+
backgrounds plus explicit light/dark overrides.
|
|
75
|
+
- Adds `configure_mantine()` with theme defaults sourced from Dash Mantine
|
|
76
|
+
Components and its pre-render color-scheme hook.
|
|
77
|
+
- Updates the Dash Ant Design example for the structured
|
|
78
|
+
`ConfigProvider.theme.algorithm` API.
|
|
79
|
+
- Adds a Dash Mantine Components example with persisted theme switching.
|
|
80
|
+
|
|
69
81
|
## What's new in 0.2.0
|
|
70
82
|
|
|
71
83
|
- Resolves the loading background before first paint from a persisted
|
|
@@ -104,25 +116,33 @@ from dash import Dash, html
|
|
|
104
116
|
from dash_startup_loading_plugin import configure
|
|
105
117
|
|
|
106
118
|
configure(
|
|
107
|
-
required_selectors=["#
|
|
119
|
+
required_selectors=["#usage-header", "#usage-sidebar-menu"],
|
|
108
120
|
pending_selector="[data-dac-async-placeholder]",
|
|
109
121
|
timeout_ms=6000,
|
|
110
|
-
minimum_display_ms=200,
|
|
111
122
|
fade_duration_ms=160,
|
|
112
|
-
color="#1677ff",
|
|
113
|
-
dark_color="#4096ff",
|
|
114
123
|
)
|
|
115
124
|
|
|
116
125
|
app = Dash(__name__)
|
|
117
126
|
app.layout = html.Div(
|
|
118
127
|
[
|
|
119
|
-
html.Header("Dashboard", id="
|
|
120
|
-
html.Nav("Navigation", id="sidebar-menu"),
|
|
128
|
+
html.Header("Dashboard", id="usage-header"),
|
|
129
|
+
html.Nav("Navigation", id="usage-sidebar-menu"),
|
|
121
130
|
]
|
|
122
131
|
)
|
|
123
132
|
```
|
|
124
133
|
|
|
125
|
-
|
|
134
|
+
Runnable examples:
|
|
135
|
+
|
|
136
|
+
- [`examples/basic.py`](examples/basic.py): Dash Ant Design theme persistence.
|
|
137
|
+
- [`examples/mantine_theme.py`](examples/mantine_theme.py):
|
|
138
|
+
Dash Mantine Components with a persisted light/dark theme switch.
|
|
139
|
+
|
|
140
|
+
Run the Mantine example with:
|
|
141
|
+
|
|
142
|
+
```bash
|
|
143
|
+
pip install -e ".[mantine]"
|
|
144
|
+
python examples/mantine_theme.py
|
|
145
|
+
```
|
|
126
146
|
|
|
127
147
|
## Readiness behavior
|
|
128
148
|
|
|
@@ -192,29 +212,105 @@ parsed. This isolates the overlay styling without changing classes or data
|
|
|
192
212
|
attributes owned by Tailwind, Dash Mantine Components, or the application.
|
|
193
213
|
The stylesheet also adjusts its animation for `prefers-reduced-motion`.
|
|
194
214
|
|
|
215
|
+
### Dash Mantine Components
|
|
216
|
+
|
|
217
|
+
Dash Mantine Components 2.6 and later includes `ColorSchemeToggle`, which
|
|
218
|
+
switches the active color scheme without a callback and persists it in
|
|
219
|
+
`mantine-color-scheme-value`. The startup plugin reads that value before Dash
|
|
220
|
+
and React mount. To make the loading transition seamless, use Mantine's current
|
|
221
|
+
default dark body color for the overlay:
|
|
222
|
+
|
|
223
|
+
```python
|
|
224
|
+
import dash_mantine_components as dmc
|
|
225
|
+
from dash_startup_loading_plugin import configure_mantine
|
|
226
|
+
|
|
227
|
+
configure_mantine()
|
|
228
|
+
|
|
229
|
+
app.layout = dmc.MantineProvider(
|
|
230
|
+
[
|
|
231
|
+
dmc.ColorSchemeToggle(
|
|
232
|
+
lightIcon=dmc.Text("☀"),
|
|
233
|
+
darkIcon=dmc.Text("☾"),
|
|
234
|
+
),
|
|
235
|
+
dmc.Text("Application content", id="app-ready"),
|
|
236
|
+
],
|
|
237
|
+
defaultColorScheme="auto",
|
|
238
|
+
)
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
See
|
|
242
|
+
[`examples/mantine_theme.py`](examples/mantine_theme.py)
|
|
243
|
+
for the complete runnable example.
|
|
244
|
+
|
|
245
|
+
`configure_mantine()` keeps DMC optional. When used, it registers DMC's
|
|
246
|
+
pre-render color-scheme hook, disables the Dash Ant Design placeholder check,
|
|
247
|
+
and derives the light and dark loading backgrounds from DMC's active default
|
|
248
|
+
theme. Any explicit configuration still takes precedence:
|
|
249
|
+
|
|
250
|
+
```python
|
|
251
|
+
configure_mantine(dark_background="#202020")
|
|
252
|
+
```
|
|
253
|
+
|
|
195
254
|
### Dash Ant Design persistence
|
|
196
255
|
|
|
197
|
-
`dash_antd_components.ConfigProvider`
|
|
198
|
-
|
|
256
|
+
Current `dash_antd_components.ConfigProvider.theme` accepts a structured theme
|
|
257
|
+
configuration whose `algorithm` is `"default"`, `"dark"`, `"compact"`, or an
|
|
258
|
+
array of those values. Persist a user-facing system/light/dark selector, resolve
|
|
259
|
+
the system preference in a clientside callback, and store the semantic choice
|
|
260
|
+
in the small conventional `localStorage["theme"]` value that the startup plugin
|
|
261
|
+
reads before Dash mounts:
|
|
199
262
|
|
|
200
263
|
```python
|
|
201
264
|
import dash_antd_components as dac
|
|
202
|
-
from
|
|
265
|
+
from dash import Input, Output, clientside_callback
|
|
266
|
+
from dash_startup_loading_plugin import configure_antd
|
|
203
267
|
|
|
204
|
-
|
|
268
|
+
configure_antd()
|
|
205
269
|
|
|
206
270
|
provider = dac.ConfigProvider(
|
|
207
|
-
|
|
271
|
+
[
|
|
272
|
+
app_content,
|
|
273
|
+
dac.Segmented(
|
|
274
|
+
id="theme-mode",
|
|
275
|
+
options=["system", "light", "dark"],
|
|
276
|
+
value="system",
|
|
277
|
+
persistence=True,
|
|
278
|
+
persisted_props=["value"],
|
|
279
|
+
persistence_type="local",
|
|
280
|
+
),
|
|
281
|
+
],
|
|
208
282
|
id="theme-provider",
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
283
|
+
theme={"algorithm": "default"},
|
|
284
|
+
)
|
|
285
|
+
|
|
286
|
+
clientside_callback(
|
|
287
|
+
"""function(mode) {
|
|
288
|
+
localStorage.setItem("theme", JSON.stringify(mode));
|
|
289
|
+
const dark = mode === "dark" || (
|
|
290
|
+
mode === "system"
|
|
291
|
+
&& window.matchMedia("(prefers-color-scheme: dark)").matches
|
|
292
|
+
);
|
|
293
|
+
return {algorithm: dark ? "dark" : "default"};
|
|
294
|
+
}""",
|
|
295
|
+
Output("theme-provider", "theme"),
|
|
296
|
+
Input("theme-mode", "value"),
|
|
297
|
+
)
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
The selector owns the semantic `"system"` state; `ConfigProvider` receives only
|
|
301
|
+
the algorithms allowed by its current API. `configure_antd()` uses `#ffffff`
|
|
302
|
+
and `#121212`, matching the component library's default light and dark
|
|
303
|
+
backgrounds. If the application customizes either background, pass the same
|
|
304
|
+
values explicitly:
|
|
305
|
+
|
|
306
|
+
```python
|
|
307
|
+
configure_antd(
|
|
308
|
+
background="#f5f5f5",
|
|
309
|
+
dark_background="#202020",
|
|
212
310
|
)
|
|
213
311
|
```
|
|
214
312
|
|
|
215
|
-
|
|
216
|
-
the UI edit, `ConfigProvider` restores it after mount, and the startup plugin
|
|
217
|
-
uses it for the loading background before mount. See
|
|
313
|
+
See
|
|
218
314
|
[`examples/basic.py`](examples/basic.py) for the complete switcher.
|
|
219
315
|
|
|
220
316
|
### Custom loader markup
|
|
@@ -242,12 +338,18 @@ configuration. Never populate it with user input.
|
|
|
242
338
|
from dash_startup_loading_plugin import (
|
|
243
339
|
StartupLoadingConfig,
|
|
244
340
|
configure,
|
|
341
|
+
configure_antd,
|
|
342
|
+
configure_mantine,
|
|
245
343
|
get_config,
|
|
246
344
|
reset_config,
|
|
247
345
|
)
|
|
248
346
|
```
|
|
249
347
|
|
|
250
348
|
- `configure(**changes)` validates and applies a partial configuration update.
|
|
349
|
+
- `configure_antd(**changes)` matches the loading backgrounds to Dash Ant
|
|
350
|
+
Design's light and dark themes and accepts explicit overrides.
|
|
351
|
+
- `configure_mantine(**changes)` applies DMC theme defaults and registers DMC's
|
|
352
|
+
pre-render color-scheme hook.
|
|
251
353
|
- `get_config()` returns the current immutable configuration.
|
|
252
354
|
- `reset_config()` restores all defaults. It is primarily useful in tests.
|
|
253
355
|
- `StartupLoadingConfig` is the frozen dataclass containing all options.
|
|
@@ -345,120 +447,6 @@ This is expected with automatic `dash_hooks` discovery. Use a dedicated virtual
|
|
|
345
447
|
environment, uninstall the package where it is not wanted, or call
|
|
346
448
|
`configure(enabled=False)` before constructing those Dash apps.
|
|
347
449
|
|
|
348
|
-
## Development
|
|
349
|
-
|
|
350
|
-
Clone the repository, then install the project and test dependencies:
|
|
351
|
-
|
|
352
|
-
```bash
|
|
353
|
-
uv sync --extra test
|
|
354
|
-
```
|
|
355
|
-
|
|
356
|
-
Run the tests and example:
|
|
357
|
-
|
|
358
|
-
```bash
|
|
359
|
-
uv run pytest
|
|
360
|
-
uv run --with dash-ant-design python examples/basic.py
|
|
361
|
-
```
|
|
362
|
-
|
|
363
|
-
The example uses `dash_antd_components.ConfigProvider` to demonstrate native
|
|
364
|
-
theme persistence, so `dash-ant-design` is an example-only dependency rather
|
|
365
|
-
than a runtime dependency of the plugin.
|
|
366
|
-
|
|
367
|
-
Build the source distribution and wheel:
|
|
368
|
-
|
|
369
|
-
```bash
|
|
370
|
-
uv build
|
|
371
|
-
```
|
|
372
|
-
|
|
373
|
-
## Publishing to PyPI
|
|
374
|
-
|
|
375
|
-
PyPI does not allow replacing an existing distribution file. Confirm that the
|
|
376
|
-
version in `pyproject.toml` is new before building, then create the artifacts
|
|
377
|
-
from a clean `dist/` directory:
|
|
378
|
-
|
|
379
|
-
```bash
|
|
380
|
-
rm -rf build dist src/*.egg-info
|
|
381
|
-
uv lock
|
|
382
|
-
uv run --extra test pytest
|
|
383
|
-
uvx ruff check src tests examples
|
|
384
|
-
uv build
|
|
385
|
-
uvx twine check dist/*
|
|
386
|
-
```
|
|
387
|
-
|
|
388
|
-
Inspect the resulting files before upload:
|
|
389
|
-
|
|
390
|
-
```bash
|
|
391
|
-
ls -lh dist/
|
|
392
|
-
unzip -l dist/dash_startup_loading_plugin-0.2.0-py3-none-any.whl
|
|
393
|
-
tar -tzf dist/dash_startup_loading_plugin-0.2.0.tar.gz
|
|
394
|
-
uv publish --dry-run dist/*
|
|
395
|
-
```
|
|
396
|
-
|
|
397
|
-
### TestPyPI
|
|
398
|
-
|
|
399
|
-
Create a TestPyPI API token, then upload to its legacy upload endpoint:
|
|
400
|
-
|
|
401
|
-
```bash
|
|
402
|
-
export UV_PUBLISH_TOKEN="pypi-your-test-token"
|
|
403
|
-
uv publish \
|
|
404
|
-
--publish-url https://test.pypi.org/legacy/ \
|
|
405
|
-
--check-url https://test.pypi.org/simple/ \
|
|
406
|
-
dist/*
|
|
407
|
-
unset UV_PUBLISH_TOKEN
|
|
408
|
-
```
|
|
409
|
-
|
|
410
|
-
Test installation in an isolated environment:
|
|
411
|
-
|
|
412
|
-
```bash
|
|
413
|
-
uv run --isolated \
|
|
414
|
-
--index-url https://test.pypi.org/simple/ \
|
|
415
|
-
--extra-index-url https://pypi.org/simple/ \
|
|
416
|
-
--with dash-startup-loading-plugin==0.2.0 \
|
|
417
|
-
python -c "import dash_startup_loading_plugin; print('import ok')"
|
|
418
|
-
```
|
|
419
|
-
|
|
420
|
-
The extra PyPI index is needed because TestPyPI may not contain the plugin's
|
|
421
|
-
runtime dependencies.
|
|
422
|
-
|
|
423
|
-
### PyPI with an API token
|
|
424
|
-
|
|
425
|
-
Create a project-scoped PyPI token and publish the checked artifacts:
|
|
426
|
-
|
|
427
|
-
```bash
|
|
428
|
-
export UV_PUBLISH_TOKEN="pypi-your-project-token"
|
|
429
|
-
uv publish dist/*
|
|
430
|
-
unset UV_PUBLISH_TOKEN
|
|
431
|
-
```
|
|
432
|
-
|
|
433
|
-
Do not place tokens in `pyproject.toml`, shell history, source control, or CI
|
|
434
|
-
logs.
|
|
435
|
-
|
|
436
|
-
### PyPI Trusted Publishing
|
|
437
|
-
|
|
438
|
-
For CI releases, configure the repository and workflow as a Trusted Publisher
|
|
439
|
-
in the PyPI project settings. The publishing job needs the
|
|
440
|
-
`id-token: write` permission and can upload without a long-lived token:
|
|
441
|
-
|
|
442
|
-
```yaml
|
|
443
|
-
permissions:
|
|
444
|
-
contents: read
|
|
445
|
-
id-token: write
|
|
446
|
-
|
|
447
|
-
steps:
|
|
448
|
-
- uses: actions/checkout@v4
|
|
449
|
-
- uses: astral-sh/setup-uv@v8
|
|
450
|
-
- run: uv build
|
|
451
|
-
- run: uv publish --trusted-publishing always
|
|
452
|
-
```
|
|
453
|
-
|
|
454
|
-
After publishing, verify the release:
|
|
455
|
-
|
|
456
|
-
```bash
|
|
457
|
-
uv run --isolated \
|
|
458
|
-
--with dash-startup-loading-plugin==0.2.0 \
|
|
459
|
-
python -c "import dash_startup_loading_plugin; print('import ok')"
|
|
460
|
-
```
|
|
461
|
-
|
|
462
450
|
## License
|
|
463
451
|
|
|
464
452
|
MIT. See [`LICENSE`](LICENSE).
|
{dash_startup_loading_plugin-0.2.0 → dash_startup_loading_plugin-0.3.0}/tests/test_plugin.py
RENAMED
|
@@ -1,12 +1,21 @@
|
|
|
1
1
|
import html
|
|
2
2
|
import json
|
|
3
3
|
import re
|
|
4
|
+
import sys
|
|
4
5
|
from importlib.resources import files
|
|
6
|
+
from types import SimpleNamespace
|
|
5
7
|
|
|
6
8
|
import pytest
|
|
7
9
|
from dash import Dash
|
|
8
10
|
from dash import html as dash_html
|
|
9
|
-
|
|
11
|
+
|
|
12
|
+
from dash_startup_loading_plugin import (
|
|
13
|
+
configure,
|
|
14
|
+
configure_antd,
|
|
15
|
+
configure_mantine,
|
|
16
|
+
get_config,
|
|
17
|
+
reset_config,
|
|
18
|
+
)
|
|
10
19
|
from dash_startup_loading_plugin.plugin import _inject_overlay
|
|
11
20
|
|
|
12
21
|
|
|
@@ -99,6 +108,40 @@ def test_configuration_validation():
|
|
|
99
108
|
assert get_config().required_selectors == ("#react-entry-point",)
|
|
100
109
|
|
|
101
110
|
|
|
111
|
+
def test_mantine_configuration_uses_semantic_theme_defaults(monkeypatch):
|
|
112
|
+
calls = []
|
|
113
|
+
fake_dmc = SimpleNamespace(
|
|
114
|
+
DEFAULT_THEME={
|
|
115
|
+
"white": "#fff",
|
|
116
|
+
"colors": {"dark": [""] * 7 + ["#242424"]},
|
|
117
|
+
},
|
|
118
|
+
pre_render_color_scheme=lambda: calls.append("registered"),
|
|
119
|
+
)
|
|
120
|
+
monkeypatch.setitem(sys.modules, "dash_mantine_components", fake_dmc)
|
|
121
|
+
monkeypatch.setattr(
|
|
122
|
+
"dash_startup_loading_plugin.plugin._mantine_prerender_registered",
|
|
123
|
+
False,
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
config = configure_mantine(minimum_display_ms=200)
|
|
127
|
+
|
|
128
|
+
assert config.background == "#fff"
|
|
129
|
+
assert config.dark_background == "#242424"
|
|
130
|
+
assert config.pending_selector is None
|
|
131
|
+
assert config.minimum_display_ms == 200
|
|
132
|
+
assert calls == ["registered"]
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def test_antd_configuration_matches_library_backgrounds_and_allows_overrides():
|
|
136
|
+
config = configure_antd()
|
|
137
|
+
assert config.background == "#ffffff"
|
|
138
|
+
assert config.dark_background == "#121212"
|
|
139
|
+
|
|
140
|
+
config = configure_antd(background="#f5f5f5", dark_background="#202020")
|
|
141
|
+
assert config.background == "#f5f5f5"
|
|
142
|
+
assert config.dark_background == "#202020"
|
|
143
|
+
|
|
144
|
+
|
|
102
145
|
def test_resolved_startup_theme_controls_overlay_colors():
|
|
103
146
|
css = (
|
|
104
147
|
files("dash_startup_loading_plugin")
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|