sphinx-filter-tabs 1.2.5__py3-none-any.whl → 1.2.6__py3-none-any.whl
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.
- filter_tabs/extension.py +22 -7
- filter_tabs/renderer.py +19 -9
- filter_tabs/static/filter_tabs.css +141 -27
- sphinx_filter_tabs-1.2.6.dist-info/METADATA +267 -0
- sphinx_filter_tabs-1.2.6.dist-info/RECORD +10 -0
- filter_tabs/static/filter_tabs.js +0 -101
- sphinx_filter_tabs-1.2.5.dist-info/METADATA +0 -70
- sphinx_filter_tabs-1.2.5.dist-info/RECORD +0 -11
- {sphinx_filter_tabs-1.2.5.dist-info → sphinx_filter_tabs-1.2.6.dist-info}/WHEEL +0 -0
- {sphinx_filter_tabs-1.2.5.dist-info → sphinx_filter_tabs-1.2.6.dist-info}/entry_points.txt +0 -0
- {sphinx_filter_tabs-1.2.5.dist-info → sphinx_filter_tabs-1.2.6.dist-info}/licenses/LICENSE +0 -0
- {sphinx_filter_tabs-1.2.5.dist-info → sphinx_filter_tabs-1.2.6.dist-info}/top_level.txt +0 -0
filter_tabs/extension.py
CHANGED
|
@@ -419,12 +419,31 @@ def depart_summary_node(self: HTML5Translator, node: SummaryNode) -> None:
|
|
|
419
419
|
self.body.append('</summary>')
|
|
420
420
|
|
|
421
421
|
|
|
422
|
+
# =============================================================================
|
|
423
|
+
# Use ARIA to Improve Strong Element Behavior
|
|
424
|
+
# =============================================================================
|
|
425
|
+
|
|
426
|
+
def improve_inline_formatting(app: Sphinx, doctree: nodes.document, docname: str):
|
|
427
|
+
"""Improve screen reader handling of inline formatting."""
|
|
428
|
+
if app.builder.name != 'html':
|
|
429
|
+
return
|
|
430
|
+
|
|
431
|
+
# Find all strong/emphasis nodes and add ARIA attributes
|
|
432
|
+
for node in doctree.findall(nodes.strong):
|
|
433
|
+
# Add aria-label to make the content read as one unit
|
|
434
|
+
text_content = node.astext()
|
|
435
|
+
node['aria-label'] = text_content
|
|
436
|
+
|
|
437
|
+
for node in doctree.findall(nodes.emphasis):
|
|
438
|
+
text_content = node.astext()
|
|
439
|
+
node['aria-label'] = text_content
|
|
440
|
+
|
|
422
441
|
# =============================================================================
|
|
423
442
|
# Static File Handling
|
|
424
443
|
# =============================================================================
|
|
425
444
|
|
|
426
445
|
def copy_static_files(app: Sphinx):
|
|
427
|
-
"""Copy CSS
|
|
446
|
+
"""Copy CSS file to the build directory."""
|
|
428
447
|
if app.builder.name != 'html':
|
|
429
448
|
return
|
|
430
449
|
|
|
@@ -436,11 +455,7 @@ def copy_static_files(app: Sphinx):
|
|
|
436
455
|
css_file = static_source_dir / "filter_tabs.css"
|
|
437
456
|
if css_file.exists():
|
|
438
457
|
shutil.copy(css_file, dest_dir)
|
|
439
|
-
|
|
440
|
-
# Copy JS file if it exists
|
|
441
|
-
js_file = static_source_dir / "filter_tabs.js"
|
|
442
|
-
if js_file.exists():
|
|
443
|
-
shutil.copy(js_file, dest_dir)
|
|
458
|
+
|
|
444
459
|
|
|
445
460
|
|
|
446
461
|
# =============================================================================
|
|
@@ -456,7 +471,6 @@ def setup(app: Sphinx) -> Dict[str, Any]:
|
|
|
456
471
|
|
|
457
472
|
# Add static files
|
|
458
473
|
app.add_css_file('filter_tabs.css')
|
|
459
|
-
app.add_js_file('filter_tabs.js')
|
|
460
474
|
|
|
461
475
|
# Register custom nodes (keep existing node registration code)
|
|
462
476
|
app.add_node(ContainerNode, html=(visit_container_node, depart_container_node))
|
|
@@ -475,6 +489,7 @@ def setup(app: Sphinx) -> Dict[str, Any]:
|
|
|
475
489
|
# Connect event handlers
|
|
476
490
|
app.connect('builder-inited', copy_static_files)
|
|
477
491
|
app.connect('doctree-resolved', setup_collapsible_admonitions)
|
|
492
|
+
app.connect('doctree-resolved', improve_inline_formatting)
|
|
478
493
|
|
|
479
494
|
return {
|
|
480
495
|
'version': __version__,
|
filter_tabs/renderer.py
CHANGED
|
@@ -149,8 +149,6 @@ class FilterTabsRenderer:
|
|
|
149
149
|
'style': self.config.to_css_properties()
|
|
150
150
|
}
|
|
151
151
|
|
|
152
|
-
# REMOVED: _generate_compatible_css method - no longer needed!
|
|
153
|
-
|
|
154
152
|
def _create_fieldset(self) -> FieldsetNode:
|
|
155
153
|
"""Create the main fieldset containing the legend, radio buttons, and panels."""
|
|
156
154
|
fieldset = FieldsetNode(role="radiogroup")
|
|
@@ -196,13 +194,16 @@ class FilterTabsRenderer:
|
|
|
196
194
|
radio_group += self._create_screen_reader_description(i, tab)
|
|
197
195
|
|
|
198
196
|
def _create_radio_button(self, index: int, tab: TabData, is_checked: bool) -> RadioInputNode:
|
|
199
|
-
"""Create a single radio button input."""
|
|
197
|
+
"""Create a single radio button input with data attribute."""
|
|
200
198
|
radio = RadioInputNode(
|
|
201
199
|
classes=['sr-only'],
|
|
202
200
|
type='radio',
|
|
203
201
|
name=self.group_id,
|
|
204
202
|
ids=[self.id_gen.radio_id(index)],
|
|
205
|
-
**{
|
|
203
|
+
**{
|
|
204
|
+
'aria-describedby': self.id_gen.desc_id(index),
|
|
205
|
+
'data-tab-index': str(index) # FIXED: Add data attribute
|
|
206
|
+
}
|
|
206
207
|
)
|
|
207
208
|
if tab.aria_label:
|
|
208
209
|
radio['aria-label'] = tab.aria_label
|
|
@@ -224,9 +225,16 @@ class FilterTabsRenderer:
|
|
|
224
225
|
return description_node
|
|
225
226
|
|
|
226
227
|
def _populate_content_area(self, content_area: ContainerNode) -> None:
|
|
227
|
-
"""Create and add all general and tab-specific content panels."""
|
|
228
|
+
"""Create and add all general and tab-specific content panels with accessibility enhancements."""
|
|
228
229
|
if self.general_content:
|
|
229
|
-
general_panel = PanelNode(
|
|
230
|
+
general_panel = PanelNode(
|
|
231
|
+
classes=[SFT_PANEL],
|
|
232
|
+
**{
|
|
233
|
+
'data-filter': 'General',
|
|
234
|
+
'aria-label': 'General information',
|
|
235
|
+
'role': 'region'
|
|
236
|
+
}
|
|
237
|
+
)
|
|
230
238
|
general_panel.extend(copy.deepcopy(self.general_content))
|
|
231
239
|
content_area += general_panel
|
|
232
240
|
|
|
@@ -234,15 +242,17 @@ class FilterTabsRenderer:
|
|
|
234
242
|
content_area += self._create_tab_panel(i, tab)
|
|
235
243
|
|
|
236
244
|
def _create_tab_panel(self, index: int, tab: TabData) -> PanelNode:
|
|
237
|
-
"""Create a single content panel for a tab."""
|
|
245
|
+
"""Create a single content panel for a tab - CSS only version."""
|
|
238
246
|
panel_attrs = {
|
|
239
247
|
'classes': [SFT_PANEL],
|
|
240
248
|
'ids': [self.id_gen.panel_id(index)],
|
|
241
249
|
'role': 'tabpanel',
|
|
242
250
|
'aria-labelledby': self.id_gen.radio_id(index),
|
|
243
|
-
'tabindex': '0',
|
|
244
|
-
'data-tab': tab.name.lower().replace(' ', '-')
|
|
251
|
+
'tabindex': '0', # Keep for keyboard accessibility
|
|
252
|
+
'data-tab': tab.name.lower().replace(' ', '-'),
|
|
253
|
+
'data-tab-index': str(index)
|
|
245
254
|
}
|
|
246
255
|
panel = PanelNode(**panel_attrs)
|
|
247
256
|
panel.extend(copy.deepcopy(tab.content))
|
|
248
257
|
return panel
|
|
258
|
+
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/* Sphinx Filter Tabs -
|
|
1
|
+
/* Sphinx Filter Tabs - Enhanced CSS with improved accessibility */
|
|
2
2
|
|
|
3
3
|
/* Main container */
|
|
4
4
|
.sft-container {
|
|
@@ -95,11 +95,39 @@
|
|
|
95
95
|
outline: none;
|
|
96
96
|
}
|
|
97
97
|
|
|
98
|
-
/*
|
|
98
|
+
/* Enhanced focus styling for CSS-only version */
|
|
99
99
|
.sft-panel:focus {
|
|
100
|
-
outline:
|
|
101
|
-
outline-offset:
|
|
100
|
+
outline: 3px solid var(--sft-highlight-color, #007bff);
|
|
101
|
+
outline-offset: 2px;
|
|
102
102
|
border-radius: 4px;
|
|
103
|
+
background: rgba(0, 123, 255, 0.05);
|
|
104
|
+
/* Ensure smooth transition */
|
|
105
|
+
transition: background-color 0.2s ease;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/* Visual focus indicator for better UX */
|
|
109
|
+
.sft-panel:focus::before {
|
|
110
|
+
content: "→ ";
|
|
111
|
+
color: var(--sft-highlight-color, #007bff);
|
|
112
|
+
font-weight: bold;
|
|
113
|
+
margin-right: 0.5em;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/* Improve screen reader flow for inline formatting */
|
|
117
|
+
strong, b, em, i {
|
|
118
|
+
speak: normal;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/* Ensure tab panels are easily discoverable when focused */
|
|
122
|
+
.sft-panel[role="tabpanel"]:focus {
|
|
123
|
+
position: relative;
|
|
124
|
+
z-index: 1;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/* ENHANCED: Focus styling for content within panels */
|
|
128
|
+
.sft-panel:focus-within {
|
|
129
|
+
outline: 1px solid var(--sft-highlight-color, #007bff);
|
|
130
|
+
outline-offset: -1px;
|
|
103
131
|
}
|
|
104
132
|
|
|
105
133
|
/* General content panel - always visible */
|
|
@@ -110,31 +138,71 @@
|
|
|
110
138
|
border-bottom: 1px solid #eee;
|
|
111
139
|
}
|
|
112
140
|
|
|
113
|
-
/*
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
.sft-radio-group input[type="radio"]
|
|
121
|
-
.sft-radio-group input[type="radio"]
|
|
122
|
-
.sft-radio-group input[type="radio"]
|
|
123
|
-
.sft-radio-group input[type="radio"]
|
|
124
|
-
.sft-radio-group input[type="radio"]:nth-child(9):checked ~ .sft-content .sft-panel:nth-of-type(5),
|
|
125
|
-
.sft-radio-group input[type="radio"]:nth-child(11):checked ~ .sft-content .sft-panel:nth-of-type(6),
|
|
126
|
-
.sft-radio-group input[type="radio"]:nth-child(13):checked ~ .sft-content .sft-panel:nth-of-type(7),
|
|
127
|
-
.sft-radio-group input[type="radio"]:nth-child(15):checked ~ .sft-content .sft-panel:nth-of-type(8),
|
|
128
|
-
.sft-radio-group input[type="radio"]:nth-child(17):checked ~ .sft-content .sft-panel:nth-of-type(9),
|
|
129
|
-
.sft-radio-group input[type="radio"]:nth-child(19):checked ~ .sft-content .sft-panel:nth-of-type(10) {
|
|
141
|
+
/* Panel visibility using data attributes */
|
|
142
|
+
.sft-radio-group input[type="radio"][data-tab-index="0"]:checked ~ .sft-content .sft-panel[data-tab-index="0"],
|
|
143
|
+
.sft-radio-group input[type="radio"][data-tab-index="1"]:checked ~ .sft-content .sft-panel[data-tab-index="1"],
|
|
144
|
+
.sft-radio-group input[type="radio"][data-tab-index="2"]:checked ~ .sft-content .sft-panel[data-tab-index="2"],
|
|
145
|
+
.sft-radio-group input[type="radio"][data-tab-index="3"]:checked ~ .sft-content .sft-panel[data-tab-index="3"],
|
|
146
|
+
.sft-radio-group input[type="radio"][data-tab-index="4"]:checked ~ .sft-content .sft-panel[data-tab-index="4"],
|
|
147
|
+
.sft-radio-group input[type="radio"][data-tab-index="5"]:checked ~ .sft-content .sft-panel[data-tab-index="5"],
|
|
148
|
+
.sft-radio-group input[type="radio"][data-tab-index="6"]:checked ~ .sft-content .sft-panel[data-tab-index="6"],
|
|
149
|
+
.sft-radio-group input[type="radio"][data-tab-index="7"]:checked ~ .sft-content .sft-panel[data-tab-index="7"],
|
|
150
|
+
.sft-radio-group input[type="radio"][data-tab-index="8"]:checked ~ .sft-content .sft-panel[data-tab-index="8"],
|
|
151
|
+
.sft-radio-group input[type="radio"][data-tab-index="9"]:checked ~ .sft-content .sft-panel[data-tab-index="9"] {
|
|
130
152
|
display: block;
|
|
131
153
|
}
|
|
132
154
|
|
|
133
|
-
/*
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
155
|
+
/* ENHANCED: Accessibility improvements for content within panels */
|
|
156
|
+
|
|
157
|
+
/* Make paragraphs within panels more accessible */
|
|
158
|
+
.sft-panel p {
|
|
159
|
+
margin: 0.75em 0;
|
|
160
|
+
line-height: 1.5;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
.sft-panel p:first-child {
|
|
164
|
+
margin-top: 0;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
.sft-panel p:last-child {
|
|
168
|
+
margin-bottom: 0;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/* Enhanced code block accessibility */
|
|
172
|
+
.sft-panel .highlight pre,
|
|
173
|
+
.sft-panel code {
|
|
174
|
+
/* Ensure code blocks are focusable and readable */
|
|
175
|
+
border-radius: 4px;
|
|
176
|
+
position: relative;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
.sft-panel .highlight pre:focus,
|
|
180
|
+
.sft-panel code:focus {
|
|
181
|
+
outline: 2px solid var(--sft-highlight-color, #007bff);
|
|
182
|
+
outline-offset: 2px;
|
|
183
|
+
/* Ensure the content is announced to screen readers */
|
|
184
|
+
z-index: 1;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/* Make lists more accessible */
|
|
188
|
+
.sft-panel ul,
|
|
189
|
+
.sft-panel ol {
|
|
190
|
+
margin: 0.75em 0;
|
|
191
|
+
padding-left: 2em;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
.sft-panel li {
|
|
195
|
+
margin: 0.25em 0;
|
|
196
|
+
line-height: 1.4;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/* Enhanced blockquote accessibility */
|
|
200
|
+
.sft-panel blockquote {
|
|
201
|
+
margin: 1em 0;
|
|
202
|
+
padding: 0.5em 1em;
|
|
203
|
+
border-left: 4px solid #ddd;
|
|
204
|
+
background: #f9f9f9;
|
|
205
|
+
}
|
|
138
206
|
|
|
139
207
|
/* Screen reader only content */
|
|
140
208
|
.sr-only {
|
|
@@ -149,6 +217,23 @@
|
|
|
149
217
|
border: 0;
|
|
150
218
|
}
|
|
151
219
|
|
|
220
|
+
/* ENHANCED: Skip link for better keyboard navigation */
|
|
221
|
+
.sft-container .skip-to-content {
|
|
222
|
+
position: absolute;
|
|
223
|
+
top: -40px;
|
|
224
|
+
left: 6px;
|
|
225
|
+
background: var(--sft-highlight-color, #007bff);
|
|
226
|
+
color: white;
|
|
227
|
+
padding: 8px;
|
|
228
|
+
text-decoration: none;
|
|
229
|
+
border-radius: 4px;
|
|
230
|
+
z-index: 1000;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
.sft-container .skip-to-content:focus {
|
|
234
|
+
top: 6px;
|
|
235
|
+
}
|
|
236
|
+
|
|
152
237
|
/* Collapsible sections */
|
|
153
238
|
.collapsible-section {
|
|
154
239
|
border: 1px solid #e0e0e0;
|
|
@@ -190,14 +275,22 @@
|
|
|
190
275
|
padding: 15px;
|
|
191
276
|
}
|
|
192
277
|
|
|
193
|
-
/* High contrast mode support */
|
|
278
|
+
/* ENHANCED: High contrast mode support with better visibility */
|
|
194
279
|
@media (prefers-contrast: high) {
|
|
195
280
|
.sft-radio-group input[type="radio"]:checked + label {
|
|
196
281
|
border-bottom-width: 4px;
|
|
282
|
+
background: #000;
|
|
283
|
+
color: #fff;
|
|
197
284
|
}
|
|
198
285
|
|
|
199
286
|
.sft-radio-group input[type="radio"]:focus + label {
|
|
200
287
|
box-shadow: 0 0 0 4px rgba(0, 0, 0, 0.8);
|
|
288
|
+
outline: 3px solid #fff;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
.sft-panel:focus {
|
|
292
|
+
outline: 3px solid #000;
|
|
293
|
+
background: #fff;
|
|
201
294
|
}
|
|
202
295
|
}
|
|
203
296
|
|
|
@@ -208,3 +301,24 @@
|
|
|
208
301
|
transition: none;
|
|
209
302
|
}
|
|
210
303
|
}
|
|
304
|
+
|
|
305
|
+
/* ENHANCED: Screen reader specific improvements */
|
|
306
|
+
@media (prefers-reduced-motion: reduce) {
|
|
307
|
+
/* Ensure focus changes are immediate for screen reader users */
|
|
308
|
+
.sft-panel {
|
|
309
|
+
transition: none;
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
/* Force visibility for screen reader testing */
|
|
314
|
+
@media (forced-colors: active) {
|
|
315
|
+
.sft-panel {
|
|
316
|
+
border: 1px solid ButtonText;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
.sft-radio-group input[type="radio"]:checked + label {
|
|
320
|
+
border: 2px solid Highlight;
|
|
321
|
+
background: Highlight;
|
|
322
|
+
color: HighlightText;
|
|
323
|
+
}
|
|
324
|
+
}
|
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: sphinx-filter-tabs
|
|
3
|
+
Version: 1.2.6
|
|
4
|
+
Summary: A Sphinx extension for accessible, CSS-first filterable content tabs.
|
|
5
|
+
Author-email: Aputsiak Niels Janussen <aputtu+sphinx@gmail.com>
|
|
6
|
+
License: GNU General Public License v3.0
|
|
7
|
+
Project-URL: Homepage, https://github.com/aputtu/sphinx-filter-tabs
|
|
8
|
+
Project-URL: Repository, https://github.com/aputtu/sphinx-filter-tabs.git
|
|
9
|
+
Project-URL: Issues, https://github.com/aputtu/sphinx-filter-tabs/issues
|
|
10
|
+
Keywords: sphinx,extension,tabs,filter,documentation,css-only,accessibility,keyboard-navigation
|
|
11
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
12
|
+
Classifier: Framework :: Sphinx :: Extension
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: License :: OSI Approved :: GNU General Public License v3 (GPLv3)
|
|
15
|
+
Classifier: Operating System :: OS Independent
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Topic :: Documentation :: Sphinx
|
|
20
|
+
Classifier: Topic :: Software Development :: Documentation
|
|
21
|
+
Requires-Python: >=3.10
|
|
22
|
+
Description-Content-Type: text/markdown
|
|
23
|
+
License-File: LICENSE
|
|
24
|
+
Requires-Dist: Sphinx<9.0,>=7.0
|
|
25
|
+
Dynamic: license-file
|
|
26
|
+
|
|
27
|
+
# Sphinx Filter Tabs Extension
|
|
28
|
+
|
|
29
|
+
[](https://github.com/aputtu/sphinx-filter-tabs/actions/workflows/test.yml)
|
|
30
|
+
[](https://pypi.org/project/sphinx-filter-tabs/)
|
|
31
|
+
[](https://pypi.org/project/sphinx-filter-tabs/)
|
|
32
|
+
[](https://github.com/aputtu/sphinx-filter-tabs/blob/main/LICENSE)
|
|
33
|
+
|
|
34
|
+
A robust Sphinx extension for creating accessible, filterable content tabs using pure CSS and semantic HTML.
|
|
35
|
+
|
|
36
|
+
**📖 View extension and documentation at: https://aputtu.github.io/sphinx-filter-tabs/**
|
|
37
|
+
|
|
38
|
+
This extension provides `filter-tabs` and `tab` directives to create user-friendly, switchable content blocks. Perfect for showing code examples in multiple languages, installation instructions for different platforms, or any content that benefits from organized, filterable presentation.
|
|
39
|
+
|
|
40
|
+
## Key Features
|
|
41
|
+
|
|
42
|
+
- **Pure CSS Implementation:** Zero JavaScript dependencies for maximum compatibility and performance
|
|
43
|
+
- **Fully Accessible:** WAI-ARIA compliant with native keyboard navigation and screen reader support
|
|
44
|
+
- **Semantic HTML:** Uses standard form controls (radio buttons) for robust, predictable behavior
|
|
45
|
+
- **Universal Compatibility:** Works in all environments where CSS is supported, including strict CSP policies
|
|
46
|
+
- **Easy Customization:** Theme colors and styling through simple CSS custom properties
|
|
47
|
+
- **Multiple Output Formats:** Graceful fallback to sequential content in PDF/LaTeX builds
|
|
48
|
+
- **Proven Reliability:** Comprehensive test suite across multiple Python and Sphinx versions
|
|
49
|
+
|
|
50
|
+
## Quick Start
|
|
51
|
+
|
|
52
|
+
### Installation
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
pip install sphinx-filter-tabs
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### Enable the Extension
|
|
59
|
+
|
|
60
|
+
Add to your `conf.py`:
|
|
61
|
+
|
|
62
|
+
```python
|
|
63
|
+
extensions = [
|
|
64
|
+
# ... your other extensions ...
|
|
65
|
+
'filter_tabs.extension',
|
|
66
|
+
]
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### Basic Usage
|
|
70
|
+
|
|
71
|
+
```rst
|
|
72
|
+
.. filter-tabs::
|
|
73
|
+
|
|
74
|
+
This content appears above all tabs.
|
|
75
|
+
|
|
76
|
+
.. tab:: Python
|
|
77
|
+
|
|
78
|
+
Install using pip:
|
|
79
|
+
|
|
80
|
+
.. code-block:: bash
|
|
81
|
+
|
|
82
|
+
pip install my-package
|
|
83
|
+
|
|
84
|
+
.. tab:: Conda (default)
|
|
85
|
+
|
|
86
|
+
Install using conda:
|
|
87
|
+
|
|
88
|
+
.. code-block:: bash
|
|
89
|
+
|
|
90
|
+
conda install my-package
|
|
91
|
+
|
|
92
|
+
.. tab:: From Source
|
|
93
|
+
|
|
94
|
+
Build from source:
|
|
95
|
+
|
|
96
|
+
.. code-block:: bash
|
|
97
|
+
|
|
98
|
+
git clone https://github.com/user/repo.git
|
|
99
|
+
cd repo
|
|
100
|
+
pip install -e .
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## Configuration Options
|
|
104
|
+
|
|
105
|
+
Add these optional settings to your `conf.py`:
|
|
106
|
+
|
|
107
|
+
```python
|
|
108
|
+
# Customize the active tab highlight color
|
|
109
|
+
filter_tabs_highlight_color = '#007bff' # Default: '#007bff'
|
|
110
|
+
|
|
111
|
+
# Enable debug logging during development
|
|
112
|
+
filter_tabs_debug_mode = False # Default: False
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
## Advanced Usage
|
|
116
|
+
|
|
117
|
+
### Custom Legend
|
|
118
|
+
|
|
119
|
+
Override the auto-generated legend:
|
|
120
|
+
|
|
121
|
+
```rst
|
|
122
|
+
.. filter-tabs::
|
|
123
|
+
:legend: Select Your Installation Method
|
|
124
|
+
|
|
125
|
+
.. tab:: Quick Install
|
|
126
|
+
Content here...
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### ARIA Labels for Accessibility
|
|
130
|
+
|
|
131
|
+
Provide descriptive labels for screen readers:
|
|
132
|
+
|
|
133
|
+
```rst
|
|
134
|
+
.. filter-tabs::
|
|
135
|
+
|
|
136
|
+
.. tab:: CLI
|
|
137
|
+
:aria-label: Command line installation instructions
|
|
138
|
+
|
|
139
|
+
Content for command line users...
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
### Nested Tabs
|
|
143
|
+
|
|
144
|
+
Create complex layouts with nested tab groups:
|
|
145
|
+
|
|
146
|
+
```rst
|
|
147
|
+
.. filter-tabs::
|
|
148
|
+
|
|
149
|
+
.. tab:: Windows
|
|
150
|
+
|
|
151
|
+
Choose your package manager:
|
|
152
|
+
|
|
153
|
+
.. filter-tabs::
|
|
154
|
+
|
|
155
|
+
.. tab:: Chocolatey
|
|
156
|
+
choco install my-package
|
|
157
|
+
|
|
158
|
+
.. tab:: Scoop
|
|
159
|
+
scoop install my-package
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
## How It Works
|
|
163
|
+
|
|
164
|
+
This extension uses a **pure CSS architecture** with semantic HTML:
|
|
165
|
+
|
|
166
|
+
- **Radio buttons** provide the selection mechanism (hidden but accessible)
|
|
167
|
+
- **CSS `:checked` selectors** control panel visibility
|
|
168
|
+
- **Fieldset/legend** structure provides semantic grouping
|
|
169
|
+
- **ARIA attributes** enhance screen reader support
|
|
170
|
+
- **Native keyboard navigation** works through standard form controls
|
|
171
|
+
|
|
172
|
+
This approach ensures:
|
|
173
|
+
- **Maximum compatibility** across all browsers and assistive technologies
|
|
174
|
+
- **Better performance** with no JavaScript parsing or execution
|
|
175
|
+
- **Enhanced security** for environments with strict Content Security Policies
|
|
176
|
+
- **Simplified maintenance** with fewer dependencies and potential conflicts
|
|
177
|
+
|
|
178
|
+
## Browser Support
|
|
179
|
+
|
|
180
|
+
Works in all modern browsers that support:
|
|
181
|
+
- CSS3 selectors (`:checked`, attribute selectors)
|
|
182
|
+
- Basic ARIA attributes
|
|
183
|
+
- HTML5 form elements
|
|
184
|
+
|
|
185
|
+
This includes all browsers from the last 10+ years.
|
|
186
|
+
|
|
187
|
+
## Development
|
|
188
|
+
|
|
189
|
+
### Quick Setup
|
|
190
|
+
|
|
191
|
+
```bash
|
|
192
|
+
git clone https://github.com/aputtu/sphinx-filter-tabs.git
|
|
193
|
+
cd sphinx-filter-tabs
|
|
194
|
+
./scripts/setup_dev.sh
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
This creates a virtual environment and builds the documentation.
|
|
198
|
+
|
|
199
|
+
### Development Commands
|
|
200
|
+
|
|
201
|
+
```bash
|
|
202
|
+
# Activate virtual environment
|
|
203
|
+
source venv/bin/activate
|
|
204
|
+
|
|
205
|
+
# Run tests
|
|
206
|
+
pytest
|
|
207
|
+
|
|
208
|
+
# Build documentation
|
|
209
|
+
./scripts/dev.sh html
|
|
210
|
+
|
|
211
|
+
# Run tests across multiple Sphinx versions
|
|
212
|
+
tox
|
|
213
|
+
|
|
214
|
+
# Clean build and start fresh
|
|
215
|
+
./scripts/dev.sh clean-all
|
|
216
|
+
|
|
217
|
+
# Export project structure for analysis
|
|
218
|
+
./scripts/export-project.sh
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
### Testing
|
|
222
|
+
|
|
223
|
+
The project includes comprehensive tests covering:
|
|
224
|
+
- Basic tab functionality and content visibility
|
|
225
|
+
- Accessibility features and ARIA compliance
|
|
226
|
+
- Nested tabs and complex layouts
|
|
227
|
+
- Multiple output formats (HTML, LaTeX)
|
|
228
|
+
- Cross-browser compatibility
|
|
229
|
+
|
|
230
|
+
Tests run automatically on:
|
|
231
|
+
- Python 3.10, 3.12
|
|
232
|
+
- Sphinx 7.0, 7.4, 8.0, 8.2
|
|
233
|
+
- Multiple operating systems via GitHub Actions
|
|
234
|
+
|
|
235
|
+
## Architecture
|
|
236
|
+
|
|
237
|
+
The extension consists of three main components:
|
|
238
|
+
|
|
239
|
+
- **`extension.py`** - Sphinx integration, directives, and node definitions
|
|
240
|
+
- **`renderer.py`** - HTML generation and output formatting
|
|
241
|
+
- **`static/filter_tabs.css`** - Pure CSS styling and functionality
|
|
242
|
+
|
|
243
|
+
This clean separation makes the code easy to understand, test, and maintain.
|
|
244
|
+
|
|
245
|
+
## Contributing
|
|
246
|
+
|
|
247
|
+
Contributions are welcome! Please:
|
|
248
|
+
|
|
249
|
+
1. Fork the repository
|
|
250
|
+
2. Create a feature branch
|
|
251
|
+
3. Add tests for new functionality
|
|
252
|
+
4. Ensure all tests pass: `pytest`
|
|
253
|
+
5. Submit a pull request
|
|
254
|
+
|
|
255
|
+
## License
|
|
256
|
+
|
|
257
|
+
GNU General Public License v3.0. See [LICENSE](LICENSE) for details.
|
|
258
|
+
|
|
259
|
+
## Changelog
|
|
260
|
+
|
|
261
|
+
See [CHANGELOG.md](docs/changelog.rst) for version history and migration notes.
|
|
262
|
+
|
|
263
|
+
## Support
|
|
264
|
+
|
|
265
|
+
- **Documentation**: https://aputtu.github.io/sphinx-filter-tabs/
|
|
266
|
+
- **Issues**: https://github.com/aputtu/sphinx-filter-tabs/issues
|
|
267
|
+
- **PyPI**: https://pypi.org/project/sphinx-filter-tabs/
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
filter_tabs/__init__.py,sha256=VPpIhj4HaLeMX7ai7dZFkUm81ii2ePPGjCd9hsMjsN4,397
|
|
2
|
+
filter_tabs/extension.py,sha256=6mna59Qg1zdk6Vn9eXU_CaaQg1yBq4ktx2jx9O01frM,18679
|
|
3
|
+
filter_tabs/renderer.py,sha256=ublmQSoFewdPGRtEO7gLrxGqCZCjlooeom0wudx4phk,10100
|
|
4
|
+
filter_tabs/static/filter_tabs.css,sha256=ZoiSWcn2YBEWgkQ-vPbIPHwQ7s2TG4aUikyxM1A8b9I,7956
|
|
5
|
+
sphinx_filter_tabs-1.2.6.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
|
|
6
|
+
sphinx_filter_tabs-1.2.6.dist-info/METADATA,sha256=Wk2ZurjL5XIgRdHoyqtcjwFBGXft5K0aKIREoZWHAMA,7608
|
|
7
|
+
sphinx_filter_tabs-1.2.6.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
8
|
+
sphinx_filter_tabs-1.2.6.dist-info/entry_points.txt,sha256=za_bQcueY8AHyq7XnnjkW9X3C-LsZjeERVQ_ds7jV1A,62
|
|
9
|
+
sphinx_filter_tabs-1.2.6.dist-info/top_level.txt,sha256=K0Iy-6EsYYdvlyXdsJT0SQg-BLDBgT5-Y8ZKy5SNAfc,12
|
|
10
|
+
sphinx_filter_tabs-1.2.6.dist-info/RECORD,,
|
|
@@ -1,101 +0,0 @@
|
|
|
1
|
-
// Progressive enhancement for focus management and accessibility announcements.
|
|
2
|
-
// This file provides enhancements while maintaining native radio button keyboard behavior.
|
|
3
|
-
|
|
4
|
-
(function() {
|
|
5
|
-
'use strict';
|
|
6
|
-
|
|
7
|
-
// Only enhance if the extension's HTML is present on the page.
|
|
8
|
-
if (!document.querySelector('.sft-container')) return;
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Moves focus to the content panel associated with a given radio button.
|
|
12
|
-
* This improves accessibility by directing screen reader users to the new content.
|
|
13
|
-
* @param {HTMLInputElement} radio The radio button that was selected.
|
|
14
|
-
*/
|
|
15
|
-
function focusOnPanel(radio) {
|
|
16
|
-
if (!radio.checked) return;
|
|
17
|
-
|
|
18
|
-
// Derive the panel's ID from the radio button's ID.
|
|
19
|
-
// e.g., 'filter-group-1-radio-0' becomes 'filter-group-1-panel-0'
|
|
20
|
-
const panelId = radio.id.replace('-radio-', '-panel-');
|
|
21
|
-
const panel = document.getElementById(panelId);
|
|
22
|
-
|
|
23
|
-
if (panel) {
|
|
24
|
-
panel.focus();
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* Creates or updates a live region to announce tab changes to screen readers.
|
|
30
|
-
* @param {string} tabName The name of the selected tab.
|
|
31
|
-
*/
|
|
32
|
-
function announceTabChange(tabName) {
|
|
33
|
-
// Create or find the live region for screen reader announcements.
|
|
34
|
-
let liveRegion = document.getElementById('tab-live-region');
|
|
35
|
-
if (!liveRegion) {
|
|
36
|
-
liveRegion = document.createElement('div');
|
|
37
|
-
liveRegion.id = 'tab-live-region';
|
|
38
|
-
liveRegion.setAttribute('role', 'status');
|
|
39
|
-
liveRegion.setAttribute('aria-live', 'polite');
|
|
40
|
-
liveRegion.setAttribute('aria-atomic', 'true');
|
|
41
|
-
// Hide the element visually but keep it accessible.
|
|
42
|
-
liveRegion.style.position = 'absolute';
|
|
43
|
-
liveRegion.style.left = '-10000px';
|
|
44
|
-
liveRegion.style.width = '1px';
|
|
45
|
-
liveRegion.style.height = '1px';
|
|
46
|
-
liveRegion.style.overflow = 'hidden';
|
|
47
|
-
document.body.appendChild(liveRegion);
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
// Update the announcement text.
|
|
51
|
-
liveRegion.textContent = `${tabName} tab selected`;
|
|
52
|
-
|
|
53
|
-
// Clear the announcement after a short delay to prevent clutter.
|
|
54
|
-
setTimeout(() => {
|
|
55
|
-
liveRegion.textContent = '';
|
|
56
|
-
}, 1000);
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
/**
|
|
60
|
-
* Initializes progressive enhancements for all filter-tab components.
|
|
61
|
-
* REMOVED: Custom keyboard navigation (now uses native radio button behavior)
|
|
62
|
-
* KEPT: Focus management and screen reader announcements
|
|
63
|
-
*/
|
|
64
|
-
function initTabEnhancements() {
|
|
65
|
-
const containers = document.querySelectorAll('.sft-container');
|
|
66
|
-
|
|
67
|
-
containers.forEach(container => {
|
|
68
|
-
const tabBar = container.querySelector('.sft-radio-group');
|
|
69
|
-
if (!tabBar) return;
|
|
70
|
-
|
|
71
|
-
const radios = tabBar.querySelectorAll('input[type="radio"]');
|
|
72
|
-
const labels = tabBar.querySelectorAll('label');
|
|
73
|
-
|
|
74
|
-
if (radios.length === 0 || labels.length === 0) return;
|
|
75
|
-
|
|
76
|
-
// Add change listeners for announcements and focus management
|
|
77
|
-
radios.forEach((radio, index) => {
|
|
78
|
-
radio.addEventListener('change', () => {
|
|
79
|
-
if (radio.checked) {
|
|
80
|
-
// Get the tab name from the associated label
|
|
81
|
-
const label = labels[index];
|
|
82
|
-
const tabName = label ? label.textContent.trim() : 'Unknown';
|
|
83
|
-
|
|
84
|
-
// Announce the change to screen readers
|
|
85
|
-
announceTabChange(tabName);
|
|
86
|
-
|
|
87
|
-
// Move focus to the newly visible panel
|
|
88
|
-
focusOnPanel(radio);
|
|
89
|
-
}
|
|
90
|
-
});
|
|
91
|
-
});
|
|
92
|
-
});
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
// Initialize the enhancements once the DOM is ready
|
|
96
|
-
if (document.readyState === 'loading') {
|
|
97
|
-
document.addEventListener('DOMContentLoaded', initTabEnhancements);
|
|
98
|
-
} else {
|
|
99
|
-
initTabEnhancements();
|
|
100
|
-
}
|
|
101
|
-
})();
|
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: sphinx-filter-tabs
|
|
3
|
-
Version: 1.2.5
|
|
4
|
-
Summary: A Sphinx extension for accessible, CSS-first filterable content tabs.
|
|
5
|
-
Author-email: Aputsiak Niels Janussen <aputtu+sphinx@gmail.com>
|
|
6
|
-
License: GNU General Public License v3.0
|
|
7
|
-
Project-URL: Homepage, https://github.com/aputtu/sphinx-filter-tabs
|
|
8
|
-
Project-URL: Repository, https://github.com/aputtu/sphinx-filter-tabs.git
|
|
9
|
-
Project-URL: Issues, https://github.com/aputtu/sphinx-filter-tabs/issues
|
|
10
|
-
Keywords: sphinx,extension,tabs,filter,documentation,css-only,accessibility,keyboard-navigation
|
|
11
|
-
Classifier: Development Status :: 5 - Production/Stable
|
|
12
|
-
Classifier: Framework :: Sphinx :: Extension
|
|
13
|
-
Classifier: Intended Audience :: Developers
|
|
14
|
-
Classifier: License :: OSI Approved :: GNU General Public License v3 (GPLv3)
|
|
15
|
-
Classifier: Operating System :: OS Independent
|
|
16
|
-
Classifier: Programming Language :: Python :: 3
|
|
17
|
-
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
-
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
-
Classifier: Topic :: Documentation :: Sphinx
|
|
20
|
-
Classifier: Topic :: Software Development :: Documentation
|
|
21
|
-
Requires-Python: >=3.10
|
|
22
|
-
Description-Content-Type: text/markdown
|
|
23
|
-
License-File: LICENSE
|
|
24
|
-
Requires-Dist: Sphinx<9.0,>=7.0
|
|
25
|
-
Dynamic: license-file
|
|
26
|
-
|
|
27
|
-
# Sphinx Filter Tabs Extension
|
|
28
|
-
|
|
29
|
-
[](https://github.com/aputtu/sphinx-filter-tabs/actions/workflows/test.yml)
|
|
30
|
-
[](https://pypi.org/project/sphinx-filter-tabs/)
|
|
31
|
-
[](https://pypi.org/project/sphinx-filter-tabs/)
|
|
32
|
-
[](https://github.com/aputtu/sphinx-filter-tabs/blob/main/LICENSE)
|
|
33
|
-
|
|
34
|
-
A robust Sphinx extension for creating accessible, JavaScript-free, filterable content tabs.
|
|
35
|
-
|
|
36
|
-
**📖 View extension and documentation at: https://aputtu.github.io/sphinx-filter-tabs/**
|
|
37
|
-
|
|
38
|
-
This extension provides `filter-tabs` and `tab` directives to create user-friendly, switchable content blocks, ideal for showing code examples in multiple languages or instructions for different platforms.
|
|
39
|
-
|
|
40
|
-
## Features
|
|
41
|
-
|
|
42
|
-
- **No JavaScript:** Pure CSS implementation ensures maximum compatibility, speed, and accessibility.
|
|
43
|
-
- **WAI-ARIA Compliant:** The generated HTML follows accessibility best practices for keyboard navigation and screen readers.
|
|
44
|
-
- **Highly Customizable:** Easily theme colors, fonts, and sizes directly from your `conf.py` using CSS Custom Properties.
|
|
45
|
-
- **Graceful Fallback:** Renders content as simple admonitions in non-HTML outputs like PDF/LaTeX.
|
|
46
|
-
- **Automated Testing:** CI/CD pipeline tests against multiple Sphinx versions to ensure compatibility.
|
|
47
|
-
|
|
48
|
-
## Installation
|
|
49
|
-
|
|
50
|
-
You can install this extension using `pip`:
|
|
51
|
-
```bash
|
|
52
|
-
pip install sphinx-filter-tabs
|
|
53
|
-
```
|
|
54
|
-
|
|
55
|
-
## Development
|
|
56
|
-
|
|
57
|
-
1. You can install a local version of the Sphinx with extension using:
|
|
58
|
-
```bash
|
|
59
|
-
./scripts/setup_dev.sh # Initially cleans previous folders in _docs/build and venv.
|
|
60
|
-
```
|
|
61
|
-
|
|
62
|
-
Command to enter venv is provided.
|
|
63
|
-
|
|
64
|
-
2. Once inside virtual environment, you can use following commands:
|
|
65
|
-
```bash
|
|
66
|
-
pytest # Runs test suite on configured version of Sphinx.
|
|
67
|
-
tox # Check across multiple Sphinx versions. Manual install of tox required.
|
|
68
|
-
./scripts/export-project.sh # Outputs directory structure and code to txt
|
|
69
|
-
./dev.sh [options] # Allows for faster generation for html, pdf, clean up
|
|
70
|
-
```
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
filter_tabs/__init__.py,sha256=VPpIhj4HaLeMX7ai7dZFkUm81ii2ePPGjCd9hsMjsN4,397
|
|
2
|
-
filter_tabs/extension.py,sha256=Dspt9r8TVoYChHFvGsncuV01GEga7BOOsPaMwYdMQcg,18014
|
|
3
|
-
filter_tabs/renderer.py,sha256=m0_sD5ujtT4rmhYlhm6vMgSn0W1WAJPJWaTjh6zXezY,9730
|
|
4
|
-
filter_tabs/static/filter_tabs.css,sha256=EbsG5zdEv8g3rdV7y76CXSwEMiBaBTX01qFYDiXeJt0,5379
|
|
5
|
-
filter_tabs/static/filter_tabs.js,sha256=URduEo1P8y_-TaT485U6APGXsdQg6rFBpib3yaNc-9g,3989
|
|
6
|
-
sphinx_filter_tabs-1.2.5.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
|
|
7
|
-
sphinx_filter_tabs-1.2.5.dist-info/METADATA,sha256=nH86wxyNL4OAwRnPL0W8k4vJcj0mxKpoc_bk_2b2cv0,3483
|
|
8
|
-
sphinx_filter_tabs-1.2.5.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
9
|
-
sphinx_filter_tabs-1.2.5.dist-info/entry_points.txt,sha256=za_bQcueY8AHyq7XnnjkW9X3C-LsZjeERVQ_ds7jV1A,62
|
|
10
|
-
sphinx_filter_tabs-1.2.5.dist-info/top_level.txt,sha256=K0Iy-6EsYYdvlyXdsJT0SQg-BLDBgT5-Y8ZKy5SNAfc,12
|
|
11
|
-
sphinx_filter_tabs-1.2.5.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|