sphinx-filter-tabs 1.2.5__py3-none-any.whl → 1.3__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 +48 -14
- filter_tabs/renderer.py +21 -12
- filter_tabs/static/filter_tabs.css +141 -27
- sphinx_filter_tabs-1.3.dist-info/METADATA +267 -0
- sphinx_filter_tabs-1.3.dist-info/RECORD +10 -0
- {sphinx_filter_tabs-1.2.5.dist-info → sphinx_filter_tabs-1.3.dist-info}/WHEEL +1 -1
- 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.3.dist-info}/entry_points.txt +0 -0
- {sphinx_filter_tabs-1.2.5.dist-info → sphinx_filter_tabs-1.3.dist-info}/licenses/LICENSE +0 -0
- {sphinx_filter_tabs-1.2.5.dist-info → sphinx_filter_tabs-1.3.dist-info}/top_level.txt +0 -0
filter_tabs/extension.py
CHANGED
|
@@ -7,6 +7,7 @@ Consolidated version containing directives, data models, nodes, and Sphinx integ
|
|
|
7
7
|
from __future__ import annotations
|
|
8
8
|
import copy
|
|
9
9
|
import shutil
|
|
10
|
+
import warnings
|
|
10
11
|
from pathlib import Path
|
|
11
12
|
from docutils import nodes
|
|
12
13
|
from docutils.parsers.rst import Directive, directives
|
|
@@ -21,6 +22,12 @@ from . import __version__
|
|
|
21
22
|
if TYPE_CHECKING:
|
|
22
23
|
from sphinx.environment import BuildEnvironment
|
|
23
24
|
|
|
25
|
+
# Define the warning for Sphinx 9 compatibility
|
|
26
|
+
try:
|
|
27
|
+
from sphinx.deprecation import RemovedInSphinx11Warning
|
|
28
|
+
except ImportError:
|
|
29
|
+
RemovedInSphinx11Warning = DeprecationWarning
|
|
30
|
+
|
|
24
31
|
# =============================================================================
|
|
25
32
|
# Custom Docutils Nodes
|
|
26
33
|
# =============================================================================
|
|
@@ -43,7 +50,6 @@ class SummaryNode(nodes.General, nodes.Element): pass
|
|
|
43
50
|
class TabData:
|
|
44
51
|
"""
|
|
45
52
|
Represents a single tab's data within a filter-tabs directive.
|
|
46
|
-
|
|
47
53
|
Attributes:
|
|
48
54
|
name: Display name of the tab
|
|
49
55
|
is_default: Whether this tab should be selected by default
|
|
@@ -99,7 +105,6 @@ class FilterTabsConfig:
|
|
|
99
105
|
class IDGenerator:
|
|
100
106
|
"""
|
|
101
107
|
Centralized ID generation for consistent element identification.
|
|
102
|
-
|
|
103
108
|
This ensures all IDs follow a consistent pattern and are unique
|
|
104
109
|
within their filter-tabs group.
|
|
105
110
|
"""
|
|
@@ -151,7 +156,7 @@ class TabDirective(Directive):
|
|
|
151
156
|
|
|
152
157
|
# Validate context
|
|
153
158
|
if not hasattr(env, 'sft_context') or not env.sft_context:
|
|
154
|
-
|
|
159
|
+
raise self.error("`tab` can only be used inside a `filter-tabs` directive.")
|
|
155
160
|
|
|
156
161
|
# Parse tab argument - moved from parsers.py
|
|
157
162
|
try:
|
|
@@ -242,7 +247,7 @@ class FilterTabsDirective(Directive):
|
|
|
242
247
|
# Validate tabs - moved from parsers.py
|
|
243
248
|
if not tab_data_list:
|
|
244
249
|
error_message = (
|
|
245
|
-
"No `.. tab::` directives found inside `.. filter-tabs
|
|
250
|
+
"No `.. tab::` directives found inside `.. filter-tabs::`.\n"
|
|
246
251
|
"You must include at least one tab."
|
|
247
252
|
)
|
|
248
253
|
if general_content:
|
|
@@ -264,8 +269,21 @@ class FilterTabsDirective(Directive):
|
|
|
264
269
|
from .renderer import FilterTabsRenderer
|
|
265
270
|
renderer = FilterTabsRenderer(self, tab_data_list, general_content, custom_legend=custom_legend)
|
|
266
271
|
|
|
272
|
+
# Safe builder check that suppresses Sphinx 9 warnings
|
|
273
|
+
builder_name = 'html' # Default assumption
|
|
274
|
+
|
|
275
|
+
try:
|
|
276
|
+
# We catch the specific warning about env.app being deprecated
|
|
277
|
+
with warnings.catch_warnings():
|
|
278
|
+
warnings.filterwarnings("ignore", category=RemovedInSphinx11Warning)
|
|
279
|
+
if hasattr(env, 'app') and env.app:
|
|
280
|
+
builder_name = env.app.builder.name
|
|
281
|
+
except Exception:
|
|
282
|
+
# Fallback for very old Sphinx or edge cases
|
|
283
|
+
pass
|
|
284
|
+
|
|
267
285
|
# Render based on builder
|
|
268
|
-
if
|
|
286
|
+
if builder_name == 'html':
|
|
269
287
|
return renderer.render_html()
|
|
270
288
|
else:
|
|
271
289
|
return renderer.render_fallback()
|
|
@@ -274,7 +292,7 @@ class FilterTabsDirective(Directive):
|
|
|
274
292
|
"""Validate tab data list - moved from parsers.py"""
|
|
275
293
|
if not tab_data_list and not skip_empty_check:
|
|
276
294
|
raise ValueError(
|
|
277
|
-
"No tab directives found inside filter-tabs
|
|
295
|
+
"No tab directives found inside filter-tabs.\n"
|
|
278
296
|
"Add at least one .. tab:: directive."
|
|
279
297
|
)
|
|
280
298
|
names = []
|
|
@@ -382,7 +400,7 @@ def visit_radio_input_node(self: HTML5Translator, node: RadioInputNode) -> None:
|
|
|
382
400
|
self.body.append(self.starttag(node, 'input', **attrs))
|
|
383
401
|
|
|
384
402
|
def depart_radio_input_node(self: HTML5Translator, node: RadioInputNode) -> None:
|
|
385
|
-
pass
|
|
403
|
+
pass # Self-closing tag
|
|
386
404
|
|
|
387
405
|
def visit_label_node(self: HTML5Translator, node: LabelNode) -> None:
|
|
388
406
|
attrs = _get_html_attrs(node)
|
|
@@ -419,12 +437,31 @@ def depart_summary_node(self: HTML5Translator, node: SummaryNode) -> None:
|
|
|
419
437
|
self.body.append('</summary>')
|
|
420
438
|
|
|
421
439
|
|
|
440
|
+
# =============================================================================
|
|
441
|
+
# Use ARIA to Improve Strong Element Behavior
|
|
442
|
+
# =============================================================================
|
|
443
|
+
|
|
444
|
+
def improve_inline_formatting(app: Sphinx, doctree: nodes.document, docname: str):
|
|
445
|
+
"""Improve screen reader handling of inline formatting."""
|
|
446
|
+
if app.builder.name != 'html':
|
|
447
|
+
return
|
|
448
|
+
|
|
449
|
+
# Find all strong/emphasis nodes and add ARIA attributes
|
|
450
|
+
for node in doctree.findall(nodes.strong):
|
|
451
|
+
# Add aria-label to make the content read as one unit
|
|
452
|
+
text_content = node.astext()
|
|
453
|
+
node['aria-label'] = text_content
|
|
454
|
+
|
|
455
|
+
for node in doctree.findall(nodes.emphasis):
|
|
456
|
+
text_content = node.astext()
|
|
457
|
+
node['aria-label'] = text_content
|
|
458
|
+
|
|
422
459
|
# =============================================================================
|
|
423
460
|
# Static File Handling
|
|
424
461
|
# =============================================================================
|
|
425
462
|
|
|
426
463
|
def copy_static_files(app: Sphinx):
|
|
427
|
-
"""Copy CSS
|
|
464
|
+
"""Copy CSS file to the build directory."""
|
|
428
465
|
if app.builder.name != 'html':
|
|
429
466
|
return
|
|
430
467
|
|
|
@@ -436,11 +473,7 @@ def copy_static_files(app: Sphinx):
|
|
|
436
473
|
css_file = static_source_dir / "filter_tabs.css"
|
|
437
474
|
if css_file.exists():
|
|
438
475
|
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)
|
|
476
|
+
|
|
444
477
|
|
|
445
478
|
|
|
446
479
|
# =============================================================================
|
|
@@ -456,7 +489,6 @@ def setup(app: Sphinx) -> Dict[str, Any]:
|
|
|
456
489
|
|
|
457
490
|
# Add static files
|
|
458
491
|
app.add_css_file('filter_tabs.css')
|
|
459
|
-
app.add_js_file('filter_tabs.js')
|
|
460
492
|
|
|
461
493
|
# Register custom nodes (keep existing node registration code)
|
|
462
494
|
app.add_node(ContainerNode, html=(visit_container_node, depart_container_node))
|
|
@@ -475,9 +507,11 @@ def setup(app: Sphinx) -> Dict[str, Any]:
|
|
|
475
507
|
# Connect event handlers
|
|
476
508
|
app.connect('builder-inited', copy_static_files)
|
|
477
509
|
app.connect('doctree-resolved', setup_collapsible_admonitions)
|
|
510
|
+
app.connect('doctree-resolved', improve_inline_formatting)
|
|
478
511
|
|
|
479
512
|
return {
|
|
480
513
|
'version': __version__,
|
|
481
514
|
'parallel_read_safe': True,
|
|
482
515
|
'parallel_write_safe': True,
|
|
483
516
|
}
|
|
517
|
+
|
filter_tabs/renderer.py
CHANGED
|
@@ -88,13 +88,12 @@ class FilterTabsRenderer:
|
|
|
88
88
|
def __init__(self, directive: Directive, tab_data: List[TabData], general_content: List[nodes.Node], custom_legend: Optional[str] = None):
|
|
89
89
|
self.directive = directive
|
|
90
90
|
self.env: BuildEnvironment = directive.state.document.settings.env
|
|
91
|
-
self.app = self.env.app
|
|
92
91
|
self.tab_data = tab_data
|
|
93
92
|
self.general_content = general_content
|
|
94
93
|
self.custom_legend = custom_legend
|
|
95
94
|
|
|
96
|
-
# 1. Load configuration
|
|
97
|
-
self.config = FilterTabsConfig.from_sphinx_config(self.
|
|
95
|
+
# 1. Load configuration using env.config (Sphinx 9+ safe)
|
|
96
|
+
self.config = FilterTabsConfig.from_sphinx_config(self.env.config)
|
|
98
97
|
|
|
99
98
|
# 2. Safely initialize the counter on the environment if it doesn't exist
|
|
100
99
|
if not hasattr(self.env, 'filter_tabs_counter'):
|
|
@@ -149,8 +148,6 @@ class FilterTabsRenderer:
|
|
|
149
148
|
'style': self.config.to_css_properties()
|
|
150
149
|
}
|
|
151
150
|
|
|
152
|
-
# REMOVED: _generate_compatible_css method - no longer needed!
|
|
153
|
-
|
|
154
151
|
def _create_fieldset(self) -> FieldsetNode:
|
|
155
152
|
"""Create the main fieldset containing the legend, radio buttons, and panels."""
|
|
156
153
|
fieldset = FieldsetNode(role="radiogroup")
|
|
@@ -196,13 +193,16 @@ class FilterTabsRenderer:
|
|
|
196
193
|
radio_group += self._create_screen_reader_description(i, tab)
|
|
197
194
|
|
|
198
195
|
def _create_radio_button(self, index: int, tab: TabData, is_checked: bool) -> RadioInputNode:
|
|
199
|
-
"""Create a single radio button input."""
|
|
196
|
+
"""Create a single radio button input with data attribute."""
|
|
200
197
|
radio = RadioInputNode(
|
|
201
198
|
classes=['sr-only'],
|
|
202
199
|
type='radio',
|
|
203
200
|
name=self.group_id,
|
|
204
201
|
ids=[self.id_gen.radio_id(index)],
|
|
205
|
-
**{
|
|
202
|
+
**{
|
|
203
|
+
'aria-describedby': self.id_gen.desc_id(index),
|
|
204
|
+
'data-tab-index': str(index) # FIXED: Add data attribute
|
|
205
|
+
}
|
|
206
206
|
)
|
|
207
207
|
if tab.aria_label:
|
|
208
208
|
radio['aria-label'] = tab.aria_label
|
|
@@ -224,9 +224,16 @@ class FilterTabsRenderer:
|
|
|
224
224
|
return description_node
|
|
225
225
|
|
|
226
226
|
def _populate_content_area(self, content_area: ContainerNode) -> None:
|
|
227
|
-
"""Create and add all general and tab-specific content panels."""
|
|
227
|
+
"""Create and add all general and tab-specific content panels with accessibility enhancements."""
|
|
228
228
|
if self.general_content:
|
|
229
|
-
general_panel = PanelNode(
|
|
229
|
+
general_panel = PanelNode(
|
|
230
|
+
classes=[SFT_PANEL],
|
|
231
|
+
**{
|
|
232
|
+
'data-filter': 'General',
|
|
233
|
+
'aria-label': 'General information',
|
|
234
|
+
'role': 'region'
|
|
235
|
+
}
|
|
236
|
+
)
|
|
230
237
|
general_panel.extend(copy.deepcopy(self.general_content))
|
|
231
238
|
content_area += general_panel
|
|
232
239
|
|
|
@@ -234,15 +241,17 @@ class FilterTabsRenderer:
|
|
|
234
241
|
content_area += self._create_tab_panel(i, tab)
|
|
235
242
|
|
|
236
243
|
def _create_tab_panel(self, index: int, tab: TabData) -> PanelNode:
|
|
237
|
-
"""Create a single content panel for a tab."""
|
|
244
|
+
"""Create a single content panel for a tab - CSS only version."""
|
|
238
245
|
panel_attrs = {
|
|
239
246
|
'classes': [SFT_PANEL],
|
|
240
247
|
'ids': [self.id_gen.panel_id(index)],
|
|
241
248
|
'role': 'tabpanel',
|
|
242
249
|
'aria-labelledby': self.id_gen.radio_id(index),
|
|
243
|
-
'tabindex': '0',
|
|
244
|
-
'data-tab': tab.name.lower().replace(' ', '-')
|
|
250
|
+
'tabindex': '0', # Keep for keyboard accessibility
|
|
251
|
+
'data-tab': tab.name.lower().replace(' ', '-'),
|
|
252
|
+
'data-tab-index': str(index)
|
|
245
253
|
}
|
|
246
254
|
panel = PanelNode(**panel_attrs)
|
|
247
255
|
panel.extend(copy.deepcopy(tab.content))
|
|
248
256
|
return panel
|
|
257
|
+
|
|
@@ -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.3
|
|
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<10.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 versions 3.10, 3.12
|
|
232
|
+
- Sphinx versions 7.0, 7.4, 8.0, 8.2, 9.0, 9.1
|
|
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=dcSbnQgCy0TL2eOoFvP7eN3gVCRwtLrs4QLHXKK9bGM,19428
|
|
3
|
+
filter_tabs/renderer.py,sha256=sapT3k-pAkVIXRypuTrtuc8XHZB23Sp8mgOSU3ZIqGg,10096
|
|
4
|
+
filter_tabs/static/filter_tabs.css,sha256=ZoiSWcn2YBEWgkQ-vPbIPHwQ7s2TG4aUikyxM1A8b9I,7956
|
|
5
|
+
sphinx_filter_tabs-1.3.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
|
|
6
|
+
sphinx_filter_tabs-1.3.dist-info/METADATA,sha256=BEz8EQjjQea2bXmfX2OERvbgxsjddTdE_vw69zjUqbs,7635
|
|
7
|
+
sphinx_filter_tabs-1.3.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
8
|
+
sphinx_filter_tabs-1.3.dist-info/entry_points.txt,sha256=za_bQcueY8AHyq7XnnjkW9X3C-LsZjeERVQ_ds7jV1A,62
|
|
9
|
+
sphinx_filter_tabs-1.3.dist-info/top_level.txt,sha256=K0Iy-6EsYYdvlyXdsJT0SQg-BLDBgT5-Y8ZKy5SNAfc,12
|
|
10
|
+
sphinx_filter_tabs-1.3.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
|