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 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 and JS files to the build directory."""
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
- **{'aria-describedby': self.id_gen.desc_id(index)}
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(classes=[SFT_PANEL], **{'data-filter': 'General'})
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 - Simplified Accessibility-First Stylesheet */
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
- /* Focus styling for panels */
98
+ /* Enhanced focus styling for CSS-only version */
99
99
  .sft-panel:focus {
100
- outline: 2px solid var(--sft-highlight-color, #007bff);
101
- outline-offset: -2px;
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
- * FIXED: Panel visibility using CSS without inline styles
115
- * This uses a general approach with nth-child selectors
116
- * No more inline <style> elements needed!
117
- */
118
-
119
- /* Show panels when corresponding radio is checked */
120
- .sft-radio-group input[type="radio"]:nth-child(1):checked ~ .sft-content .sft-panel:nth-of-type(1),
121
- .sft-radio-group input[type="radio"]:nth-child(3):checked ~ .sft-content .sft-panel:nth-of-type(2),
122
- .sft-radio-group input[type="radio"]:nth-child(5):checked ~ .sft-content .sft-panel:nth-of-type(3),
123
- .sft-radio-group input[type="radio"]:nth-child(7):checked ~ .sft-content .sft-panel:nth-of-type(4),
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
- /* Alternative approach using CSS custom properties (more elegant) */
134
- /*
135
- * If you prefer a more modern approach, you could use CSS custom properties
136
- * and update the renderer to set --active-tab instead of generating CSS rules
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
+ [![Tests and Docs Deployment](https://github.com/aputtu/sphinx-filter-tabs/actions/workflows/test.yml/badge.svg)](https://github.com/aputtu/sphinx-filter-tabs/actions/workflows/test.yml)
30
+ [![PyPI version](https://img.shields.io/pypi/v/sphinx-filter-tabs.svg)](https://pypi.org/project/sphinx-filter-tabs/)
31
+ [![PyPI - Python Version](https://img.shields.io/pypi/pyversions/sphinx-filter-tabs.svg)](https://pypi.org/project/sphinx-filter-tabs/)
32
+ [![PyPI - License](https://img.shields.io/pypi/l/sphinx-filter-tabs.svg)](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
- [![Tests and Docs Deployment](https://github.com/aputtu/sphinx-filter-tabs/actions/workflows/test.yml/badge.svg)](https://github.com/aputtu/sphinx-filter-tabs/actions/workflows/test.yml)
30
- [![PyPI version](https://img.shields.io/pypi/v/sphinx-filter-tabs.svg)](https://pypi.org/project/sphinx-filter-tabs/)
31
- [![PyPI - Python Version](https://img.shields.io/pypi/pyversions/sphinx-filter-tabs.svg)](https://pypi.org/project/sphinx-filter-tabs/)
32
- [![PyPI - License](https://img.shields.io/pypi/l/sphinx-filter-tabs.svg)](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,,