datasette-sticky-table-headers 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.
@@ -0,0 +1,13 @@
1
+ Copyright 2026 Julius Welby
2
+
3
+ Licensed under the Apache License, Version 2.0 (the "License");
4
+ you may not use this file except in compliance with the License.
5
+ You may obtain a copy of the License at
6
+
7
+ http://www.apache.org/licenses/LICENSE-2.0
8
+
9
+ Unless required by applicable law or agreed to in writing, software
10
+ distributed under the License is distributed on an "AS IS" BASIS,
11
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ See the License for the specific language governing permissions and
13
+ limitations under the License.
@@ -0,0 +1 @@
1
+ include docs/screenshot.png
@@ -0,0 +1,97 @@
1
+ Metadata-Version: 2.4
2
+ Name: datasette-sticky-table-headers
3
+ Version: 0.1.0
4
+ Summary: Datasette plugin that keeps table headers visible while scrolling
5
+ Author: Julius Welby
6
+ License-Expression: Apache-2.0
7
+ Project-URL: Homepage, https://github.com/jwelby/datasette-sticky-table-headers
8
+ Project-URL: Issues, https://github.com/jwelby/datasette-sticky-table-headers/issues
9
+ Project-URL: Source, https://github.com/jwelby/datasette-sticky-table-headers
10
+ Keywords: datasette,datasette-plugin,sticky-headers,fixed-headers,frozen-headers,freeze-top-row,tables
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Framework :: Datasette
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3 :: Only
16
+ Classifier: Programming Language :: Python :: 3.9
17
+ Classifier: Programming Language :: Python :: 3.10
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Programming Language :: Python :: 3.13
21
+ Classifier: Topic :: Database :: Front-Ends
22
+ Requires-Python: >=3.9
23
+ Description-Content-Type: text/markdown
24
+ License-File: LICENSE
25
+ Requires-Dist: datasette
26
+ Dynamic: license-file
27
+
28
+ # datasette-sticky-table-headers
29
+
30
+ Keep Datasette table headers visible while scrolling.
31
+
32
+ This plugin is especially useful when exploring wide or unfamiliar datasets:
33
+ column names stay in view while you scroll down table listings, database table
34
+ previews, and SQL query results.
35
+
36
+ If you know this feature from spreadsheets, it is similar to "freeze top row"
37
+ or "freeze header row": column names remain visible while you scroll through
38
+ long or wide tables.
39
+
40
+ ## Installation
41
+
42
+ Install the plugin in the same environment as Datasette:
43
+
44
+ ```bash
45
+ datasette install datasette-sticky-table-headers
46
+ ```
47
+
48
+ For local development from this checkout:
49
+
50
+ ```bash
51
+ pip install -e . --no-build-isolation
52
+ ```
53
+
54
+ ## Usage
55
+
56
+ Run Datasette as normal. The plugin is enabled automatically:
57
+
58
+ ```bash
59
+ datasette data.db
60
+ ```
61
+
62
+ It injects a small script on Datasette table, database, and SQL query pages.
63
+ That script adds CSS targeting `thead` header cells in Datasette result tables,
64
+ so the header row sticks to the top of the browser viewport while the page
65
+ scrolls.
66
+
67
+ ```css
68
+ .rows-and-columns thead th {
69
+ position: sticky;
70
+ top: 0;
71
+ }
72
+ ```
73
+
74
+ The plugin also makes Datasette's table wrappers use normal page scrolling
75
+ rather than a separate vertical scroll box, which lets sticky headers pin to the
76
+ browser viewport.
77
+
78
+ ## Screenshots
79
+
80
+ ![Datasette table page with sticky headers visible while scrolling](docs/screenshot.png)
81
+
82
+ The screenshot shows a Datasette table page scrolled down with the column
83
+ headers still visible at the top of the page.
84
+
85
+ ## Compatibility and limitations
86
+
87
+ Tested with Datasette 0.65.2. The plugin uses Datasette's `extra_body_script`
88
+ hook and standard Datasette table markup, so it should also work with nearby
89
+ Datasette versions that render result tables with `thead` elements and the
90
+ `rows-and-columns` class.
91
+
92
+ The CSS intentionally targets only `thead` cells. It does not use a
93
+ `tr:first-child` fallback, because that can accidentally make the first data row
94
+ sticky on result tables without a header section.
95
+
96
+ The header background is white. Datasette themes or custom CSS with non-white
97
+ table backgrounds may need a small override.
@@ -0,0 +1,70 @@
1
+ # datasette-sticky-table-headers
2
+
3
+ Keep Datasette table headers visible while scrolling.
4
+
5
+ This plugin is especially useful when exploring wide or unfamiliar datasets:
6
+ column names stay in view while you scroll down table listings, database table
7
+ previews, and SQL query results.
8
+
9
+ If you know this feature from spreadsheets, it is similar to "freeze top row"
10
+ or "freeze header row": column names remain visible while you scroll through
11
+ long or wide tables.
12
+
13
+ ## Installation
14
+
15
+ Install the plugin in the same environment as Datasette:
16
+
17
+ ```bash
18
+ datasette install datasette-sticky-table-headers
19
+ ```
20
+
21
+ For local development from this checkout:
22
+
23
+ ```bash
24
+ pip install -e . --no-build-isolation
25
+ ```
26
+
27
+ ## Usage
28
+
29
+ Run Datasette as normal. The plugin is enabled automatically:
30
+
31
+ ```bash
32
+ datasette data.db
33
+ ```
34
+
35
+ It injects a small script on Datasette table, database, and SQL query pages.
36
+ That script adds CSS targeting `thead` header cells in Datasette result tables,
37
+ so the header row sticks to the top of the browser viewport while the page
38
+ scrolls.
39
+
40
+ ```css
41
+ .rows-and-columns thead th {
42
+ position: sticky;
43
+ top: 0;
44
+ }
45
+ ```
46
+
47
+ The plugin also makes Datasette's table wrappers use normal page scrolling
48
+ rather than a separate vertical scroll box, which lets sticky headers pin to the
49
+ browser viewport.
50
+
51
+ ## Screenshots
52
+
53
+ ![Datasette table page with sticky headers visible while scrolling](docs/screenshot.png)
54
+
55
+ The screenshot shows a Datasette table page scrolled down with the column
56
+ headers still visible at the top of the page.
57
+
58
+ ## Compatibility and limitations
59
+
60
+ Tested with Datasette 0.65.2. The plugin uses Datasette's `extra_body_script`
61
+ hook and standard Datasette table markup, so it should also work with nearby
62
+ Datasette versions that render result tables with `thead` elements and the
63
+ `rows-and-columns` class.
64
+
65
+ The CSS intentionally targets only `thead` cells. It does not use a
66
+ `tr:first-child` fallback, because that can accidentally make the first data row
67
+ sticky on result tables without a header section.
68
+
69
+ The header background is white. Datasette themes or custom CSS with non-white
70
+ table backgrounds may need a small override.
@@ -0,0 +1,97 @@
1
+ Metadata-Version: 2.4
2
+ Name: datasette-sticky-table-headers
3
+ Version: 0.1.0
4
+ Summary: Datasette plugin that keeps table headers visible while scrolling
5
+ Author: Julius Welby
6
+ License-Expression: Apache-2.0
7
+ Project-URL: Homepage, https://github.com/jwelby/datasette-sticky-table-headers
8
+ Project-URL: Issues, https://github.com/jwelby/datasette-sticky-table-headers/issues
9
+ Project-URL: Source, https://github.com/jwelby/datasette-sticky-table-headers
10
+ Keywords: datasette,datasette-plugin,sticky-headers,fixed-headers,frozen-headers,freeze-top-row,tables
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Framework :: Datasette
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3 :: Only
16
+ Classifier: Programming Language :: Python :: 3.9
17
+ Classifier: Programming Language :: Python :: 3.10
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Programming Language :: Python :: 3.13
21
+ Classifier: Topic :: Database :: Front-Ends
22
+ Requires-Python: >=3.9
23
+ Description-Content-Type: text/markdown
24
+ License-File: LICENSE
25
+ Requires-Dist: datasette
26
+ Dynamic: license-file
27
+
28
+ # datasette-sticky-table-headers
29
+
30
+ Keep Datasette table headers visible while scrolling.
31
+
32
+ This plugin is especially useful when exploring wide or unfamiliar datasets:
33
+ column names stay in view while you scroll down table listings, database table
34
+ previews, and SQL query results.
35
+
36
+ If you know this feature from spreadsheets, it is similar to "freeze top row"
37
+ or "freeze header row": column names remain visible while you scroll through
38
+ long or wide tables.
39
+
40
+ ## Installation
41
+
42
+ Install the plugin in the same environment as Datasette:
43
+
44
+ ```bash
45
+ datasette install datasette-sticky-table-headers
46
+ ```
47
+
48
+ For local development from this checkout:
49
+
50
+ ```bash
51
+ pip install -e . --no-build-isolation
52
+ ```
53
+
54
+ ## Usage
55
+
56
+ Run Datasette as normal. The plugin is enabled automatically:
57
+
58
+ ```bash
59
+ datasette data.db
60
+ ```
61
+
62
+ It injects a small script on Datasette table, database, and SQL query pages.
63
+ That script adds CSS targeting `thead` header cells in Datasette result tables,
64
+ so the header row sticks to the top of the browser viewport while the page
65
+ scrolls.
66
+
67
+ ```css
68
+ .rows-and-columns thead th {
69
+ position: sticky;
70
+ top: 0;
71
+ }
72
+ ```
73
+
74
+ The plugin also makes Datasette's table wrappers use normal page scrolling
75
+ rather than a separate vertical scroll box, which lets sticky headers pin to the
76
+ browser viewport.
77
+
78
+ ## Screenshots
79
+
80
+ ![Datasette table page with sticky headers visible while scrolling](docs/screenshot.png)
81
+
82
+ The screenshot shows a Datasette table page scrolled down with the column
83
+ headers still visible at the top of the page.
84
+
85
+ ## Compatibility and limitations
86
+
87
+ Tested with Datasette 0.65.2. The plugin uses Datasette's `extra_body_script`
88
+ hook and standard Datasette table markup, so it should also work with nearby
89
+ Datasette versions that render result tables with `thead` elements and the
90
+ `rows-and-columns` class.
91
+
92
+ The CSS intentionally targets only `thead` cells. It does not use a
93
+ `tr:first-child` fallback, because that can accidentally make the first data row
94
+ sticky on result tables without a header section.
95
+
96
+ The header background is white. Datasette themes or custom CSS with non-white
97
+ table backgrounds may need a small override.
@@ -0,0 +1,13 @@
1
+ LICENSE
2
+ MANIFEST.in
3
+ README.md
4
+ datasette_sticky_table_headers.py
5
+ pyproject.toml
6
+ datasette_sticky_table_headers.egg-info/PKG-INFO
7
+ datasette_sticky_table_headers.egg-info/SOURCES.txt
8
+ datasette_sticky_table_headers.egg-info/dependency_links.txt
9
+ datasette_sticky_table_headers.egg-info/entry_points.txt
10
+ datasette_sticky_table_headers.egg-info/requires.txt
11
+ datasette_sticky_table_headers.egg-info/top_level.txt
12
+ docs/screenshot.png
13
+ tests/test_datasette_sticky_table_headers.py
@@ -0,0 +1,2 @@
1
+ [datasette]
2
+ sticky_table_headers = datasette_sticky_table_headers
@@ -0,0 +1,37 @@
1
+ from datasette import hookimpl
2
+
3
+
4
+ SCRIPT = """
5
+ (() => {
6
+ if (document.getElementById("datasette-sticky-table-headers-style")) {
7
+ return;
8
+ }
9
+ const style = document.createElement("style");
10
+ style.id = "datasette-sticky-table-headers-style";
11
+ style.textContent = `
12
+ .table-wrapper,
13
+ .table-wrapper-overflow,
14
+ .table-wrapper-scroll {
15
+ overflow: visible;
16
+ }
17
+
18
+ .rows-and-columns thead th,
19
+ table.rows-and-columns thead th,
20
+ table.rows-and-columns thead td {
21
+ position: sticky;
22
+ top: 0;
23
+ z-index: 5;
24
+ background: #fff;
25
+ box-shadow: 0 1px 0 rgba(0, 0, 0, 0.12);
26
+ }
27
+ `;
28
+ document.head.appendChild(style);
29
+ })();
30
+ """
31
+
32
+
33
+ @hookimpl
34
+ def extra_body_script(view_name):
35
+ if view_name not in {"table", "query", "database"}:
36
+ return None
37
+ return SCRIPT
@@ -0,0 +1,49 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "datasette-sticky-table-headers"
7
+ version = "0.1.0"
8
+ description = "Datasette plugin that keeps table headers visible while scrolling"
9
+ readme = "README.md"
10
+ authors = [{name = "Julius Welby"}]
11
+ license = "Apache-2.0"
12
+ license-files = ["LICENSE"]
13
+ keywords = [
14
+ "datasette",
15
+ "datasette-plugin",
16
+ "sticky-headers",
17
+ "fixed-headers",
18
+ "frozen-headers",
19
+ "freeze-top-row",
20
+ "tables",
21
+ ]
22
+ requires-python = ">=3.9"
23
+ dependencies = [
24
+ "datasette",
25
+ ]
26
+ classifiers = [
27
+ "Development Status :: 4 - Beta",
28
+ "Framework :: Datasette",
29
+ "Intended Audience :: Developers",
30
+ "Programming Language :: Python :: 3",
31
+ "Programming Language :: Python :: 3 :: Only",
32
+ "Programming Language :: Python :: 3.9",
33
+ "Programming Language :: Python :: 3.10",
34
+ "Programming Language :: Python :: 3.11",
35
+ "Programming Language :: Python :: 3.12",
36
+ "Programming Language :: Python :: 3.13",
37
+ "Topic :: Database :: Front-Ends",
38
+ ]
39
+
40
+ [project.urls]
41
+ Homepage = "https://github.com/jwelby/datasette-sticky-table-headers"
42
+ Issues = "https://github.com/jwelby/datasette-sticky-table-headers/issues"
43
+ Source = "https://github.com/jwelby/datasette-sticky-table-headers"
44
+
45
+ [project.entry-points.datasette]
46
+ sticky_table_headers = "datasette_sticky_table_headers"
47
+
48
+ [tool.setuptools]
49
+ py-modules = ["datasette_sticky_table_headers"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,87 @@
1
+ import unittest
2
+ import sqlite3
3
+ import tempfile
4
+ from pathlib import Path
5
+
6
+ from datasette.app import Datasette
7
+ from datasette import plugins
8
+
9
+ import datasette_sticky_table_headers
10
+ from datasette_sticky_table_headers import SCRIPT, extra_body_script
11
+
12
+
13
+ class StickyTableHeadersTests(unittest.TestCase):
14
+ def test_script_contains_sticky_css(self):
15
+ self.assertIn("position: sticky", SCRIPT)
16
+ self.assertIn(".rows-and-columns thead th", SCRIPT)
17
+ self.assertNotIn("tr:first-child", SCRIPT)
18
+ self.assertIn("datasette-sticky-table-headers-style", SCRIPT)
19
+
20
+ def test_enabled_on_table_and_query_pages(self):
21
+ self.assertEqual(SCRIPT, extra_body_script("table"))
22
+ self.assertEqual(SCRIPT, extra_body_script("query"))
23
+ self.assertEqual(SCRIPT, extra_body_script("database"))
24
+
25
+ def test_disabled_elsewhere(self):
26
+ self.assertIsNone(extra_body_script("index"))
27
+
28
+
29
+ class DatasetteRenderTests(unittest.IsolatedAsyncioTestCase):
30
+ plugin_name = "datasette-sticky-table-headers-test"
31
+
32
+ async def asyncSetUp(self):
33
+ self.registered_plugin = not plugins.pm.is_registered(
34
+ datasette_sticky_table_headers
35
+ )
36
+ if self.registered_plugin:
37
+ plugins.pm.register(
38
+ datasette_sticky_table_headers,
39
+ name=self.plugin_name,
40
+ )
41
+
42
+ self.tempdir = tempfile.TemporaryDirectory()
43
+ self.db_path = Path(self.tempdir.name) / "fixtures.db"
44
+ connection = sqlite3.connect(self.db_path)
45
+ connection.execute(
46
+ "create table demo (id integer primary key, name text, notes text)"
47
+ )
48
+ connection.execute(
49
+ "insert into demo (name, notes) values (?, ?)",
50
+ ("Alice", "wide table header test"),
51
+ )
52
+ connection.commit()
53
+ connection.close()
54
+
55
+ self.ds = Datasette([str(self.db_path)])
56
+
57
+ async def asyncTearDown(self):
58
+ if self.registered_plugin:
59
+ plugins.pm.unregister(name=self.plugin_name)
60
+ for database in self.ds.databases.values():
61
+ database.close()
62
+ self.tempdir.cleanup()
63
+
64
+ async def assert_script_injected(self, path):
65
+ response = await self.ds.client.get(path)
66
+ self.assertEqual(200, response.status_code)
67
+ self.assertIn("datasette-sticky-table-headers-style", response.text)
68
+ self.assertIn(".rows-and-columns thead th", response.text)
69
+ self.assertNotIn("tr:first-child", response.text)
70
+
71
+ async def test_script_injected_on_table_page(self):
72
+ await self.assert_script_injected("/fixtures/demo")
73
+
74
+ async def test_script_injected_on_database_page(self):
75
+ await self.assert_script_injected("/fixtures")
76
+
77
+ async def test_script_injected_on_sql_query_page(self):
78
+ await self.assert_script_injected("/fixtures?sql=select+*+from+demo")
79
+
80
+ async def test_script_not_injected_on_index_page(self):
81
+ response = await self.ds.client.get("/")
82
+ self.assertEqual(200, response.status_code)
83
+ self.assertNotIn("datasette-sticky-table-headers-style", response.text)
84
+
85
+
86
+ if __name__ == "__main__":
87
+ unittest.main()