Skip to content

Commit

Permalink
Add Collapsible.Toggled. (#3825)
Browse files Browse the repository at this point in the history
Add Collapsible.Toggled, Collapsible.Expanded, and Collapsible.Collapsed.
  • Loading branch information
rodrigogiraoserrao authored Dec 7, 2023
1 parent de83fae commit 880fc89
Show file tree
Hide file tree
Showing 4 changed files with 117 additions and 2 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
### Added

- Added `get_loading_widget` to Widget and App customize the loading widget. https://github.com/Textualize/textual/pull/3816
- Added messages `Collapsible.Expanded` and `Collapsible.Collapsed` that inherit from `Collapsible.Toggled`. https://github.com/Textualize/textual/issues/3824

## [0.44.1] - 2023-12-4

Expand Down
2 changes: 1 addition & 1 deletion docs/widgets/collapsible.md
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ The following example shows `Collapsible` widgets with custom expand/collapse sy

## Messages

This widget posts no messages.
- [Collapsible.Toggled][textual.widgets.Collapsible.Toggled]

## Bindings

Expand Down
42 changes: 41 additions & 1 deletion src/textual/widgets/_collapsible.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,42 @@ class Collapsible(Widget):
}
"""

class Toggled(Message):
"""Parent class subclassed by `Collapsible` messages.
Can be handled with `on(Collapsible.Toggled)` if you want to handle expansions
and collapsed in the same way, or you can handle the specific events individually.
"""

def __init__(self, collapsible: Collapsible) -> None:
"""Create an instance of the message.
Args:
collapsible: The `Collapsible` widget that was toggled.
"""
self.collapsible: Collapsible = collapsible
"""The collapsible that was toggled."""
super().__init__()

@property
def control(self) -> Collapsible:
"""An alias for [Toggled.collapsible][textual.widgets.Collapsible.Toggled.collapsible]."""
return self.collapsible

class Expanded(Toggled):
"""Event sent when the `Collapsible` widget is expanded.
Can be handled using `on_collapsible_expanded` in a subclass of
[`Collapsible`][textual.widgets.Collapsible] or in a parent widget in the DOM.
"""

class Collapsed(Toggled):
"""Event sent when the `Collapsible` widget is collapsed.
Can be handled using `on_collapsible_collapsed` in a subclass of
[`Collapsible`][textual.widgets.Collapsible] or in a parent widget in the DOM.
"""

class Contents(Container):
DEFAULT_CSS = """
Contents {
Expand Down Expand Up @@ -143,9 +179,13 @@ def __init__(
super().__init__(name=name, id=id, classes=classes, disabled=disabled)
self.collapsed = collapsed

def on_collapsible_title_toggle(self, event: CollapsibleTitle.Toggle) -> None:
def _on_collapsible_title_toggle(self, event: CollapsibleTitle.Toggle) -> None:
event.stop()
self.collapsed = not self.collapsed
if self.collapsed:
self.post_message(self.Collapsed(self))
else:
self.post_message(self.Expanded(self))

def _watch_collapsed(self, collapsed: bool) -> None:
"""Update collapsed state when reactive is changed."""
Expand Down
74 changes: 74 additions & 0 deletions tests/test_collapsible.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from __future__ import annotations

from textual import on
from textual.app import App, ComposeResult
from textual.widgets import Collapsible, Label
from textual.widgets._collapsible import CollapsibleTitle
Expand Down Expand Up @@ -115,3 +116,76 @@ def compose(self) -> ComposeResult:

await pilot.click(CollapsibleTitle)
assert not collapsible.collapsed


async def test_toggle_message():
"""Toggling should post a message."""

hits = []

class CollapsibleApp(App[None]):
def compose(self) -> ComposeResult:
yield Collapsible(collapsed=True)

@on(Collapsible.Toggled)
def catch_collapsible_events(self) -> None:
hits.append("toggled")

async with CollapsibleApp().run_test() as pilot:
assert pilot.app.query_one(Collapsible).collapsed

await pilot.click(CollapsibleTitle)
await pilot.pause()

assert not pilot.app.query_one(Collapsible).collapsed
assert len(hits) == 1

await pilot.click(CollapsibleTitle)
await pilot.pause()

assert pilot.app.query_one(Collapsible).collapsed
assert len(hits) == 2


async def test_expand_message():
"""Toggling should post a message."""

hits = []

class CollapsibleApp(App[None]):
def compose(self) -> ComposeResult:
yield Collapsible(collapsed=True)

def on_collapsible_expanded(self) -> None:
hits.append("expanded")

async with CollapsibleApp().run_test() as pilot:
assert pilot.app.query_one(Collapsible).collapsed

await pilot.click(CollapsibleTitle)
await pilot.pause()

assert not pilot.app.query_one(Collapsible).collapsed
assert len(hits) == 1


async def test_collapse_message():
"""Toggling should post a message."""

hits = []

class CollapsibleApp(App[None]):
def compose(self) -> ComposeResult:
yield Collapsible(collapsed=False)

def on_collapsible_collapsed(self) -> None:
hits.append("collapsed")

async with CollapsibleApp().run_test() as pilot:
assert not pilot.app.query_one(Collapsible).collapsed

await pilot.click(CollapsibleTitle)
await pilot.pause()

assert pilot.app.query_one(Collapsible).collapsed
assert len(hits) == 1

0 comments on commit 880fc89

Please sign in to comment.