django-scroll-to-top 0.1.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.
Files changed (104) hide show
  1. django_scroll_to_top-0.1.0/LICENSE +21 -0
  2. django_scroll_to_top-0.1.0/PKG-INFO +632 -0
  3. django_scroll_to_top-0.1.0/README.md +590 -0
  4. django_scroll_to_top-0.1.0/pyproject.toml +111 -0
  5. django_scroll_to_top-0.1.0/setup.cfg +4 -0
  6. django_scroll_to_top-0.1.0/src/django_scroll_to_top/__init__.py +3 -0
  7. django_scroll_to_top-0.1.0/src/django_scroll_to_top/admin.py +1000 -0
  8. django_scroll_to_top-0.1.0/src/django_scroll_to_top/admin_preview.py +297 -0
  9. django_scroll_to_top-0.1.0/src/django_scroll_to_top/apps.py +13 -0
  10. django_scroll_to_top-0.1.0/src/django_scroll_to_top/checks.py +262 -0
  11. django_scroll_to_top-0.1.0/src/django_scroll_to_top/contrast.py +62 -0
  12. django_scroll_to_top-0.1.0/src/django_scroll_to_top/forms.py +867 -0
  13. django_scroll_to_top-0.1.0/src/django_scroll_to_top/icons/__init__.py +1 -0
  14. django_scroll_to_top-0.1.0/src/django_scroll_to_top/icons/recolor.py +119 -0
  15. django_scroll_to_top-0.1.0/src/django_scroll_to_top/icons/registry.py +244 -0
  16. django_scroll_to_top-0.1.0/src/django_scroll_to_top/icons/sanitizer.py +299 -0
  17. django_scroll_to_top-0.1.0/src/django_scroll_to_top/icons/tabler/LICENSE.tabler.txt +21 -0
  18. django_scroll_to_top-0.1.0/src/django_scroll_to_top/icons/tabler/__init__.py +1 -0
  19. django_scroll_to_top-0.1.0/src/django_scroll_to_top/icons/tabler/filled/arrow-badge-up.svg +13 -0
  20. django_scroll_to_top-0.1.0/src/django_scroll_to_top/icons/tabler/filled/arrow-big-up.svg +13 -0
  21. django_scroll_to_top-0.1.0/src/django_scroll_to_top/icons/tabler/filled/caret-up.svg +13 -0
  22. django_scroll_to_top-0.1.0/src/django_scroll_to_top/icons/tabler/filled/circle-arrow-up.svg +13 -0
  23. django_scroll_to_top-0.1.0/src/django_scroll_to_top/icons/tabler/filled/circle-caret-up.svg +13 -0
  24. django_scroll_to_top-0.1.0/src/django_scroll_to_top/icons/tabler/filled/circle-chevron-up.svg +13 -0
  25. django_scroll_to_top-0.1.0/src/django_scroll_to_top/icons/tabler/filled/square-arrow-up.svg +13 -0
  26. django_scroll_to_top-0.1.0/src/django_scroll_to_top/icons/tabler/filled/square-chevron-up.svg +13 -0
  27. django_scroll_to_top-0.1.0/src/django_scroll_to_top/icons/tabler/filled/square-rounded-arrow-up.svg +13 -0
  28. django_scroll_to_top-0.1.0/src/django_scroll_to_top/icons/tabler/filled/square-rounded-chevron-up.svg +13 -0
  29. django_scroll_to_top-0.1.0/src/django_scroll_to_top/icons/tabler/manifest.json +144 -0
  30. django_scroll_to_top-0.1.0/src/django_scroll_to_top/icons/tabler/outline/arrow-badge-up.svg +19 -0
  31. django_scroll_to_top-0.1.0/src/django_scroll_to_top/icons/tabler/outline/arrow-big-up.svg +19 -0
  32. django_scroll_to_top-0.1.0/src/django_scroll_to_top/icons/tabler/outline/arrow-up.svg +21 -0
  33. django_scroll_to_top-0.1.0/src/django_scroll_to_top/icons/tabler/outline/caret-up.svg +19 -0
  34. django_scroll_to_top-0.1.0/src/django_scroll_to_top/icons/tabler/outline/chevron-up.svg +19 -0
  35. django_scroll_to_top-0.1.0/src/django_scroll_to_top/icons/tabler/outline/circle-arrow-up.svg +22 -0
  36. django_scroll_to_top-0.1.0/src/django_scroll_to_top/icons/tabler/outline/circle-caret-up.svg +20 -0
  37. django_scroll_to_top-0.1.0/src/django_scroll_to_top/icons/tabler/outline/circle-chevron-up.svg +20 -0
  38. django_scroll_to_top-0.1.0/src/django_scroll_to_top/icons/tabler/outline/square-arrow-up.svg +21 -0
  39. django_scroll_to_top-0.1.0/src/django_scroll_to_top/icons/tabler/outline/square-chevron-up.svg +20 -0
  40. django_scroll_to_top-0.1.0/src/django_scroll_to_top/icons/tabler/outline/square-rounded-arrow-up.svg +21 -0
  41. django_scroll_to_top-0.1.0/src/django_scroll_to_top/icons/tabler/outline/square-rounded-chevron-up.svg +20 -0
  42. django_scroll_to_top-0.1.0/src/django_scroll_to_top/locale/ru/LC_MESSAGES/django.mo +0 -0
  43. django_scroll_to_top-0.1.0/src/django_scroll_to_top/locale/ru/LC_MESSAGES/django.po +1517 -0
  44. django_scroll_to_top-0.1.0/src/django_scroll_to_top/management/__init__.py +0 -0
  45. django_scroll_to_top-0.1.0/src/django_scroll_to_top/management/commands/__init__.py +0 -0
  46. django_scroll_to_top-0.1.0/src/django_scroll_to_top/management/commands/scroll_to_top_check_contrast.py +55 -0
  47. django_scroll_to_top-0.1.0/src/django_scroll_to_top/management/commands/scroll_to_top_diagnose.py +58 -0
  48. django_scroll_to_top-0.1.0/src/django_scroll_to_top/migrations/0001_initial.py +171 -0
  49. django_scroll_to_top-0.1.0/src/django_scroll_to_top/migrations/__init__.py +1 -0
  50. django_scroll_to_top-0.1.0/src/django_scroll_to_top/models.py +1074 -0
  51. django_scroll_to_top-0.1.0/src/django_scroll_to_top/presentation.py +117 -0
  52. django_scroll_to_top-0.1.0/src/django_scroll_to_top/py.typed +1 -0
  53. django_scroll_to_top-0.1.0/src/django_scroll_to_top/renderer.py +294 -0
  54. django_scroll_to_top-0.1.0/src/django_scroll_to_top/services.py +143 -0
  55. django_scroll_to_top-0.1.0/src/django_scroll_to_top/settings.py +115 -0
  56. django_scroll_to_top-0.1.0/src/django_scroll_to_top/signals.py +20 -0
  57. django_scroll_to_top-0.1.0/src/django_scroll_to_top/site_config.py +200 -0
  58. django_scroll_to_top-0.1.0/src/django_scroll_to_top/static/django_scroll_to_top/admin-icon-picker.css +719 -0
  59. django_scroll_to_top-0.1.0/src/django_scroll_to_top/static/django_scroll_to_top/admin-icon-picker.js +882 -0
  60. django_scroll_to_top-0.1.0/src/django_scroll_to_top/static/django_scroll_to_top/obstacle-adapter.d.ts +41 -0
  61. django_scroll_to_top-0.1.0/src/django_scroll_to_top/static/django_scroll_to_top/obstacle-adapter.js +190 -0
  62. django_scroll_to_top-0.1.0/src/django_scroll_to_top/static/django_scroll_to_top/obstacle-adapter.min.js +1 -0
  63. django_scroll_to_top-0.1.0/src/django_scroll_to_top/static/django_scroll_to_top/scroll-to-top.css +446 -0
  64. django_scroll_to_top-0.1.0/src/django_scroll_to_top/static/django_scroll_to_top/scroll-to-top.d.ts +70 -0
  65. django_scroll_to_top-0.1.0/src/django_scroll_to_top/static/django_scroll_to_top/scroll-to-top.js +993 -0
  66. django_scroll_to_top-0.1.0/src/django_scroll_to_top/static/django_scroll_to_top/scroll-to-top.min.css +1 -0
  67. django_scroll_to_top-0.1.0/src/django_scroll_to_top/static/django_scroll_to_top/scroll-to-top.min.js +1 -0
  68. django_scroll_to_top-0.1.0/src/django_scroll_to_top/styles.py +139 -0
  69. django_scroll_to_top-0.1.0/src/django_scroll_to_top/templates/admin/base_site.html +11 -0
  70. django_scroll_to_top-0.1.0/src/django_scroll_to_top/templates/admin/django_scroll_to_top/scrolltoprevision/change_form.html +69 -0
  71. django_scroll_to_top-0.1.0/src/django_scroll_to_top/templates/django_scroll_to_top/admin_preview.html +84 -0
  72. django_scroll_to_top-0.1.0/src/django_scroll_to_top/templates/django_scroll_to_top/includes/scroll_to_top_tag.html +1 -0
  73. django_scroll_to_top-0.1.0/src/django_scroll_to_top/templates/django_scroll_to_top/scroll_to_top.html +62 -0
  74. django_scroll_to_top-0.1.0/src/django_scroll_to_top/templatetags/__init__.py +1 -0
  75. django_scroll_to_top-0.1.0/src/django_scroll_to_top/templatetags/scroll_to_top.py +74 -0
  76. django_scroll_to_top-0.1.0/src/django_scroll_to_top/urls.py +11 -0
  77. django_scroll_to_top-0.1.0/src/django_scroll_to_top/views.py +26 -0
  78. django_scroll_to_top-0.1.0/src/django_scroll_to_top.egg-info/PKG-INFO +632 -0
  79. django_scroll_to_top-0.1.0/src/django_scroll_to_top.egg-info/SOURCES.txt +102 -0
  80. django_scroll_to_top-0.1.0/src/django_scroll_to_top.egg-info/dependency_links.txt +1 -0
  81. django_scroll_to_top-0.1.0/src/django_scroll_to_top.egg-info/requires.txt +12 -0
  82. django_scroll_to_top-0.1.0/src/django_scroll_to_top.egg-info/top_level.txt +1 -0
  83. django_scroll_to_top-0.1.0/tests/test_accessibility.py +80 -0
  84. django_scroll_to_top-0.1.0/tests/test_admin_integration.py +492 -0
  85. django_scroll_to_top-0.1.0/tests/test_admin_preview.py +262 -0
  86. django_scroll_to_top-0.1.0/tests/test_app.py +7 -0
  87. django_scroll_to_top-0.1.0/tests/test_base_fields.py +71 -0
  88. django_scroll_to_top-0.1.0/tests/test_collision_contract.py +264 -0
  89. django_scroll_to_top-0.1.0/tests/test_css_contract.py +71 -0
  90. django_scroll_to_top-0.1.0/tests/test_diagnostics.py +118 -0
  91. django_scroll_to_top-0.1.0/tests/test_dismissal.py +197 -0
  92. django_scroll_to_top-0.1.0/tests/test_hooks.py +100 -0
  93. django_scroll_to_top-0.1.0/tests/test_lifecycle.py +145 -0
  94. django_scroll_to_top-0.1.0/tests/test_localization.py +41 -0
  95. django_scroll_to_top-0.1.0/tests/test_obstacle_adapter.py +64 -0
  96. django_scroll_to_top-0.1.0/tests/test_registry.py +96 -0
  97. django_scroll_to_top-0.1.0/tests/test_renderer.py +195 -0
  98. django_scroll_to_top-0.1.0/tests/test_runtime_contract.py +61 -0
  99. django_scroll_to_top-0.1.0/tests/test_security.py +38 -0
  100. django_scroll_to_top-0.1.0/tests/test_site_config.py +303 -0
  101. django_scroll_to_top-0.1.0/tests/test_styling.py +272 -0
  102. django_scroll_to_top-0.1.0/tests/test_svg_sanitizer.py +157 -0
  103. django_scroll_to_top-0.1.0/tests/test_uploaded_icon_admin.py +257 -0
  104. django_scroll_to_top-0.1.0/tests/test_visibility_scroll.py +226 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 kroxiksut
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,632 @@
1
+ Metadata-Version: 2.4
2
+ Name: django-scroll-to-top
3
+ Version: 0.1.0
4
+ Summary: Accessible and configurable scroll-to-top controls for Django sites and Django Admin
5
+ Author-email: kroxiksut <fmalkov91@gmail.com>
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/kroxiksut/django-scroll-to-top
8
+ Project-URL: Source, https://github.com/kroxiksut/django-scroll-to-top
9
+ Project-URL: Documentation, https://github.com/kroxiksut/django-scroll-to-top/tree/main/docs
10
+ Project-URL: Changelog, https://github.com/kroxiksut/django-scroll-to-top/blob/main/CHANGELOG.md
11
+ Project-URL: Issues, https://github.com/kroxiksut/django-scroll-to-top/issues
12
+ Keywords: django,scroll-to-top,back-to-top,scroll,go-to-top,accessibility,a11y,wcag,django-admin,ui,frontend,csp
13
+ Classifier: Development Status :: 4 - Beta
14
+ Classifier: Framework :: Django
15
+ Classifier: Framework :: Django :: 4.2
16
+ Classifier: Framework :: Django :: 5.0
17
+ Classifier: Framework :: Django :: 5.1
18
+ Classifier: Framework :: Django :: 5.2
19
+ Classifier: Framework :: Django :: 6.0
20
+ Classifier: Intended Audience :: Developers
21
+ Classifier: Programming Language :: Python :: 3
22
+ Classifier: Programming Language :: Python :: 3.10
23
+ Classifier: Programming Language :: Python :: 3.11
24
+ Classifier: Programming Language :: Python :: 3.12
25
+ Classifier: Programming Language :: Python :: 3.13
26
+ Classifier: Programming Language :: Python :: 3.14
27
+ Classifier: Typing :: Typed
28
+ Requires-Python: >=3.10
29
+ Description-Content-Type: text/markdown
30
+ License-File: LICENSE
31
+ Requires-Dist: Django<7,>=4.2
32
+ Provides-Extra: test
33
+ Requires-Dist: pytest<9,>=8; extra == "test"
34
+ Requires-Dist: pytest-django<5,>=4.8; extra == "test"
35
+ Provides-Extra: dev
36
+ Requires-Dist: ruff<1,>=0.11; extra == "dev"
37
+ Requires-Dist: pyright<2,>=1.1.390; extra == "dev"
38
+ Requires-Dist: django-stubs<6,>=4.2; extra == "dev"
39
+ Requires-Dist: build<2,>=1.2; extra == "dev"
40
+ Requires-Dist: twine<7,>=5; extra == "dev"
41
+ Dynamic: license-file
42
+
43
+ <p align="center">
44
+ <img src="https://raw.githubusercontent.com/kroxiksut/django-scroll-to-top/main/docs/assets/django-scroll-to-top-logo.png" alt="django-scroll-to-top logo" width="180">
45
+ </p>
46
+
47
+ <h1 align="center">django-scroll-to-top</h1>
48
+
49
+ <p align="center">
50
+ Accessible, configurable scroll-to-top control for Django sites and the Django Admin.
51
+ </p>
52
+
53
+ <p align="center">
54
+ <a href="https://pypi.org/project/django-scroll-to-top/"><img src="https://img.shields.io/pypi/v/django-scroll-to-top.svg" alt="PyPI version"></a>
55
+ <a href="https://pypi.org/project/django-scroll-to-top/"><img src="https://img.shields.io/badge/python-3.10%20%7C%203.11%20%7C%203.12%20%7C%203.13%20%7C%203.14-blue.svg" alt="Python versions"></a>
56
+ <a href="https://www.djangoproject.com/"><img src="https://img.shields.io/badge/Django-4.2%20LTS%20%7C%205.x%20%7C%206.0-092E20.svg?logo=django&logoColor=white" alt="Django versions"></a>
57
+ </p>
58
+
59
+ <p align="center">
60
+ <a href="https://github.com/kroxiksut/django-scroll-to-top/actions/workflows/ci.yml"><img src="https://github.com/kroxiksut/django-scroll-to-top/actions/workflows/ci.yml/badge.svg" alt="CI status"></a>
61
+ <a href="LICENSE"><img src="https://img.shields.io/badge/License-MIT-yellow.svg" alt="License: MIT"></a>
62
+ <a href="https://github.com/astral-sh/ruff"><img src="https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json" alt="Linted with Ruff"></a>
63
+ <a href="https://microsoft.github.io/pyright/"><img src="https://microsoft.github.io/pyright/img/pyright_badge.svg" alt="Checked with pyright"></a>
64
+ </p>
65
+
66
+ <p align="center">
67
+ <b>English</b> · <a href="README.ru.md">Русский</a>
68
+ </p>
69
+
70
+ > **Beta (`0.x`).** The package is feature-complete, tested, and builds clean
71
+ > distributions. While the version stays in the `0.x` series the public API may
72
+ > still change between minor releases before `1.0.0`.
73
+
74
+ `django-scroll-to-top` is a reusable Django application that adds an accessible,
75
+ configurable scroll-to-top control to application pages and the standard Django
76
+ Admin.
77
+
78
+ The developer experience is deliberately small:
79
+
80
+ 1. Install the package and add its Django app.
81
+ 2. Run migrations.
82
+ 3. Add one template tag to the base site template.
83
+ 4. Configure appearance and behavior through Django Admin.
84
+
85
+ No jQuery, external CDN, frontend framework, or mandatory frontend build step is
86
+ required.
87
+
88
+ ## Why This Package
89
+
90
+ A basic scroll-to-top link is easy to write. A reusable production component
91
+ also needs to handle:
92
+
93
+ - separate desktop and mobile presentation;
94
+ - keyboard navigation, focus, contrast, and reduced motion;
95
+ - standard Django Admin integration;
96
+ - safe custom SVG icons;
97
+ - light and dark themes;
98
+ - cookie banners, chats, sticky navigation, and other fixed elements;
99
+ - user dismissal without breaking the primary scroll action;
100
+ - strict Content Security Policy deployments;
101
+ - caching, localization, packaging, and upgrade compatibility.
102
+
103
+ This package provides those behaviors while keeping page-template changes
104
+ minimal.
105
+
106
+ ## Features
107
+
108
+ - One template tag for public site integration.
109
+ - Independent site and standard Django Admin configurations.
110
+ - Database-backed configuration managed through Django Admin.
111
+ - Draft, publish, rollback, and configuration-copy workflows.
112
+ - Explicit desktop values and per-field mobile inheritance/overrides.
113
+ - Top-left, top-right, bottom-left, and bottom-right placement.
114
+ - Circle, square, rounded-square, pill, and icon-with-label templates.
115
+ - Solid, outline, soft, ghost, glass, and controlled gradient variants.
116
+ - Configurable colors, borders, shadows, focus rings, spacing, and sizing.
117
+ - Built-in light/dark presets and theme inheritance.
118
+ - Live desktop/mobile preview using the production renderer.
119
+ - Built-in Tabler starter icons plus developer and administrator icon sources.
120
+ - Safe SVG upload, sanitization, preview, recoloring, and license metadata.
121
+ - Scroll thresholds, short-page detection, fixed-header offsets, and optional
122
+ target selectors.
123
+ - Native smooth scrolling with `prefers-reduced-motion` support.
124
+ - Collision avoidance for banners, launchers, chats, toast containers, sticky
125
+ navigation, and other floating controls.
126
+ - Local/session user dismissal with expiration, reset versions, and restore UX.
127
+ - Django i18n with English canonical strings and Russian as the first bundled
128
+ translation.
129
+
130
+ ## Installation
131
+
132
+ ```console
133
+ python -m pip install django-scroll-to-top
134
+ python manage.py migrate
135
+ ```
136
+
137
+ Add the application and enable the required integration scopes:
138
+
139
+ ```python
140
+ INSTALLED_APPS = [
141
+ # ...
142
+ "django_scroll_to_top",
143
+ ]
144
+
145
+ DJANGO_SCROLL_TO_TOP = {
146
+ "SITE_ENABLED": True,
147
+ "ADMIN_ENABLED": True,
148
+ }
149
+ ```
150
+
151
+ Include the package URLConf so the public template tag can load its versioned
152
+ same-origin stylesheet endpoint in strict-CSP deployments:
153
+
154
+ ```python
155
+ from django.urls import include, path
156
+
157
+ urlpatterns = [
158
+ # ...
159
+ path(
160
+ "scroll-to-top/",
161
+ include(
162
+ ("django_scroll_to_top.urls", "django_scroll_to_top"),
163
+ namespace="django_scroll_to_top",
164
+ ),
165
+ ),
166
+ ]
167
+ ```
168
+
169
+ The package supports and tests the standard Django Admin templates on the
170
+ documented Django compatibility matrix. The integration uses documented Django
171
+ extension points, preserves standard branding, and avoids monkeypatching private
172
+ Django APIs.
173
+
174
+ When `DJANGO_SCROLL_TO_TOP["ADMIN_ENABLED"]` is true, place
175
+ `"django_scroll_to_top"` before `"django.contrib.admin"` in `INSTALLED_APPS`
176
+ so the package's `admin/base_site.html` footer override is selected through the
177
+ normal Django template resolution rules.
178
+
179
+ Custom `AdminSite` instances, overridden base admin templates, and third-party
180
+ admin themes are best-effort integrations and are not yet covered by the
181
+ compatibility test matrix. Projects using them should verify the integration
182
+ locally and report incompatibilities so they can be investigated and fixed
183
+ collaboratively.
184
+
185
+ No separate admin-integration app is required for the current standard-admin
186
+ footer injection. If a future integration app becomes necessary, it should use
187
+ its own app label instead of overloading the main package label.
188
+
189
+ ### Custom AdminSite Recipe
190
+
191
+ If a project overrides `admin/base_site.html` or uses a custom `AdminSite`
192
+ template tree, keep the normal branding blocks and add the package tag in the
193
+ footer block:
194
+
195
+ ```django
196
+ {% extends "admin/base.html" %}
197
+ {% load i18n scroll_to_top %}
198
+
199
+ {% block branding %}
200
+ {{ block.super }}
201
+ {% endblock %}
202
+
203
+ {% block footer %}
204
+ {{ block.super }}
205
+ {% scroll_to_top scope="admin" %}
206
+ {% endblock %}
207
+ ```
208
+
209
+ The default admin policy hides the control on anonymous auth pages such as the
210
+ login and password-reset screens. Set
211
+ `DJANGO_SCROLL_TO_TOP["ADMIN_SHOW_ON_AUTH_PAGES"] = True` to opt into showing it
212
+ there as well.
213
+
214
+ ## Template Usage
215
+
216
+ Add one tag near the end of the shared base template:
217
+
218
+ ```django
219
+ {% load scroll_to_top %}
220
+
221
+ <!-- Page content -->
222
+
223
+ {% scroll_to_top %}
224
+ ```
225
+
226
+ The tag resolves the published site configuration, renders the selected safe
227
+ icon and controlled template variant, and loads package assets. Ordinary visual
228
+ options are not passed as template-tag arguments. Dynamic color and sizing
229
+ variables are delivered through a versioned same-origin stylesheet rather than
230
+ an inline `style` attribute, so the one-tag contract remains intact without
231
+ silently requiring `unsafe-inline`.
232
+
233
+ Projects can override documented package templates through standard Django
234
+ template loaders.
235
+
236
+ ## Configuration Model
237
+
238
+ Django settings own installation and infrastructure choices only. Normal
239
+ appearance and behavior are stored in database-backed configuration and edited
240
+ through Django Admin.
241
+
242
+ Configuration scopes:
243
+
244
+ | Scope | Purpose |
245
+ | --- | --- |
246
+ | `site` | Public and application pages using the template tag |
247
+ | `admin` | Standard Django Admin pages |
248
+
249
+ Django Sites integration is included. When `django.contrib.sites` is installed,
250
+ the current Site may have a Site-specific profile with a global fallback. The
251
+ package also remains usable without the Sites Framework. Site and admin
252
+ configurations remain independent but can be copied explicitly.
253
+
254
+ Desktop values are primary. Each mobile-capable field explicitly inherits its
255
+ desktop value or stores an override. The admin form shows this relationship
256
+ instead of hiding inheritance behind empty values.
257
+
258
+ ### Visibility and Scrolling
259
+
260
+ Each revision configures when the control appears and where it scrolls:
261
+
262
+ - visibility threshold mode (`pixels`, `viewport`, or `combined`) with
263
+ `show_after_px`, `show_after_viewports`, and a `min_document_height_px` floor
264
+ so short pages never show the control;
265
+ - show/hide delays and a visibility direction (`always`, `scroll_up_only`, or
266
+ `hide_on_scroll_down`);
267
+ - a page-level opt-out via a `data-scroll-top="disabled"` attribute on `<body>`;
268
+ - an optional scroll target selector with a vertical offset and an optional
269
+ fixed-header selector whose height is subtracted, falling back to the top of
270
+ the document when empty or not found;
271
+ - `smooth` or `instant` scrolling (reduced-motion users always get an instant
272
+ jump), using native `window.scrollTo` without a custom animation loop.
273
+
274
+ ### Visual Templates and Styling
275
+
276
+ Appearance is built from controlled package CSS classes, never arbitrary
277
+ templates stored in the database:
278
+
279
+ - shapes: `circle`, `square`, `rounded-square`, and `pill`;
280
+ - fill variants: `solid`, `outline`, `soft`, `ghost`, `glass` (translucent with
281
+ a backdrop-blur fallback), and `gradient` (two configured colors and an angle);
282
+ - shadow presets (`none`/`small`/`medium`/`large`), opacity, border width,
283
+ focus-ring width/offset, and a backdrop-blur amount for the glass variant.
284
+
285
+ The six fill variants, shown on the default circle shape:
286
+
287
+ | Fill | Preview | Description |
288
+ | --- | :---: | --- |
289
+ | `solid` | <img src="https://raw.githubusercontent.com/kroxiksut/django-scroll-to-top/main/docs/assets/shared/7.2-fill-solid.png" alt="Solid fill" width="96"> | Opaque background |
290
+ | `outline` | <img src="https://raw.githubusercontent.com/kroxiksut/django-scroll-to-top/main/docs/assets/shared/7.2-fill-outline.png" alt="Outline fill" width="96"> | Border only, no fill |
291
+ | `soft` | <img src="https://raw.githubusercontent.com/kroxiksut/django-scroll-to-top/main/docs/assets/shared/7.2-fill-soft.png" alt="Soft fill" width="96"> | Soft translucent background |
292
+ | `ghost` | <img src="https://raw.githubusercontent.com/kroxiksut/django-scroll-to-top/main/docs/assets/shared/7.2-fill-ghost.png" alt="Ghost fill" width="96"> | Transparent; background appears on hover |
293
+ | `glass` | <img src="https://raw.githubusercontent.com/kroxiksut/django-scroll-to-top/main/docs/assets/shared/7.2-fill-glass.png" alt="Glass fill" width="96"> | Glassy: translucent with a backdrop blur |
294
+ | `gradient` | <img src="https://raw.githubusercontent.com/kroxiksut/django-scroll-to-top/main/docs/assets/shared/7.2-fill-gradient.png" alt="Gradient fill" width="96"> | Gradient between two configured colors |
295
+
296
+ Unknown shapes, fills, or shadows fall back to safe defaults, forced-colors mode
297
+ neutralizes every variant, and projects can still override the package template
298
+ through standard Django template resolution.
299
+
300
+ ## Placement and Floating-Element Collisions
301
+
302
+ Both desktop and mobile modes support all four viewport corners, independent
303
+ offsets, safe-area insets, bounded `z-index`, obstacle spacing, and
304
+ fallback-corner order.
305
+
306
+ Host elements can declare themselves as obstacles:
307
+
308
+ ```html
309
+ <div data-scroll-top-obstacle>
310
+ <!-- Cookie banner, chat launcher, sticky action, and so on -->
311
+ </div>
312
+ ```
313
+
314
+ Administrators may also configure validated CSS selectors. The browser runtime
315
+ measures visible rectangles and applies one of these policies:
316
+
317
+ - ignore obstacles;
318
+ - shift along the selected edge;
319
+ - try configured fallback corners;
320
+ - hide the control when no safe placement exists.
321
+
322
+ Cookie and chat packages remain optional. They do not become dependencies of
323
+ `django-scroll-to-top`.
324
+
325
+ ### Optional Obstacle Adapter
326
+
327
+ For cookie banners and other floating widgets that are not easy to target with a
328
+ single static selector, an optional adapter ships at
329
+ `django_scroll_to_top/obstacle-adapter.js`. It is never loaded by the
330
+ `{% scroll_to_top %}` tag; include it only where you need it:
331
+
332
+ ```html
333
+ <script src="{% static 'django_scroll_to_top/obstacle-adapter.min.js' %}" defer></script>
334
+ <script>
335
+ document.addEventListener("DOMContentLoaded", function () {
336
+ // Generic registration: tag selectors and recalculate on widget events.
337
+ window.djsttObstacleAdapter.register({
338
+ selectors: [".chat-widget", ".sticky-bottom-nav", ".toast-stack"],
339
+ gap: 12,
340
+ priority: 5,
341
+ events: ["my-widget:open", "my-widget:close"]
342
+ });
343
+
344
+ // Or reuse the bundled django-cookies-152fz preset (panel + launcher).
345
+ window.djsttObstacleAdapter.register(
346
+ window.djsttObstacleAdapter.presets.djangoCookies152fz
347
+ );
348
+ });
349
+ </script>
350
+ ```
351
+
352
+ The adapter tags matching markup with `data-scroll-top-obstacle` (including
353
+ elements inserted later, such as a compact launcher that appears after the
354
+ banner closes), and bridges the configured open/close/collapse events to
355
+ `window.djstt.refresh()`. The cookie banner panel and its compact launcher are
356
+ listed as separate selectors so each is measured on its own bounding rectangle
357
+ while visible. Cross-origin iframe contents are never inspected; tagging an
358
+ `<iframe>` makes the engine treat the iframe rectangle itself as an obstacle.
359
+
360
+ Collision avoidance and theme-aware enhancements are progressive enhancement
361
+ behaviors. The no-JavaScript baseline remains a plain top-of-document link in
362
+ the configured corner with the conservative built-in preset.
363
+
364
+ ## Icons
365
+
366
+ The unified icon catalog has three sources:
367
+
368
+ - `builtin`: vendored Tabler starter icons;
369
+ - `developer`: icons registered by trusted project code;
370
+ - `uploaded`: SVG files uploaded and approved through Django Admin.
371
+
372
+ Built-in examples include suitable arrow, chevron, caret, circle, badge,
373
+ bar, and square variants from [Tabler Icons](https://tabler.io/icons).
374
+
375
+ ### Icon Color Requirements
376
+
377
+ The button reads its colors from the database configuration. For an icon to pick
378
+ up the configured color, its SVG must paint with `currentColor` rather than a
379
+ hard-coded color — that is, use `fill="currentColor"` (filled icons) or
380
+ `stroke="currentColor"` (outline icons). The control then applies, in priority
381
+ order:
382
+
383
+ 1. **Icon color override** (`icon_color` / `dark_icon_color`) when set;
384
+ 2. otherwise the **foreground color** (`foreground_color`), which is also the
385
+ label color.
386
+
387
+ Multicolor/original uploaded icons keep their own colors and ignore these
388
+ fields (they do not use `currentColor`). Built-in Tabler icons already use
389
+ `currentColor`, so they recolor automatically.
390
+
391
+ Tabler Icons are distributed under the
392
+ [MIT License](https://github.com/tabler/tabler-icons/blob/main/LICENSE). That
393
+ license permits broad use, modification, and redistribution, including
394
+ commercial use, while requiring preservation of its copyright and license
395
+ notice. Release artifacts include the required notice and source/version
396
+ metadata.
397
+
398
+ ### Uploaded SVG Safety
399
+
400
+ Administrator-uploaded SVG is not rendered directly. The pipeline:
401
+
402
+ - parses SVG as XML;
403
+ - rejects DTD, entities, scripts, event handlers, external resources, embedded
404
+ documents, unsafe namespaces, and excessive complexity;
405
+ - allows only documented graphical elements and attributes;
406
+ - normalizes geometry and `viewBox` data;
407
+ - stores and renders only a sanitized payload;
408
+ - supports `currentColor` recoloring for compatible icons;
409
+ - preserves safe original colors only in an explicit multicolor mode.
410
+
411
+ Technical sanitization does not grant a right to use an icon. Uploaded icons
412
+ carry author, source, license, copyright, and attribution metadata, plus an
413
+ administrator confirmation that the project may use and distribute the file.
414
+
415
+ The package does not treat an uploaded SVG as free or open content merely
416
+ because the file was accepted by the sanitizer. The site operator remains
417
+ responsible for confirming usage and redistribution rights, keeping attribution
418
+ data accurate, and exporting icon-attribution records when the deployment needs
419
+ them.
420
+
421
+ ## Themes and Colors
422
+
423
+ Administrator-configured colors are delivered through a versioned same-origin
424
+ stylesheet generated by the package, without storing arbitrary CSS or requiring
425
+ `unsafe-inline`. The stylesheet uses the same resolved Site/profile
426
+ configuration as the component renderer and remains effective without
427
+ JavaScript.
428
+
429
+ Manual and inherited color modes are supported. Standard Django Admin
430
+ configuration can inherit supported admin theme variables and react to
431
+ light/dark changes with safe fallbacks. The built-in `inherit_admin_theme`
432
+ mode prefers publicly observable Django Admin variables, falls back to a neutral
433
+ preset when they are absent, and also exposes namespaced `--dstt-admin-*`
434
+ adapter hooks for third-party admin themes.
435
+
436
+ Colors are otherwise configured manually per revision. (The earlier
437
+ browser-based page color analysis workflow was removed as unnecessary.)
438
+
439
+ ## User Dismissal
440
+
441
+ Persistent user dismissal is separate from temporary runtime visibility and from
442
+ the administrative enable flag. Each revision configures dismissal:
443
+
444
+ - `allow_user_dismissal` renders a visible close control;
445
+ - the storage mechanism is `local`, `session`, a functional `cookie`, or `none`
446
+ (in-memory only). The default `local` mode stores nothing until the visitor
447
+ actually dismisses the control;
448
+ - the duration is either `persistent` (kept until the revision's configuration
449
+ token or `dismissal_version` changes) or day-based via `dismissal_days`, which
450
+ self-clears once it lapses;
451
+ - `dismissal_requires_confirmation` asks the visitor to confirm before hiding;
452
+ - `dismissal_version` is an explicit knob to intentionally re-show the control.
453
+
454
+ Storage keys are namespaced by scope, site, configuration token, and dismissal
455
+ version, and all storage access tolerates denied or unavailable storage without
456
+ breaking the scroll action. The control can be restored programmatically with
457
+ `window.djstt.restore()`. Triple-click dismissal remains experimental because
458
+ the first normal click starts scrolling and may hide the control immediately; it
459
+ is not enabled by default.
460
+
461
+ ## Accessibility
462
+
463
+ The component targets WCAG 2.2 AA within its scope:
464
+
465
+ - accessible name independent of icon or tooltip;
466
+ - keyboard operation and visible focus;
467
+ - appropriate pointer target size;
468
+ - contrast validation for normal, hover, active, and focus states;
469
+ - `prefers-reduced-motion` support;
470
+ - forced-colors/high-contrast behavior;
471
+ - zoom and RTL support;
472
+ - no keyboard trap or unexpected focus movement;
473
+ - decorative SVG hidden from accessibility APIs.
474
+
475
+ The structural contract above is implemented and covered by tests: a translatable
476
+ accessible name on both the control and the close control, a minimum 24x24 CSS px
477
+ target floor, a no-JavaScript link fallback, no positive tabindex, visible
478
+ focus-visible outlines, forced-colors and reduced-motion handling, and RTL-safe
479
+ logical properties. A full WCAG 2.2 AA audit and zoom (200%/400%) verification in
480
+ real browsers are tracked as later stabilization steps (see [Roadmap](#roadmap)).
481
+
482
+ ## Security, Privacy, and CSP
483
+
484
+ - No arbitrary HTML, JavaScript, or CSS is stored through admin forms.
485
+ - Production never renders an unsanitized original SVG upload.
486
+ - No telemetry or external network calls are enabled by default.
487
+ - Strict CSP support does not silently require `unsafe-inline`.
488
+ - Browser storage failures degrade safely.
489
+ - Cookie-based or authenticated database dismissal is optional and documented
490
+ separately if enabled.
491
+
492
+ See [SECURITY.md](SECURITY.md) for the supported versions and how to report a
493
+ vulnerability.
494
+
495
+ ### Minimal CSP Configuration
496
+
497
+ The default `DJANGO_SCROLL_TO_TOP["CSP_MODE"]` is `"external"`. In that mode,
498
+ the component uses same-origin `<link>` and `<script>` tags and works with a
499
+ policy such as:
500
+
501
+ ```text
502
+ Content-Security-Policy:
503
+ default-src 'self';
504
+ style-src 'self';
505
+ script-src 'self';
506
+ ```
507
+
508
+ If the host project uses nonce-based script delivery, set
509
+ `DJANGO_SCROLL_TO_TOP["CSP_MODE"] = "nonce"` and provide `csp_nonce` in the
510
+ template context or `request.csp_nonce`. The package adds that nonce to its
511
+ external script tag while keeping style delivery on the same-origin stylesheet
512
+ endpoint.
513
+
514
+ ## Runtime Events
515
+
516
+ The browser runtime uses one documented global entrypoint, `window.djstt`, with
517
+ `init(root?)`, `refresh(root?)`, and `destroy(root?)` helpers for progressive
518
+ enhancement and partial-navigation integrations.
519
+
520
+ The runtime dispatches these namespaced DOM events from each component root:
521
+
522
+ - `djstt:show`
523
+ - `djstt:hide`
524
+ - `djstt:scroll-start`
525
+ - `djstt:scroll-end`
526
+ - `djstt:dismiss`
527
+
528
+ HTMX-like fragment replacement may call `window.djstt.init(fragmentRoot)`. Turbo
529
+ and similar full-page navigation layers may call `window.djstt.refresh()`.
530
+
531
+ ## Extension Points
532
+
533
+ Optional hooks are configured in `DJANGO_SCROLL_TO_TOP` as a dotted path or a
534
+ callable, and a faulty hook never breaks rendering:
535
+
536
+ - `SITE_ID_RESOLVER(request) -> int | None` — resolve the current Site id without
537
+ depending on `django.contrib.sites`;
538
+ - `PROFILE_RESOLVER(scope, site_id) -> ScrollTopProfile | None` — override
539
+ profile/revision selection; return `None` to fall back to built-in resolution;
540
+ - `OBSTACLE_SELECTORS() -> list[str]` — merge extra obstacle selectors into every
541
+ rendered control.
542
+
543
+ Developer icons are registered through `register_developer_icon(...)`. The
544
+ stable public surface is `window.djstt` (`init`/`refresh`/`destroy`, `version`,
545
+ `dismiss`/`restore`/`debug`), the namespaced `djstt:*` DOM events, the
546
+ `data-dstt-*` attributes on the control wrapper, the `data-scroll-top-obstacle`
547
+ marker, and the `django_scroll_to_top/scroll_to_top.html` template. Internal
548
+ service and model APIs are not part of the public contract.
549
+
550
+ ## Diagnostics
551
+
552
+ System checks report common misconfigurations with stable ids and actionable
553
+ hints:
554
+
555
+ - `dstt.W001`/`W002`: migrations cannot be inspected, or are unapplied.
556
+ - `dstt.W003`: unsupported `CSP_MODE`.
557
+ - `dstt.W004`: package URLConf is missing while site rendering is enabled.
558
+ - `dstt.W005`: `django_scroll_to_top` is ordered after `django.contrib.admin`.
559
+ - `dstt.W006`: unsupported `DEFAULT_COLLISION_POLICY`.
560
+ - `dstt.W007`: `DJANGO_SCROLL_TO_TOP` is not a dict or has unknown keys.
561
+ - `dstt.W008`: `ADMIN_ENABLED` is set but `django.contrib.admin` is not installed.
562
+ - `dstt.W009`: `SITES_FRAMEWORK_ENABLED` is set but `django.contrib.sites` is not
563
+ installed.
564
+ - `dstt.W010`: packaged templates or minified static assets are missing.
565
+
566
+ Two management commands help diagnose a deployment:
567
+
568
+ ```console
569
+ python manage.py scroll_to_top_diagnose # resolved config per scope, no secrets
570
+ python manage.py scroll_to_top_check_contrast # non-zero exit if a published revision fails
571
+ ```
572
+
573
+ ## Asset Build
574
+
575
+ Release assets are minified reproducibly with:
576
+
577
+ ```console
578
+ python tools/minify_assets.py
579
+ ```
580
+
581
+ ## Compatibility
582
+
583
+ The support matrix covers Django 4.2 LTS, 5.x, and 6.x with the Python versions
584
+ supported by each selected Django release. Django 4 support is scoped to 4.2 LTS
585
+ rather than the entire 4.x line. The matrix is defined in `pyproject.toml` and
586
+ verified in CI (`tox.ini` mirrors it).
587
+
588
+ The base runtime dependency set is limited to Django.
589
+
590
+ ## Feedback
591
+
592
+ This is an early (`0.x` beta) release, and real-world reports are especially
593
+ valuable. Bug reports and feedback are welcome via
594
+ [GitHub issues](https://github.com/kroxiksut/django-scroll-to-top/issues),
595
+ particularly on:
596
+
597
+ - **Admin integration** — the package is tested against the standard Django
598
+ Admin templates. Custom `AdminSite` instances, overridden base admin
599
+ templates, and third-party admin themes are best-effort and not yet in the
600
+ compatibility test matrix; please report what works and what does not.
601
+ - **Frontend behavior** — collision avoidance with real cookie banners, chat
602
+ launchers, sticky navigation, and toast stacks; placement across viewport
603
+ sizes and safe-area insets; behavior under strict CSP; theme inheritance with
604
+ custom admin themes; and partial-navigation layers such as HTMX and Turbo.
605
+ - **Accessibility in real browsers** — keyboard, focus, contrast, forced-colors,
606
+ reduced motion, RTL, and zoom (200%/400%); see the [Roadmap](#roadmap) for the
607
+ audits still in progress.
608
+
609
+ See [CONTRIBUTING.md](CONTRIBUTING.md) for how to file an effective report.
610
+
611
+ ## Roadmap
612
+
613
+ Tracked for after the initial release:
614
+
615
+ - Full WCAG 2.2 AA audit and zoom (200%/400%) verification in real browsers.
616
+ - Optional authenticated database-backed dismissal endpoint.
617
+
618
+ ## Project Documentation
619
+
620
+ - [Changelog](CHANGELOG.md)
621
+ - [Architecture](ARCHITECTURE.md)
622
+ - [Contributor guide](CONTRIBUTING.md)
623
+ - [Security policy](SECURITY.md)
624
+ - [Code of conduct](CODE_OF_CONDUCT.md)
625
+ - [Repository structure](STRUCTURE.md)
626
+
627
+ ## License
628
+
629
+ The package is licensed under MIT. See [LICENSE](LICENSE).
630
+
631
+ Third-party assets retain their own licenses. The vendored Tabler subset and
632
+ its upstream MIT notice are documented in [THIRD_PARTY_LICENSES.md](THIRD_PARTY_LICENSES.md).