From f95e30870b72741ca1b06738aa7de1aeb8695533 Mon Sep 17 00:00:00 2001 From: darrenburns Date: Wed, 12 Apr 2023 10:55:14 +0100 Subject: [PATCH] Tabbed content activated message (#2260) * Add a message for the tabbed content activated * Add a docstring * Testing tabbed content activated message * Update changelog * Add reference to the docs about TabbedContent.TabActivated --- CHANGELOG.md | 1 + docs/widgets/tabbed_content.md | 4 +++ docs/widgets/tabs.md | 2 +- src/textual/widgets/_tabbed_content.py | 26 ++++++++++++++++++++ tests/test_tabbed_content.py | 34 +++++++++++++++++++++++--- 5 files changed, 63 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6bc2f69e9..412f9928f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - Added `DataTable.remove_row` method https://github.com/Textualize/textual/pull/2253 - `Widget.scroll_to_center` now scrolls the widget to the center of the screen https://github.com/Textualize/textual/pull/2255 +- Added `TabActivated` message to `TabbedContent` https://github.com/Textualize/textual/pull/2260 ## [0.19.1] - 2023-04-10 diff --git a/docs/widgets/tabbed_content.md b/docs/widgets/tabbed_content.md index 73667e023..7a61318df 100644 --- a/docs/widgets/tabbed_content.md +++ b/docs/widgets/tabbed_content.md @@ -101,6 +101,10 @@ The following example contains a `TabbedContent` with three tabs. | `active` | `str` | `""` | The `id` attribute of the active tab. Set this to switch tabs. | +## Messages + +- [TabbedContent.TabActivated][textual.widgets.TabbedContent.TabActivated] + ## See also diff --git a/docs/widgets/tabs.md b/docs/widgets/tabs.md index a77e31755..b7d7130d7 100644 --- a/docs/widgets/tabs.md +++ b/docs/widgets/tabs.md @@ -61,7 +61,7 @@ The following example adds a `Tabs` widget above a text label. Press ++a++ to ad ## Messages -- [Tabs.TabActivate][textual.widgets.Tabs.TabActivated] +- [Tabs.TabActivated][textual.widgets.Tabs.TabActivated] - [Tabs.Cleared][textual.widgets.Tabs.Cleared] ## Bindings diff --git a/src/textual/widgets/_tabbed_content.py b/src/textual/widgets/_tabbed_content.py index f2b9b06cd..ffaa1112b 100644 --- a/src/textual/widgets/_tabbed_content.py +++ b/src/textual/widgets/_tabbed_content.py @@ -2,9 +2,11 @@ from __future__ import annotations from itertools import zip_longest +from rich.repr import Result from rich.text import Text, TextType from ..app import ComposeResult +from ..message import Message from ..reactive import reactive from ..widget import Widget from ._content_switcher import ContentSwitcher @@ -84,6 +86,24 @@ class TabbedContent(Widget): active: reactive[str] = reactive("", init=False) """The ID of the active tab, or empty string if none are active.""" + class TabActivated(Message): + """Posted when the active tab changes.""" + + def __init__(self, tabbed_content: TabbedContent, tab: Tab) -> None: + """Initialize message. + + Args: + tabbed_content: The TabbedContent widget. + tab: The Tab widget that was selected (contains the tab label). + """ + self.tabbed_content = tabbed_content + self.tab = tab + super().__init__() + + def __rich_repr__(self) -> Result: + yield self.tabbed_content + yield self.tab + def __init__(self, *titles: TextType, initial: str = "") -> None: """Initialize a TabbedContent widgets. @@ -167,6 +187,12 @@ class TabbedContent(Widget): assert isinstance(event.tab, ContentTab) switcher.current = event.tab.id self.active = event.tab.id + self.post_message( + TabbedContent.TabActivated( + tabbed_content=self, + tab=event.tab, + ) + ) def _on_tabs_cleared(self, event: Tabs.Cleared) -> None: """All tabs were removed.""" diff --git a/tests/test_tabbed_content.py b/tests/test_tabbed_content.py index 06a676a6f..23af3151c 100644 --- a/tests/test_tabbed_content.py +++ b/tests/test_tabbed_content.py @@ -14,7 +14,7 @@ async def test_tabbed_content_switch(): yield Label("Foo", id="foo-label") with TabPane("bar", id="bar"): yield Label("Bar", id="bar-label") - with TabPane("baz`", id="baz"): + with TabPane("baz", id="baz"): yield Label("Baz", id="baz-label") app = TabbedApp() @@ -73,11 +73,11 @@ async def test_tabbed_content_initial(): yield Label("Foo", id="foo-label") with TabPane("bar", id="bar"): yield Label("Bar", id="bar-label") - with TabPane("baz`", id="baz"): + with TabPane("baz", id="baz"): yield Label("Baz", id="baz-label") app = TabbedApp() - async with app.run_test() as pilot: + async with app.run_test(): tabbed_content = app.query_one(TabbedContent) assert tabbed_content.active == "bar" @@ -85,3 +85,31 @@ async def test_tabbed_content_initial(): assert not app.query_one("#foo-label").region assert app.query_one("#bar-label").region assert not app.query_one("#baz-label").region + + +async def test_tabbed_content_messages(): + class TabbedApp(App): + message = None + + def compose(self) -> ComposeResult: + with TabbedContent(initial="bar"): + with TabPane("foo", id="foo"): + yield Label("Foo", id="foo-label") + + with TabPane("bar", id="bar"): + yield Label("Bar", id="bar-label") + with TabPane("baz", id="baz"): + yield Label("Baz", id="baz-label") + + def on_tabbed_content_tab_activated( + self, event: TabbedContent.TabActivated + ) -> None: + self.message = event + + app = TabbedApp() + async with app.run_test() as pilot: + tabbed_content = app.query_one(TabbedContent) + tabbed_content.active = "bar" + await pilot.pause() + assert isinstance(app.message, TabbedContent.TabActivated) + assert app.message.tab.label.plain == "bar"