Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,14 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/)
and this project adheres to [Semantic Versioning](http://semver.org/).

# Unreleased

### Added

- Added `Button.flat` boolean to enable flat button style https://github.com/Textualize/textual/pull/6094
- Added `namespaces` parameter to `run_action` https://github.com/Textualize/textual/pull/6094
- Added "block" border style https://github.com/Textualize/textual/pull/6094

# [6.0.0] - 2025-08-31

### Fixed
Expand Down
16 changes: 16 additions & 0 deletions docs/examples/widgets/button.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,22 @@ def compose(self) -> ComposeResult:
Button.warning("Warning!", disabled=True),
Button.error("Error!", disabled=True),
),
VerticalScroll(
Static("Flat Buttons", classes="header"),
Button("Default", flat=True),
Button("Primary!", variant="primary", flat=True),
Button.success("Success!", flat=True),
Button.warning("Warning!", flat=True),
Button.error("Error!", flat=True),
),
VerticalScroll(
Static("Disabled Flat Buttons", classes="header"),
Button("Default", disabled=True, flat=True),
Button("Primary!", variant="primary", disabled=True, flat=True),
Button.success("Success!", disabled=True, flat=True),
Button.warning("Warning!", disabled=True, flat=True),
Button.error("Error!", disabled=True, flat=True),
),
)

def on_button_pressed(self, event: Button.Pressed) -> None:
Expand Down
10 changes: 10 additions & 0 deletions src/textual/_border.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,11 @@
("█", " ", "█"),
("█", "▄", "█"),
),
"block": (
("▄", "▄", "▄"),
("█", " ", "█"),
("▀", "▀", "▀"),
),
"hkey": (
("▔", "▔", "▔"),
(" ", " ", " "),
Expand Down Expand Up @@ -190,6 +195,11 @@
(0, 0, 0),
(0, 0, 0),
),
"block": (
(1, 1, 1),
(0, 0, 0),
(1, 1, 1),
),
"hkey": (
(0, 0, 0),
(0, 0, 0),
Expand Down
18 changes: 14 additions & 4 deletions src/textual/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
Generic,
Iterable,
Iterator,
Mapping,
NamedTuple,
Sequence,
TextIO,
Expand Down Expand Up @@ -3969,12 +3970,17 @@ def escape_to_minimize(self) -> bool:
)

def _parse_action(
self, action: str | ActionParseResult, default_namespace: DOMNode
self,
action: str | ActionParseResult,
default_namespace: DOMNode,
namespaces: Mapping[str, DOMNode] | None = None,
) -> tuple[DOMNode, str, tuple[object, ...]]:
"""Parse an action.

Args:
action: An action string.
default_namespace: Namespace to user when none is supplied in the action.
namespaces: Mapping of namespaces.

Raises:
ActionError: If there are any errors parsing the action string.
Expand All @@ -3987,8 +3993,10 @@ def _parse_action(
else:
destination, action_name, params = actions.parse(action)

action_target: DOMNode | None = None
if destination:
action_target: DOMNode | None = (
None if namespaces is None else namespaces.get(destination)
)
if destination and action_target is None:
if destination not in self._action_targets:
raise ActionError(f"Action namespace {destination} is not known")
action_target = getattr(self, destination, None)
Expand Down Expand Up @@ -4021,6 +4029,7 @@ async def run_action(
self,
action: str | ActionParseResult,
default_namespace: DOMNode | None = None,
namespaces: Mapping[str, DOMNode] | None = None,
) -> bool:
"""Perform an [action](/guide/actions).

Expand All @@ -4030,12 +4039,13 @@ async def run_action(
action: Action encoded in a string.
default_namespace: Namespace to use if not provided in the action,
or None to use app.
namespaces: Mapping of namespaces.

Returns:
True if the event has been handled.
"""
action_target, action_name, params = self._parse_action(
action, self if default_namespace is None else default_namespace
action, self if default_namespace is None else default_namespace, namespaces
)
if action_target.check_action(action_name, params):
return await self._dispatch_action(action_target, action_name, params)
Expand Down
1 change: 1 addition & 0 deletions src/textual/css/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
"tall",
"tab",
"thick",
"block",
"vkey",
"wide",
}
Expand Down
1 change: 1 addition & 0 deletions src/textual/css/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
"round",
"solid",
"thick",
"block",
"double",
"dashed",
"heavy",
Expand Down
8 changes: 6 additions & 2 deletions src/textual/widget.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
Collection,
Generator,
Iterable,
Mapping,
NamedTuple,
Sequence,
TypeVar,
Expand Down Expand Up @@ -4304,13 +4305,16 @@ def _render(self) -> Visual:
self._layout_cache[cache_key] = visual
return visual

async def run_action(self, action: str) -> None:
async def run_action(
self, action: str, namespaces: Mapping[str, DOMNode] | None = None
) -> None:
"""Perform a given action, with this widget as the default namespace.

Args:
action: Action encoded as a string.
namespaces: Mapping of namespaces.
"""
await self.app.run_action(action, self)
await self.app.run_action(action, self, namespaces)

def post_message(self, message: Message) -> bool:
"""Post a message to this widget.
Expand Down
Loading
Loading