diff --git a/docs/usage.md b/docs/usage.md index 968e253..51feb1a 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -164,10 +164,10 @@ Why not. ## Synchronisation -Tabs across multiple sets are synchronised according to the label, unconditionally. This requires JavaScript to be enabled on the end user's browser and, thus, should be considered a progressive enhancement. +Tabs across multiple sets are synchronised according to the label. This requires JavaScript to be enabled on the end user's browser and, thus, should be considered a progressive enhancement. ```{hint} -Nearly all usage of tabbed content in documentation is based on something about the user which stays consistent throughout the reading (like their OS, or preferred language etc). That's why this behaviour is unconditional. +Nearly all usage of tabbed content in documentation is based on something about the user which stays consistent throughout the reading (like their OS, or preferred language etc). That's why this behaviour is the default. ``` ````{tab} Windows @@ -195,6 +195,73 @@ $ make html ``` ```` +### Customisation + +```{versionadded} 2024.12.08 + +``` + +Synchronisation can be disable by adding the `:no-sync:` option to a tab. It can +also be disabled from the config file. + +```py +# disable sync globally +tabs_default_sync_behavior = "none" # default "tab-title" + +# disable sync globally for labels "One" and "Two" +tabs_no_sync_labels = {"One", "Two"} # default set() +``` + +The sync label can be overwritten with the `:sync: ` option. + +```rst +.. tab:: One + :no-sync: + + Sync is disabled for this one. + +.. tab:: Two + :sync: Five + + Sync with label `Five`. + +.. tab:: Three + + Three is an odd prime. + +.. tab:: Four + + Four is not a perfect number. +``` + +```{tab} One +:no-sync: +Sync is disabled for this one. +``` + +```{tab} Two +:sync: Five +Sync with label `Five`. +``` + +```{tab} Three +Three is an odd prime. +``` + +```{tab} Four +Four is not a perfect number. +``` + + + +```{tab} One +This tab is synchronized. +``` + +```{tab} Five +Five is a nice number. +``` + ## Nesting ```{versionadded} 2020.04.11.beta8 diff --git a/src/sphinx_inline_tabs/__init__.py b/src/sphinx_inline_tabs/__init__.py index 6096ad3..d0cb7f5 100644 --- a/src/sphinx_inline_tabs/__init__.py +++ b/src/sphinx_inline_tabs/__init__.py @@ -13,6 +13,8 @@ def setup(app): # We do imports from Sphinx, after validating the Sphinx version from ._impl import TabContainer, TabDirective, TabHtmlTransform, TabInput, TabLabel + app.add_config_value("tabs_default_sync_behavior", "tab-title", "html", types=[str]) + app.add_config_value("tabs_no_sync_labels", set(), "html", types=[set]) app.add_directive("tab", TabDirective) app.add_post_transform(TabHtmlTransform) app.add_node(TabInput, html=(TabInput.visit, TabInput.depart)) diff --git a/src/sphinx_inline_tabs/_impl.py b/src/sphinx_inline_tabs/_impl.py index 48e9537..b799594 100644 --- a/src/sphinx_inline_tabs/_impl.py +++ b/src/sphinx_inline_tabs/_impl.py @@ -1,14 +1,17 @@ """The actual implementation.""" import itertools -from typing import List +from typing import List, Set from docutils import nodes from docutils.parsers.rst import directives from sphinx.transforms.post_transforms import SphinxPostTransform +from sphinx.util import logging from sphinx.util.docutils import SphinxDirective from sphinx.util.nodes import NodeMatcher +logger = logging.getLogger(__name__) + class TabContainer(nodes.container): """The initial tree-node for holding tab content.""" @@ -60,13 +63,42 @@ class TabDirective(SphinxDirective): has_content = True option_spec = { "new-set": directives.flag, + "sync": directives.unchanged_required, + "no-sync": directives.flag, } def run(self): """Parse a tabs directive.""" self.assert_has_content() - container = TabContainer("", type="tab", new_set="new-set" in self.options) + default_sync_behavior = self.env.app.config.tabs_default_sync_behavior + no_sync_labels = self.env.app.config.tabs_no_sync_labels + + sync_label = self.options.get("sync") or self.arguments[0] + + no_sync = ( + default_sync_behavior == "none" + or sync_label in no_sync_labels + or "no-sync" in self.options + ) + + if no_sync and self.options.get("sync"): + # no_sync with explicit sync label + logger.warning( + "Sychronisation is disabled for tab group '%s' in %s:%s [sphinx_inline_tabs]", + self.arguments[0], + self.reporter.source, + self.lineno, + ) + + container = TabContainer( + "", + type="tab", + new_set="new-set" in self.options, + label_text=self.arguments[0], + sync_label=sync_label, + no_sync=no_sync, + ) self.set_source_info(container) # Handle the label (non-plain-text variants allowed) @@ -164,11 +196,26 @@ def finalize_set(self, tab_set: List[TabContainer], set_counter: int): tab_set_name = f"tab-set--{set_counter}" node_counter = 0 + labels: Set[str] = set() for node in tab_set: node_counter += 1 tab_id = tab_set_name + f"-input--{node_counter}" title, content = node.children + if node.attributes["no_sync"] is False: + sync_label = node.attributes["sync_label"] + if sync_label in labels: + logger.warning( + "Duplicate sync label in tab group '%s' in %s:%s [sphinx_inline_tabs]", + node.attributes["label_text"], + node.source, + node.line, + ) + labels.add(sync_label) + sync_attr = {"sync": sync_label} + else: + sync_attr = {"no_sync": True} + # , for storing state in radio boxes. input_node = TabInput( type="radio", ids=[tab_id], name=tab_set_name, classes=["tab-input"] @@ -176,7 +223,10 @@ def finalize_set(self, tab_set: List[TabContainer], set_counter: int): #