From 44129f0e4b9a5cbaa8ccee85e42ac7a43d1d79cf Mon Sep 17 00:00:00 2001 From: Will McGugan Date: Mon, 1 Sep 2025 17:52:06 +0100 Subject: [PATCH 1/7] flat buttons --- docs/examples/widgets/button.py | 16 +++ src/textual/_border.py | 10 ++ src/textual/app.py | 18 ++- src/textual/css/constants.py | 1 + src/textual/css/types.py | 1 + src/textual/widget.py | 8 +- src/textual/widgets/_button.py | 219 ++++++++++++++++++++------------ 7 files changed, 187 insertions(+), 86 deletions(-) diff --git a/docs/examples/widgets/button.py b/docs/examples/widgets/button.py index afed67ac9c..6bc1e69151 100644 --- a/docs/examples/widgets/button.py +++ b/docs/examples/widgets/button.py @@ -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: diff --git a/src/textual/_border.py b/src/textual/_border.py index d14b17be8e..7fc18c777b 100644 --- a/src/textual/_border.py +++ b/src/textual/_border.py @@ -87,6 +87,11 @@ ("█", " ", "█"), ("█", "▄", "█"), ), + "block": ( + ("▄", "▄", "▄"), + ("█", " ", "█"), + ("▀", "▀", "▀"), + ), "hkey": ( ("▔", "▔", "▔"), (" ", " ", " "), @@ -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), diff --git a/src/textual/app.py b/src/textual/app.py index 8e7cde95f1..f6f7fec722 100644 --- a/src/textual/app.py +++ b/src/textual/app.py @@ -41,6 +41,7 @@ Generic, Iterable, Iterator, + Mapping, NamedTuple, Sequence, TextIO, @@ -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. @@ -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) @@ -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). @@ -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) diff --git a/src/textual/css/constants.py b/src/textual/css/constants.py index 7928dfd90a..2145302306 100644 --- a/src/textual/css/constants.py +++ b/src/textual/css/constants.py @@ -24,6 +24,7 @@ "tall", "tab", "thick", + "block", "vkey", "wide", } diff --git a/src/textual/css/types.py b/src/textual/css/types.py index d75b0c38ab..4bf9e6671e 100644 --- a/src/textual/css/types.py +++ b/src/textual/css/types.py @@ -16,6 +16,7 @@ "round", "solid", "thick", + "block", "double", "dashed", "heavy", diff --git a/src/textual/widget.py b/src/textual/widget.py index 6d77efcd60..1d272f8c2f 100644 --- a/src/textual/widget.py +++ b/src/textual/widget.py @@ -19,6 +19,7 @@ Collection, Generator, Iterable, + Mapping, NamedTuple, Sequence, TypeVar, @@ -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. diff --git a/src/textual/widgets/_button.py b/src/textual/widgets/_button.py index e36085b7c6..c1d4333290 100644 --- a/src/textual/widgets/_button.py +++ b/src/textual/widgets/_button.py @@ -50,109 +50,149 @@ class Button(Widget, can_focus=True): Button { width: auto; min-width: 16; - height: auto; - color: $button-foreground; - background: $surface; - border: none; - border-top: tall $surface-lighten-1; - border-bottom: tall $surface-darken-1; + height:auto; + line-pad: 1; text-align: center; content-align: center middle; - text-style: bold; - line-pad: 1; - - &.-textual-compact { - border: none !important; - } - - &:disabled { - text-opacity: 0.6; - } + - &:focus { - text-style: $button-focus-text-style; - background-tint: $foreground 5%; - } - &:hover { - border-top: tall $surface; - background: $surface-darken-1; - } - &.-active { + &.-style-flat { + + color: auto 90%; background: $surface; - border-bottom: tall $surface-lighten-1; - border-top: tall $surface-darken-1; - tint: $background 30%; - } - - &.-primary { - color: $button-color-foreground; - background: $primary; - border-top: tall $primary-lighten-3; - border-bottom: tall $primary-darken-3; - + border: block $surface; &:hover { - background: $primary-darken-2; - border-top: tall $primary; + opacity: 90%; + } + &:focus { + text-style: $button-focus-text-style; } - &.-active { - background: $primary; - border-bottom: tall $primary-lighten-3; - border-top: tall $primary-darken-3; + background: $surface; + border: block $surface; + tint: $background 30%; } - } - &.-success { - color: $button-color-foreground; - background: $success; - border-top: tall $success-lighten-2; - border-bottom: tall $success-darken-3; - - &:hover { - background: $success-darken-2; - border-top: tall $success; + &.-primary { + background: $primary-muted; + border: block $primary-muted; } - - &.-active { - background: $success; - border-bottom: tall $success-lighten-2; - border-top: tall $success-darken-2; + &.-success { + background: $success-muted; + border: block $success-muted; + } + &.-warning { + background: $warning-muted; + border: block $warning-muted; + } + &.-error { + background: $error-muted; + border: block $error-muted; } } + &.-style-default { + text-style: bold; + color: $button-foreground; + background: $surface; + border: none; + border-top: tall $surface-lighten-1; + border-bottom: tall $surface-darken-1; + - &.-warning{ - color: $button-color-foreground; - background: $warning; - border-top: tall $warning-lighten-2; - border-bottom: tall $warning-darken-3; + &.-textual-compact { + border: none !important; + } - &:hover { - background: $warning-darken-2; - border-top: tall $warning; + &:disabled { + text-opacity: 0.4; } + &:focus { + text-style: $button-focus-text-style; + background-tint: $foreground 5%; + } + &:hover { + border-top: tall $surface; + background: $surface-darken-1; + } + &.-active { - background: $warning; - border-bottom: tall $warning-lighten-2; - border-top: tall $warning-darken-2; + background: $surface; + border-bottom: tall $surface-lighten-1; + border-top: tall $surface-darken-1; + tint: $background 30%; } - } - &.-error { - color: $button-color-foreground; - background: $error; - border-top: tall $error-lighten-2; - border-bottom: tall $error-darken-3; + &.-primary { + color: $button-color-foreground; + background: $primary; + border-top: tall $primary-lighten-3; + border-bottom: tall $primary-darken-3; + + &:hover { + background: $primary-darken-2; + border-top: tall $primary; + } + + &.-active { + background: $primary; + border-bottom: tall $primary-lighten-3; + border-top: tall $primary-darken-3; + } + } - &:hover { - background: $error-darken-1; - border-top: tall $error; + &.-success { + color: $button-color-foreground; + background: $success; + border-top: tall $success-lighten-2; + border-bottom: tall $success-darken-3; + + &:hover { + background: $success-darken-2; + border-top: tall $success; + } + + &.-active { + background: $success; + border-bottom: tall $success-lighten-2; + border-top: tall $success-darken-2; + } } - &.-active { + &.-warning{ + color: $button-color-foreground; + background: $warning; + border-top: tall $warning-lighten-2; + border-bottom: tall $warning-darken-3; + + &:hover { + background: $warning-darken-2; + border-top: tall $warning; + } + + &.-active { + background: $warning; + border-bottom: tall $warning-lighten-2; + border-top: tall $warning-darken-2; + } + } + + &.-error { + color: $button-color-foreground; background: $error; - border-bottom: tall $error-lighten-2; - border-top: tall $error-darken-2; + border-top: tall $error-lighten-2; + border-bottom: tall $error-darken-3; + + &:hover { + background: $error-darken-1; + border-top: tall $error; + } + + &.-active { + background: $error; + border-bottom: tall $error-lighten-2; + border-top: tall $error-darken-2; + } } } } @@ -169,6 +209,9 @@ class Button(Widget, can_focus=True): compact = reactive(False, toggle_class="-textual-compact") """Make the button compact (without borders).""" + flat = reactive(False) + """Enable alternative flat button style.""" + class Pressed(Message): """Event sent when a `Button` is pressed and there is no Button action. @@ -201,6 +244,7 @@ def __init__( tooltip: RenderableType | None = None, action: str | None = None, compact: bool = False, + flat: bool = False, ): """Create a Button widget. @@ -214,6 +258,7 @@ def __init__( tooltip: Optional tooltip. action: Optional action to run when clicked. compact: Enable compact button style. + flat: Enable alternative flat look buttons. """ super().__init__(name=name, id=id, classes=classes, disabled=disabled) @@ -224,6 +269,7 @@ def __init__( self.variant = variant self.action = action self.compact = compact + self.flat = flat self.active_effect_duration = 0.2 """Amount of time in seconds the button 'press' animation lasts.""" @@ -253,6 +299,10 @@ def watch_variant(self, old_variant: str, variant: str): self.remove_class(f"-{old_variant}") self.add_class(f"-{variant}") + def watch_flat(self, flat: bool) -> None: + self.set_class(flat, "-style-flat") + self.set_class(not flat, "-style-default") + def validate_label(self, label: ContentText) -> Content: """Parse markup for self.label""" return Content.from_text(label) @@ -314,6 +364,7 @@ def success( id: str | None = None, classes: str | None = None, disabled: bool = False, + flat: bool = False, ) -> Button: """Utility constructor for creating a success Button variant. @@ -324,6 +375,7 @@ def success( id: The ID of the button in the DOM. classes: The CSS classes of the button. disabled: Whether the button is disabled or not. + flat: Enable alternative flat look buttons. Returns: A [`Button`][textual.widgets.Button] widget of the 'success' @@ -336,6 +388,7 @@ def success( id=id, classes=classes, disabled=disabled, + flat=flat, ) @classmethod @@ -347,6 +400,7 @@ def warning( id: str | None = None, classes: str | None = None, disabled: bool = False, + flat: bool = False, ) -> Button: """Utility constructor for creating a warning Button variant. @@ -357,6 +411,7 @@ def warning( id: The ID of the button in the DOM. classes: The CSS classes of the button. disabled: Whether the button is disabled or not. + flat: Enable alternative flat look buttons. Returns: A [`Button`][textual.widgets.Button] widget of the 'warning' @@ -369,6 +424,7 @@ def warning( id=id, classes=classes, disabled=disabled, + flat=flat, ) @classmethod @@ -380,6 +436,7 @@ def error( id: str | None = None, classes: str | None = None, disabled: bool = False, + flat: bool = False, ) -> Button: """Utility constructor for creating an error Button variant. @@ -390,6 +447,7 @@ def error( id: The ID of the button in the DOM. classes: The CSS classes of the button. disabled: Whether the button is disabled or not. + flat: Enable alternative flat look buttons. Returns: A [`Button`][textual.widgets.Button] widget of the 'error' @@ -402,4 +460,5 @@ def error( id=id, classes=classes, disabled=disabled, + flat=flat, ) From 26d8b8350f478b5a02520db49ab4b6317484294a Mon Sep 17 00:00:00 2001 From: Will McGugan Date: Mon, 1 Sep 2025 18:05:25 +0100 Subject: [PATCH 2/7] style tweak --- src/textual/widgets/_button.py | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/src/textual/widgets/_button.py b/src/textual/widgets/_button.py index c1d4333290..1d901adb00 100644 --- a/src/textual/widgets/_button.py +++ b/src/textual/widgets/_button.py @@ -62,7 +62,8 @@ class Button(Widget, can_focus=True): background: $surface; border: block $surface; &:hover { - opacity: 90%; + background: $primary; + border: block $primary; } &:focus { text-style: $button-focus-text-style; @@ -72,22 +73,41 @@ class Button(Widget, can_focus=True): border: block $surface; tint: $background 30%; } + &:disabled { + color: auto 50%; + } &.-primary { background: $primary-muted; border: block $primary-muted; + &:hover { + background: $primary; + border: block $primary; + } } &.-success { background: $success-muted; border: block $success-muted; + &:hover { + background: $success; + border: block $success; + } } &.-warning { background: $warning-muted; border: block $warning-muted; + &:hover { + background: $warning; + border: block $warning; + } } &.-error { background: $error-muted; border: block $error-muted; + &:hover { + background: $error; + border: block $error; + } } } &.-style-default { @@ -104,7 +124,7 @@ class Button(Widget, can_focus=True): } &:disabled { - text-opacity: 0.4; + text-opacity: 0.6; } &:focus { From 69880aa8cc56dafe495def22231d999484631bff Mon Sep 17 00:00:00 2001 From: Will McGugan Date: Tue, 2 Sep 2025 08:20:43 +0100 Subject: [PATCH 3/7] button style --- src/textual/widgets/_button.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/textual/widgets/_button.py b/src/textual/widgets/_button.py index 1d901adb00..66ace702c7 100644 --- a/src/textual/widgets/_button.py +++ b/src/textual/widgets/_button.py @@ -57,7 +57,7 @@ class Button(Widget, can_focus=True): &.-style-flat { - + text-style: bold; color: auto 90%; background: $surface; border: block $surface; @@ -80,7 +80,9 @@ class Button(Widget, can_focus=True): &.-primary { background: $primary-muted; border: block $primary-muted; + color: $text-primary; &:hover { + color: $text; background: $primary; border: block $primary; } @@ -88,7 +90,9 @@ class Button(Widget, can_focus=True): &.-success { background: $success-muted; border: block $success-muted; + color: $text-success; &:hover { + color: $text; background: $success; border: block $success; } @@ -96,7 +100,9 @@ class Button(Widget, can_focus=True): &.-warning { background: $warning-muted; border: block $warning-muted; + color: $text-warning; &:hover { + color: $text; background: $warning; border: block $warning; } @@ -104,7 +110,9 @@ class Button(Widget, can_focus=True): &.-error { background: $error-muted; border: block $error-muted; + color: $text-error; &:hover { + color: $text; background: $error; border: block $error; } From 24cb64aa03e0b60b0f43cff674f7c5a7ff7cd437 Mon Sep 17 00:00:00 2001 From: Will McGugan Date: Tue, 2 Sep 2025 08:24:46 +0100 Subject: [PATCH 4/7] changelog --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 74658b4738..e387f69409 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,13 @@ 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 + # [6.0.0] - 2025-08-31 ### Fixed From 3302bd91a95960235bd9d4627c3faebcea21db12 Mon Sep 17 00:00:00 2001 From: Will McGugan Date: Tue, 2 Sep 2025 08:26:23 +0100 Subject: [PATCH 5/7] changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e387f69409..d553573bd3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - 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 From 596c2a7e9a9a73f62c6bbf41c9f9ee1c4551d003 Mon Sep 17 00:00:00 2001 From: Will McGugan Date: Tue, 2 Sep 2025 08:29:58 +0100 Subject: [PATCH 6/7] snapshots --- .../test_snapshots/test_buttons_render.svg | 109 ++++++++++-------- .../test_textual_dev_border_preview.svg | 36 +++--- 2 files changed, 82 insertions(+), 63 deletions(-) diff --git a/tests/snapshot_tests/__snapshots__/test_snapshots/test_buttons_render.svg b/tests/snapshot_tests/__snapshots__/test_snapshots/test_buttons_render.svg index 529df4ecfe..bbd7b8a996 100644 --- a/tests/snapshot_tests/__snapshots__/test_snapshots/test_buttons_render.svg +++ b/tests/snapshot_tests/__snapshots__/test_snapshots/test_buttons_render.svg @@ -36,34 +36,53 @@ .terminal-r2 { fill: #e0e0e0;font-weight: bold } .terminal-r3 { fill: #2d2d2d } .terminal-r4 { fill: #1e1e1e } -.terminal-r5 { fill: #272727;font-weight: bold } -.terminal-r6 { fill: #6a6a6a;font-weight: bold } -.terminal-r7 { fill: #0d0d0d } -.terminal-r8 { fill: #0f0f0f } -.terminal-r9 { fill: #6db2ff } -.terminal-r10 { fill: #3e6085 } -.terminal-r11 { fill: #ddedf9;font-weight: bold } -.terminal-r12 { fill: #637f94;font-weight: bold } -.terminal-r13 { fill: #004295 } -.terminal-r14 { fill: #082951 } -.terminal-r15 { fill: #7ae998 } -.terminal-r16 { fill: #447b53 } -.terminal-r17 { fill: #0a180e;font-weight: bold } -.terminal-r18 { fill: #193320;font-weight: bold } -.terminal-r19 { fill: #008139 } -.terminal-r20 { fill: #084724 } -.terminal-r21 { fill: #ffcf56 } -.terminal-r22 { fill: #856e32 } -.terminal-r23 { fill: #211505;font-weight: bold } -.terminal-r24 { fill: #422d10;font-weight: bold } -.terminal-r25 { fill: #b86b00 } -.terminal-r26 { fill: #633d08 } -.terminal-r27 { fill: #e76580 } -.terminal-r28 { fill: #7a3a47 } -.terminal-r29 { fill: #f5e5e9;font-weight: bold } -.terminal-r30 { fill: #8f7178;font-weight: bold } -.terminal-r31 { fill: #780028 } -.terminal-r32 { fill: #43081c } +.terminal-r5 { fill: #171717 } +.terminal-r6 { fill: #272727;font-weight: bold } +.terminal-r7 { fill: #6a6a6a;font-weight: bold } +.terminal-r8 { fill: #e8e8e8;font-weight: bold } +.terminal-r9 { fill: #676767;font-weight: bold } +.terminal-r10 { fill: #0d0d0d } +.terminal-r11 { fill: #0f0f0f } +.terminal-r12 { fill: #6db2ff } +.terminal-r13 { fill: #3e6085 } +.terminal-r14 { fill: #0c304c } +.terminal-r15 { fill: #0e202e } +.terminal-r16 { fill: #ddedf9;font-weight: bold } +.terminal-r17 { fill: #637f94;font-weight: bold } +.terminal-r18 { fill: #57a5e2;font-weight: bold } +.terminal-r19 { fill: #4278a3;font-weight: bold } +.terminal-r20 { fill: #004295 } +.terminal-r21 { fill: #082951 } +.terminal-r22 { fill: #7ae998 } +.terminal-r23 { fill: #447b53 } +.terminal-r24 { fill: #24452e } +.terminal-r25 { fill: #1a2a1f } +.terminal-r26 { fill: #0a180e;font-weight: bold } +.terminal-r27 { fill: #193320;font-weight: bold } +.terminal-r28 { fill: #8ad4a1;font-weight: bold } +.terminal-r29 { fill: #669976;font-weight: bold } +.terminal-r30 { fill: #008139 } +.terminal-r31 { fill: #084724 } +.terminal-r32 { fill: #ffcf56 } +.terminal-r33 { fill: #856e32 } +.terminal-r34 { fill: #593e19 } +.terminal-r35 { fill: #342714 } +.terminal-r36 { fill: #211505;font-weight: bold } +.terminal-r37 { fill: #422d10;font-weight: bold } +.terminal-r38 { fill: #ffc473;font-weight: bold } +.terminal-r39 { fill: #b78e55;font-weight: bold } +.terminal-r40 { fill: #b86b00 } +.terminal-r41 { fill: #633d08 } +.terminal-r42 { fill: #e76580 } +.terminal-r43 { fill: #7a3a47 } +.terminal-r44 { fill: #441e27 } +.terminal-r45 { fill: #2a171b } +.terminal-r46 { fill: #f5e5e9;font-weight: bold } +.terminal-r47 { fill: #8f7178;font-weight: bold } +.terminal-r48 { fill: #d17e92;font-weight: bold } +.terminal-r49 { fill: #975d6b;font-weight: bold } +.terminal-r50 { fill: #780028 } +.terminal-r51 { fill: #43081c } @@ -149,30 +168,30 @@ - + -Standard ButtonsDisabled Buttons +Standard ButtonsDisabled ButtonsFlat ButtonsDisabl -▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ - Default  Default  -▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ +▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ + Default  Default  Default  De +▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ -▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ - Primary!  Primary!  -▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ +▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ + Primary!  Primary!  Primary!  Pr +▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ -▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ - Success!  Success!  -▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ +▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ + Success!  Success!  Success!  Su +▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ -▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ - Warning!  Warning!  -▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ +▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ + Warning!  Warning!  Warning!  Wa +▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ -▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ - Error!  Error!  -▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ +▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ + Error!  Error!  Error!  E +▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ diff --git a/tests/snapshot_tests/__snapshots__/test_snapshots/test_textual_dev_border_preview.svg b/tests/snapshot_tests/__snapshots__/test_snapshots/test_textual_dev_border_preview.svg index b30bed6d3d..e4444fa324 100644 --- a/tests/snapshot_tests/__snapshots__/test_snapshots/test_textual_dev_border_preview.svg +++ b/tests/snapshot_tests/__snapshots__/test_snapshots/test_textual_dev_border_preview.svg @@ -123,28 +123,28 @@ - + ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ ascii blank -dashed+--------------------- ascii ----------------------+ -double|| -heavy|| -hidden|I must not fear.| -hkey|Fear is the mind-killer.| -inner|Fear is the little-death that brings total| -none|obliteration.| -outer|I will face my fear.| -panel|I will permit it to pass over me and| -round|through me.| -solid|And when it has gone past, I will turn the| -tab|inner eye to see its path.| -tall|Where the fear has gone there will be| -thick|nothing. Only I will remain.| -vkey|| -wide|| -+-------------------------------- border subtitle -+ +block+--------------------- ascii ----------------------+ +dashed|| +double|| +heavy|I must not fear.| +hidden|Fear is the mind-killer.| +hkey|Fear is the little-death that brings total| +inner|obliteration.| +none|I will face my fear.| +outer|I will permit it to pass over me and| +panel|through me.| +round|And when it has gone past, I will turn the| +solid|inner eye to see its path.| +tab|Where the fear has gone there will be| +tall|nothing. Only I will remain.| +thick|| +vkey|| +wide+-------------------------------- border subtitle -+ From 88fc25c440d04ee2c532460c461219be60719459 Mon Sep 17 00:00:00 2001 From: Will McGugan Date: Tue, 2 Sep 2025 08:38:04 +0100 Subject: [PATCH 7/7] allow CSS to override Markdown tables --- src/textual/widgets/_markdown.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/textual/widgets/_markdown.py b/src/textual/widgets/_markdown.py index c6e05fa444..397b265b50 100644 --- a/src/textual/widgets/_markdown.py +++ b/src/textual/widgets/_markdown.py @@ -649,7 +649,7 @@ def __init__(self, headers: list[Content], rows: list[list[Content]]): def pre_layout(self, layout: Layout) -> None: assert isinstance(layout, GridLayout) layout.auto_minimum = True - layout.expand = True + layout.expand = not self.query_ancestor(MarkdownTable).styles.is_auto_width layout.shrink = True layout.stretch_height = True