Fix issue with modals (#2195)

* Fix issue with modals

* changelog

* fix binding on button

* binding tweak

* changelog

* snapshots

* version bump
This commit is contained in:
Will McGugan
2023-04-02 08:52:35 +01:00
committed by GitHub
parent 4fcf44c354
commit eb4c7ef50f
10 changed files with 424 additions and 43 deletions

View File

@@ -5,12 +5,18 @@ 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
## [0.17.2] - 2023-04-02
### Fixed
### [Fixed]
- Fixed bindings persistance https://github.com/Textualize/textual/issues/1613
- The `Markdown` widget now auto-increments ordered lists https://github.com/Textualize/textual/issues/2002
- Fixed modal bindings https://github.com/Textualize/textual/issues/2194
- Fix binding enter to active button https://github.com/Textualize/textual/issues/2194
### [Changed]
- tab and shift+tab are now defined on Screen.
## [0.17.1] - 2023-03-30
@@ -687,6 +693,9 @@ https://textual.textualize.io/blog/2022/11/08/version-040/#version-040
- New handler system for messages that doesn't require inheritance
- Improved traceback handling
[0.17.2]: https://github.com/Textualize/textual/compare/v0.17.1...v0.17.2
[0.17.1]: https://github.com/Textualize/textual/compare/v0.17.0...v0.17.1
[0.17.0]: https://github.com/Textualize/textual/compare/v0.16.0...v0.17.0
[0.16.0]: https://github.com/Textualize/textual/compare/v0.15.1...v0.16.0
[0.15.1]: https://github.com/Textualize/textual/compare/v0.15.0...v0.15.1
[0.15.0]: https://github.com/Textualize/textual/compare/v0.14.0...v0.15.0

View File

@@ -1,6 +1,6 @@
[tool.poetry]
name = "textual"
version = "0.17.1"
version = "0.17.2"
homepage = "https://github.com/Textualize/textual"
description = "Modern Text User Interface framework"
authors = ["Will McGugan <will@textualize.io>"]

View File

@@ -279,11 +279,7 @@ class App(Generic[ReturnType], DOMNode):
also the `sub_title` attribute.
"""
BINDINGS = [
Binding("ctrl+c", "quit", "Quit", show=False, priority=True),
Binding("tab", "focus_next", "Focus Next", show=False),
Binding("shift+tab", "focus_previous", "Focus Previous", show=False),
]
BINDINGS = [Binding("ctrl+c", "quit", "Quit", show=False, priority=True)]
title: Reactive[str] = Reactive("", compute=False)
sub_title: Reactive[str] = Reactive("", compute=False)
@@ -1961,38 +1957,34 @@ class App(Generic[ReturnType], DOMNode):
@property
def _binding_chain(self) -> list[tuple[DOMNode, Bindings]]:
"""Get a chain of nodes and bindings to consider.
If no widget is focused, returns the bindings from both the screen and the app level bindings.
Otherwise, combines all the bindings from the currently focused node up the DOM to the root App.
Returns:
List of DOM nodes and their bindings.
"""
focused = self.focused
namespace_bindings: list[tuple[DOMNode, Bindings]]
screen = self.screen
if focused is None:
if screen.is_modal:
namespace_bindings = [
(self.screen, self.screen._bindings),
]
else:
namespace_bindings = [
(self.screen, self.screen._bindings),
(self, self._bindings),
]
namespace_bindings = [
(self.screen, self.screen._bindings),
(self, self._bindings),
]
else:
if screen.is_modal:
namespace_bindings = [
(node, node._bindings) for node in focused.ancestors
]
else:
namespace_bindings = [
(node, node._bindings) for node in focused.ancestors_with_self
]
namespace_bindings = [
(node, node._bindings) for node in focused.ancestors_with_self
]
return namespace_bindings
@property
def _modal_binding_chain(self) -> list[tuple[DOMNode, Bindings]]:
"""The binding chain, ignoring everything before the last modal."""
binding_chain = self._binding_chain
for index, (node, _bindings) in enumerate(binding_chain, 1):
if node.is_modal:
return binding_chain[:index]
return binding_chain
async def check_bindings(self, key: str, priority: bool = False) -> bool:
"""Handle a key press.
@@ -2004,7 +1996,7 @@ class App(Generic[ReturnType], DOMNode):
True if the key was handled by a binding, otherwise False
"""
for namespace, bindings in (
reversed(self._binding_chain) if priority else self._binding_chain
reversed(self._binding_chain) if priority else self._modal_binding_chain
):
binding = bindings.keys.get(key)
if binding is not None and binding.priority == priority:

View File

@@ -208,6 +208,11 @@ class DOMNode(MessagePump):
)
self._auto_refresh = interval
@property
def is_modal(self) -> bool:
"""Is the node a modal?"""
return False
def _automatic_refresh(self) -> None:
"""Perform an automatic refresh (set with auto_refresh property)."""
self.refresh()

View File

@@ -11,6 +11,7 @@ from ._callback import invoke
from ._compositor import Compositor, MapGeometry
from ._context import visible_screen_stack
from ._types import CallbackType
from .binding import Binding
from .css.match import match
from .css.parse import parse_selectors
from .css.query import QueryType
@@ -45,6 +46,11 @@ class Screen(Widget):
stack_updates: Reactive[int] = Reactive(0, repaint=False)
"""An integer that updates when the screen is resumed."""
BINDINGS = [
Binding("tab", "focus_next", "Focus Next", show=False),
Binding("shift+tab", "focus_previous", "Focus Previous", show=False),
]
def __init__(
self,
name: str | None = None,

View File

@@ -7,6 +7,7 @@ from rich.text import Text, TextType
from typing_extensions import Literal, Self
from .. import events
from ..binding import Binding
from ..css._error_tools import friendly_list
from ..message import Message
from ..reactive import reactive
@@ -145,6 +146,8 @@ class Button(Static, can_focus=True):
"""
BINDINGS = [Binding("enter", "press", "Press Button", show=False)]
ACTIVE_EFFECT_DURATION = 0.3
"""When buttons are clicked they get the `-active` class for this duration (in seconds)"""
@@ -252,10 +255,9 @@ class Button(Static, can_focus=True):
self.ACTIVE_EFFECT_DURATION, partial(self.remove_class, "-active")
)
async def _on_key(self, event: events.Key) -> None:
if event.key == "enter" and not self.disabled:
self._start_active_affect()
self.post_message(Button.Pressed(self))
def action_press(self) -> None:
"""Activate a press if"""
self.press()
@classmethod
def success(

View File

@@ -16790,6 +16790,328 @@
'''
# ---
# name: test_modal_dialog_bindings
'''
<svg class="rich-terminal" viewBox="0 0 994 635.5999999999999" xmlns="http://www.w3.org/2000/svg">
<!-- Generated with Rich https://www.textualize.io -->
<style>
@font-face {
font-family: "Fira Code";
src: local("FiraCode-Regular"),
url("https://cdnjs.cloudflare.com/ajax/libs/firacode/6.2.0/woff2/FiraCode-Regular.woff2") format("woff2"),
url("https://cdnjs.cloudflare.com/ajax/libs/firacode/6.2.0/woff/FiraCode-Regular.woff") format("woff");
font-style: normal;
font-weight: 400;
}
@font-face {
font-family: "Fira Code";
src: local("FiraCode-Bold"),
url("https://cdnjs.cloudflare.com/ajax/libs/firacode/6.2.0/woff2/FiraCode-Bold.woff2") format("woff2"),
url("https://cdnjs.cloudflare.com/ajax/libs/firacode/6.2.0/woff/FiraCode-Bold.woff") format("woff");
font-style: bold;
font-weight: 700;
}
.terminal-543315859-matrix {
font-family: Fira Code, monospace;
font-size: 20px;
line-height: 24.4px;
font-variant-east-asian: full-width;
}
.terminal-543315859-title {
font-size: 18px;
font-weight: bold;
font-family: arial;
}
.terminal-543315859-r1 { fill: #c5c8c6 }
.terminal-543315859-r2 { fill: #e3e3e3 }
.terminal-543315859-r3 { fill: #e1e1e1 }
.terminal-543315859-r4 { fill: #dde8f3;font-weight: bold }
.terminal-543315859-r5 { fill: #ddedf9 }
</style>
<defs>
<clipPath id="terminal-543315859-clip-terminal">
<rect x="0" y="0" width="975.0" height="584.5999999999999" />
</clipPath>
<clipPath id="terminal-543315859-line-0">
<rect x="0" y="1.5" width="976" height="24.65"/>
</clipPath>
<clipPath id="terminal-543315859-line-1">
<rect x="0" y="25.9" width="976" height="24.65"/>
</clipPath>
<clipPath id="terminal-543315859-line-2">
<rect x="0" y="50.3" width="976" height="24.65"/>
</clipPath>
<clipPath id="terminal-543315859-line-3">
<rect x="0" y="74.7" width="976" height="24.65"/>
</clipPath>
<clipPath id="terminal-543315859-line-4">
<rect x="0" y="99.1" width="976" height="24.65"/>
</clipPath>
<clipPath id="terminal-543315859-line-5">
<rect x="0" y="123.5" width="976" height="24.65"/>
</clipPath>
<clipPath id="terminal-543315859-line-6">
<rect x="0" y="147.9" width="976" height="24.65"/>
</clipPath>
<clipPath id="terminal-543315859-line-7">
<rect x="0" y="172.3" width="976" height="24.65"/>
</clipPath>
<clipPath id="terminal-543315859-line-8">
<rect x="0" y="196.7" width="976" height="24.65"/>
</clipPath>
<clipPath id="terminal-543315859-line-9">
<rect x="0" y="221.1" width="976" height="24.65"/>
</clipPath>
<clipPath id="terminal-543315859-line-10">
<rect x="0" y="245.5" width="976" height="24.65"/>
</clipPath>
<clipPath id="terminal-543315859-line-11">
<rect x="0" y="269.9" width="976" height="24.65"/>
</clipPath>
<clipPath id="terminal-543315859-line-12">
<rect x="0" y="294.3" width="976" height="24.65"/>
</clipPath>
<clipPath id="terminal-543315859-line-13">
<rect x="0" y="318.7" width="976" height="24.65"/>
</clipPath>
<clipPath id="terminal-543315859-line-14">
<rect x="0" y="343.1" width="976" height="24.65"/>
</clipPath>
<clipPath id="terminal-543315859-line-15">
<rect x="0" y="367.5" width="976" height="24.65"/>
</clipPath>
<clipPath id="terminal-543315859-line-16">
<rect x="0" y="391.9" width="976" height="24.65"/>
</clipPath>
<clipPath id="terminal-543315859-line-17">
<rect x="0" y="416.3" width="976" height="24.65"/>
</clipPath>
<clipPath id="terminal-543315859-line-18">
<rect x="0" y="440.7" width="976" height="24.65"/>
</clipPath>
<clipPath id="terminal-543315859-line-19">
<rect x="0" y="465.1" width="976" height="24.65"/>
</clipPath>
<clipPath id="terminal-543315859-line-20">
<rect x="0" y="489.5" width="976" height="24.65"/>
</clipPath>
<clipPath id="terminal-543315859-line-21">
<rect x="0" y="513.9" width="976" height="24.65"/>
</clipPath>
<clipPath id="terminal-543315859-line-22">
<rect x="0" y="538.3" width="976" height="24.65"/>
</clipPath>
</defs>
<rect fill="#292929" stroke="rgba(255,255,255,0.35)" stroke-width="1" x="1" y="1" width="992" height="633.6" rx="8"/><text class="terminal-543315859-title" fill="#c5c8c6" text-anchor="middle" x="496" y="27">ModalApp</text>
<g transform="translate(26,22)">
<circle cx="0" cy="0" r="7" fill="#ff5f57"/>
<circle cx="22" cy="0" r="7" fill="#febc2e"/>
<circle cx="44" cy="0" r="7" fill="#28c840"/>
</g>
<g transform="translate(9, 41)" clip-path="url(#terminal-543315859-clip-terminal)">
<rect fill="#282828" x="0" y="1.5" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#282828" x="12.2" y="1.5" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#282828" x="24.4" y="1.5" width="61" height="24.65" shape-rendering="crispEdges"/><rect fill="#282828" x="85.4" y="1.5" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#282828" x="97.6" y="1.5" width="329.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#282828" x="427" y="1.5" width="97.6" height="24.65" shape-rendering="crispEdges"/><rect fill="#282828" x="524.6" y="1.5" width="329.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#282828" x="854" y="1.5" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#282828" x="866.2" y="1.5" width="0" height="24.65" shape-rendering="crispEdges"/><rect fill="#282828" x="866.2" y="1.5" width="97.6" height="24.65" shape-rendering="crispEdges"/><rect fill="#282828" x="963.8" y="1.5" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="25.9" width="61" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="61" y="25.9" width="915" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="50.3" width="976" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="74.7" width="976" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="99.1" width="976" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="123.5" width="976" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="147.9" width="976" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="172.3" width="976" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="196.7" width="976" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="221.1" width="976" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="245.5" width="976" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="269.9" width="976" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="294.3" width="976" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="318.7" width="976" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="343.1" width="976" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="367.5" width="976" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="391.9" width="976" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="416.3" width="976" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="440.7" width="976" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="465.1" width="976" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="489.5" width="976" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="513.9" width="976" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="538.3" width="976" height="24.65" shape-rendering="crispEdges"/><rect fill="#0053aa" x="0" y="562.7" width="36.6" height="24.65" shape-rendering="crispEdges"/><rect fill="#0178d4" x="36.6" y="562.7" width="158.6" height="24.65" shape-rendering="crispEdges"/><rect fill="#0178d4" x="195.2" y="562.7" width="780.8" height="24.65" shape-rendering="crispEdges"/>
<g class="terminal-543315859-matrix">
<text class="terminal-543315859-r2" x="12.2" y="20" textLength="12.2" clip-path="url(#terminal-543315859-line-0)">⭘</text><text class="terminal-543315859-r2" x="427" y="20" textLength="97.6" clip-path="url(#terminal-543315859-line-0)">ModalApp</text><text class="terminal-543315859-r1" x="976" y="20" textLength="12.2" clip-path="url(#terminal-543315859-line-0)">
</text><text class="terminal-543315859-r3" x="0" y="44.4" textLength="61" clip-path="url(#terminal-543315859-line-1)">Hello</text><text class="terminal-543315859-r1" x="976" y="44.4" textLength="12.2" clip-path="url(#terminal-543315859-line-1)">
</text><text class="terminal-543315859-r1" x="976" y="68.8" textLength="12.2" clip-path="url(#terminal-543315859-line-2)">
</text><text class="terminal-543315859-r1" x="976" y="93.2" textLength="12.2" clip-path="url(#terminal-543315859-line-3)">
</text><text class="terminal-543315859-r1" x="976" y="117.6" textLength="12.2" clip-path="url(#terminal-543315859-line-4)">
</text><text class="terminal-543315859-r1" x="976" y="142" textLength="12.2" clip-path="url(#terminal-543315859-line-5)">
</text><text class="terminal-543315859-r1" x="976" y="166.4" textLength="12.2" clip-path="url(#terminal-543315859-line-6)">
</text><text class="terminal-543315859-r1" x="976" y="190.8" textLength="12.2" clip-path="url(#terminal-543315859-line-7)">
</text><text class="terminal-543315859-r1" x="976" y="215.2" textLength="12.2" clip-path="url(#terminal-543315859-line-8)">
</text><text class="terminal-543315859-r1" x="976" y="239.6" textLength="12.2" clip-path="url(#terminal-543315859-line-9)">
</text><text class="terminal-543315859-r1" x="976" y="264" textLength="12.2" clip-path="url(#terminal-543315859-line-10)">
</text><text class="terminal-543315859-r1" x="976" y="288.4" textLength="12.2" clip-path="url(#terminal-543315859-line-11)">
</text><text class="terminal-543315859-r1" x="976" y="312.8" textLength="12.2" clip-path="url(#terminal-543315859-line-12)">
</text><text class="terminal-543315859-r1" x="976" y="337.2" textLength="12.2" clip-path="url(#terminal-543315859-line-13)">
</text><text class="terminal-543315859-r1" x="976" y="361.6" textLength="12.2" clip-path="url(#terminal-543315859-line-14)">
</text><text class="terminal-543315859-r1" x="976" y="386" textLength="12.2" clip-path="url(#terminal-543315859-line-15)">
</text><text class="terminal-543315859-r1" x="976" y="410.4" textLength="12.2" clip-path="url(#terminal-543315859-line-16)">
</text><text class="terminal-543315859-r1" x="976" y="434.8" textLength="12.2" clip-path="url(#terminal-543315859-line-17)">
</text><text class="terminal-543315859-r1" x="976" y="459.2" textLength="12.2" clip-path="url(#terminal-543315859-line-18)">
</text><text class="terminal-543315859-r1" x="976" y="483.6" textLength="12.2" clip-path="url(#terminal-543315859-line-19)">
</text><text class="terminal-543315859-r1" x="976" y="508" textLength="12.2" clip-path="url(#terminal-543315859-line-20)">
</text><text class="terminal-543315859-r1" x="976" y="532.4" textLength="12.2" clip-path="url(#terminal-543315859-line-21)">
</text><text class="terminal-543315859-r1" x="976" y="556.8" textLength="12.2" clip-path="url(#terminal-543315859-line-22)">
</text><text class="terminal-543315859-r4" x="0" y="581.2" textLength="36.6" clip-path="url(#terminal-543315859-line-23)">&#160;⏎&#160;</text><text class="terminal-543315859-r5" x="36.6" y="581.2" textLength="158.6" clip-path="url(#terminal-543315859-line-23)">&#160;Open&#160;Dialog&#160;</text>
</g>
</g>
</svg>
'''
# ---
# name: test_modal_dialog_bindings_input
'''
<svg class="rich-terminal" viewBox="0 0 994 635.5999999999999" xmlns="http://www.w3.org/2000/svg">
<!-- Generated with Rich https://www.textualize.io -->
<style>
@font-face {
font-family: "Fira Code";
src: local("FiraCode-Regular"),
url("https://cdnjs.cloudflare.com/ajax/libs/firacode/6.2.0/woff2/FiraCode-Regular.woff2") format("woff2"),
url("https://cdnjs.cloudflare.com/ajax/libs/firacode/6.2.0/woff/FiraCode-Regular.woff") format("woff");
font-style: normal;
font-weight: 400;
}
@font-face {
font-family: "Fira Code";
src: local("FiraCode-Bold"),
url("https://cdnjs.cloudflare.com/ajax/libs/firacode/6.2.0/woff2/FiraCode-Bold.woff2") format("woff2"),
url("https://cdnjs.cloudflare.com/ajax/libs/firacode/6.2.0/woff/FiraCode-Bold.woff") format("woff");
font-style: bold;
font-weight: 700;
}
.terminal-2766044148-matrix {
font-family: Fira Code, monospace;
font-size: 20px;
line-height: 24.4px;
font-variant-east-asian: full-width;
}
.terminal-2766044148-title {
font-size: 18px;
font-weight: bold;
font-family: arial;
}
.terminal-2766044148-r1 { fill: #e0e0e0 }
.terminal-2766044148-r2 { fill: #656565 }
.terminal-2766044148-r3 { fill: #c5c8c6 }
.terminal-2766044148-r4 { fill: #121212 }
.terminal-2766044148-r5 { fill: #e1e1e1 }
.terminal-2766044148-r6 { fill: #454a50 }
.terminal-2766044148-r7 { fill: #646464 }
.terminal-2766044148-r8 { fill: #24292f;font-weight: bold }
.terminal-2766044148-r9 { fill: #000000 }
.terminal-2766044148-r10 { fill: #63676c;font-weight: bold }
.terminal-2766044148-r11 { fill: #63696e }
</style>
<defs>
<clipPath id="terminal-2766044148-clip-terminal">
<rect x="0" y="0" width="975.0" height="584.5999999999999" />
</clipPath>
<clipPath id="terminal-2766044148-line-0">
<rect x="0" y="1.5" width="976" height="24.65"/>
</clipPath>
<clipPath id="terminal-2766044148-line-1">
<rect x="0" y="25.9" width="976" height="24.65"/>
</clipPath>
<clipPath id="terminal-2766044148-line-2">
<rect x="0" y="50.3" width="976" height="24.65"/>
</clipPath>
<clipPath id="terminal-2766044148-line-3">
<rect x="0" y="74.7" width="976" height="24.65"/>
</clipPath>
<clipPath id="terminal-2766044148-line-4">
<rect x="0" y="99.1" width="976" height="24.65"/>
</clipPath>
<clipPath id="terminal-2766044148-line-5">
<rect x="0" y="123.5" width="976" height="24.65"/>
</clipPath>
<clipPath id="terminal-2766044148-line-6">
<rect x="0" y="147.9" width="976" height="24.65"/>
</clipPath>
<clipPath id="terminal-2766044148-line-7">
<rect x="0" y="172.3" width="976" height="24.65"/>
</clipPath>
<clipPath id="terminal-2766044148-line-8">
<rect x="0" y="196.7" width="976" height="24.65"/>
</clipPath>
<clipPath id="terminal-2766044148-line-9">
<rect x="0" y="221.1" width="976" height="24.65"/>
</clipPath>
<clipPath id="terminal-2766044148-line-10">
<rect x="0" y="245.5" width="976" height="24.65"/>
</clipPath>
<clipPath id="terminal-2766044148-line-11">
<rect x="0" y="269.9" width="976" height="24.65"/>
</clipPath>
<clipPath id="terminal-2766044148-line-12">
<rect x="0" y="294.3" width="976" height="24.65"/>
</clipPath>
<clipPath id="terminal-2766044148-line-13">
<rect x="0" y="318.7" width="976" height="24.65"/>
</clipPath>
<clipPath id="terminal-2766044148-line-14">
<rect x="0" y="343.1" width="976" height="24.65"/>
</clipPath>
<clipPath id="terminal-2766044148-line-15">
<rect x="0" y="367.5" width="976" height="24.65"/>
</clipPath>
<clipPath id="terminal-2766044148-line-16">
<rect x="0" y="391.9" width="976" height="24.65"/>
</clipPath>
<clipPath id="terminal-2766044148-line-17">
<rect x="0" y="416.3" width="976" height="24.65"/>
</clipPath>
<clipPath id="terminal-2766044148-line-18">
<rect x="0" y="440.7" width="976" height="24.65"/>
</clipPath>
<clipPath id="terminal-2766044148-line-19">
<rect x="0" y="465.1" width="976" height="24.65"/>
</clipPath>
<clipPath id="terminal-2766044148-line-20">
<rect x="0" y="489.5" width="976" height="24.65"/>
</clipPath>
<clipPath id="terminal-2766044148-line-21">
<rect x="0" y="513.9" width="976" height="24.65"/>
</clipPath>
<clipPath id="terminal-2766044148-line-22">
<rect x="0" y="538.3" width="976" height="24.65"/>
</clipPath>
</defs>
<rect fill="#292929" stroke="rgba(255,255,255,0.35)" stroke-width="1" x="1" y="1" width="992" height="633.6" rx="8"/><text class="terminal-2766044148-title" fill="#c5c8c6" text-anchor="middle" x="496" y="27">ModalApp</text>
<g transform="translate(26,22)">
<circle cx="0" cy="0" r="7" fill="#ff5f57"/>
<circle cx="22" cy="0" r="7" fill="#febc2e"/>
<circle cx="44" cy="0" r="7" fill="#28c840"/>
</g>
<g transform="translate(9, 41)" clip-path="url(#terminal-2766044148-clip-terminal)">
<rect fill="#121212" x="0" y="1.5" width="73.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1a1a1a" x="73.2" y="1.5" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1a1a1a" x="85.4" y="1.5" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1a1a1a" x="97.6" y="1.5" width="329.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#1a1a1a" x="427" y="1.5" width="97.6" height="24.65" shape-rendering="crispEdges"/><rect fill="#1a1a1a" x="524.6" y="1.5" width="329.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#1a1a1a" x="854" y="1.5" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1a1a1a" x="866.2" y="1.5" width="97.6" height="24.65" shape-rendering="crispEdges"/><rect fill="#1a1a1a" x="963.8" y="1.5" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#121212" x="0" y="25.9" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1b1b1b" x="12.2" y="25.9" width="951.6" height="24.65" shape-rendering="crispEdges"/><rect fill="#121212" x="963.8" y="25.9" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#121212" x="0" y="50.3" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1b1b1b" x="12.2" y="50.3" width="24.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#1b1b1b" x="36.6" y="50.3" width="36.6" height="24.65" shape-rendering="crispEdges"/><rect fill="#1b1b1b" x="73.2" y="50.3" width="866.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1b1b1b" x="939.4" y="50.3" width="24.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#121212" x="963.8" y="50.3" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#121212" x="0" y="74.7" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1b1b1b" x="12.2" y="74.7" width="951.6" height="24.65" shape-rendering="crispEdges"/><rect fill="#121212" x="963.8" y="74.7" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#24292f" x="0" y="99.1" width="195.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#161616" x="195.2" y="99.1" width="780.8" height="24.65" shape-rendering="crispEdges"/><rect fill="#24292f" x="0" y="123.5" width="73.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#e2e3e3" x="73.2" y="123.5" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#e2e3e3" x="85.4" y="123.5" width="24.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#e2e3e3" x="109.8" y="123.5" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#24292f" x="122" y="123.5" width="73.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#161616" x="195.2" y="123.5" width="780.8" height="24.65" shape-rendering="crispEdges"/><rect fill="#24292f" x="0" y="147.9" width="195.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#161616" x="195.2" y="147.9" width="780.8" height="24.65" shape-rendering="crispEdges"/><rect fill="#161616" x="0" y="172.3" width="976" height="24.65" shape-rendering="crispEdges"/><rect fill="#161616" x="0" y="196.7" width="976" height="24.65" shape-rendering="crispEdges"/><rect fill="#161616" x="0" y="221.1" width="976" height="24.65" shape-rendering="crispEdges"/><rect fill="#161616" x="0" y="245.5" width="976" height="24.65" shape-rendering="crispEdges"/><rect fill="#161616" x="0" y="269.9" width="976" height="24.65" shape-rendering="crispEdges"/><rect fill="#161616" x="0" y="294.3" width="976" height="24.65" shape-rendering="crispEdges"/><rect fill="#161616" x="0" y="318.7" width="976" height="24.65" shape-rendering="crispEdges"/><rect fill="#161616" x="0" y="343.1" width="976" height="24.65" shape-rendering="crispEdges"/><rect fill="#161616" x="0" y="367.5" width="976" height="24.65" shape-rendering="crispEdges"/><rect fill="#161616" x="0" y="391.9" width="976" height="24.65" shape-rendering="crispEdges"/><rect fill="#161616" x="0" y="416.3" width="976" height="24.65" shape-rendering="crispEdges"/><rect fill="#161616" x="0" y="440.7" width="976" height="24.65" shape-rendering="crispEdges"/><rect fill="#161616" x="0" y="465.1" width="976" height="24.65" shape-rendering="crispEdges"/><rect fill="#161616" x="0" y="489.5" width="976" height="24.65" shape-rendering="crispEdges"/><rect fill="#161616" x="0" y="513.9" width="976" height="24.65" shape-rendering="crispEdges"/><rect fill="#161616" x="0" y="538.3" width="976" height="24.65" shape-rendering="crispEdges"/><rect fill="#0a2c4e" x="0" y="562.7" width="36.6" height="24.65" shape-rendering="crispEdges"/><rect fill="#0b3a5f" x="36.6" y="562.7" width="158.6" height="24.65" shape-rendering="crispEdges"/><rect fill="#0b3a5f" x="195.2" y="562.7" width="780.8" height="24.65" shape-rendering="crispEdges"/>
<g class="terminal-2766044148-matrix">
<text class="terminal-2766044148-r1" x="0" y="20" textLength="73.2" clip-path="url(#terminal-2766044148-line-0)">Dialog</text><text class="terminal-2766044148-r2" x="427" y="20" textLength="97.6" clip-path="url(#terminal-2766044148-line-0)">ModalApp</text><text class="terminal-2766044148-r3" x="976" y="20" textLength="12.2" clip-path="url(#terminal-2766044148-line-0)">
</text><text class="terminal-2766044148-r4" x="0" y="44.4" textLength="12.2" clip-path="url(#terminal-2766044148-line-1)">▊</text><text class="terminal-2766044148-r4" x="12.2" y="44.4" textLength="951.6" clip-path="url(#terminal-2766044148-line-1)">▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔</text><text class="terminal-2766044148-r4" x="963.8" y="44.4" textLength="12.2" clip-path="url(#terminal-2766044148-line-1)">▎</text><text class="terminal-2766044148-r3" x="976" y="44.4" textLength="12.2" clip-path="url(#terminal-2766044148-line-1)">
</text><text class="terminal-2766044148-r4" x="0" y="68.8" textLength="12.2" clip-path="url(#terminal-2766044148-line-2)">▊</text><text class="terminal-2766044148-r5" x="36.6" y="68.8" textLength="36.6" clip-path="url(#terminal-2766044148-line-2)">hi!</text><text class="terminal-2766044148-r4" x="963.8" y="68.8" textLength="12.2" clip-path="url(#terminal-2766044148-line-2)">▎</text><text class="terminal-2766044148-r3" x="976" y="68.8" textLength="12.2" clip-path="url(#terminal-2766044148-line-2)">
</text><text class="terminal-2766044148-r4" x="0" y="93.2" textLength="12.2" clip-path="url(#terminal-2766044148-line-3)">▊</text><text class="terminal-2766044148-r4" x="12.2" y="93.2" textLength="951.6" clip-path="url(#terminal-2766044148-line-3)">▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁</text><text class="terminal-2766044148-r4" x="963.8" y="93.2" textLength="12.2" clip-path="url(#terminal-2766044148-line-3)">▎</text><text class="terminal-2766044148-r3" x="976" y="93.2" textLength="12.2" clip-path="url(#terminal-2766044148-line-3)">
</text><text class="terminal-2766044148-r6" x="0" y="117.6" textLength="195.2" clip-path="url(#terminal-2766044148-line-4)">▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔</text><text class="terminal-2766044148-r3" x="976" y="117.6" textLength="12.2" clip-path="url(#terminal-2766044148-line-4)">
</text><text class="terminal-2766044148-r8" x="85.4" y="142" textLength="24.4" clip-path="url(#terminal-2766044148-line-5)">OK</text><text class="terminal-2766044148-r3" x="976" y="142" textLength="12.2" clip-path="url(#terminal-2766044148-line-5)">
</text><text class="terminal-2766044148-r9" x="0" y="166.4" textLength="195.2" clip-path="url(#terminal-2766044148-line-6)">▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁</text><text class="terminal-2766044148-r3" x="976" y="166.4" textLength="12.2" clip-path="url(#terminal-2766044148-line-6)">
</text><text class="terminal-2766044148-r3" x="976" y="190.8" textLength="12.2" clip-path="url(#terminal-2766044148-line-7)">
</text><text class="terminal-2766044148-r3" x="976" y="215.2" textLength="12.2" clip-path="url(#terminal-2766044148-line-8)">
</text><text class="terminal-2766044148-r3" x="976" y="239.6" textLength="12.2" clip-path="url(#terminal-2766044148-line-9)">
</text><text class="terminal-2766044148-r3" x="976" y="264" textLength="12.2" clip-path="url(#terminal-2766044148-line-10)">
</text><text class="terminal-2766044148-r3" x="976" y="288.4" textLength="12.2" clip-path="url(#terminal-2766044148-line-11)">
</text><text class="terminal-2766044148-r3" x="976" y="312.8" textLength="12.2" clip-path="url(#terminal-2766044148-line-12)">
</text><text class="terminal-2766044148-r3" x="976" y="337.2" textLength="12.2" clip-path="url(#terminal-2766044148-line-13)">
</text><text class="terminal-2766044148-r3" x="976" y="361.6" textLength="12.2" clip-path="url(#terminal-2766044148-line-14)">
</text><text class="terminal-2766044148-r3" x="976" y="386" textLength="12.2" clip-path="url(#terminal-2766044148-line-15)">
</text><text class="terminal-2766044148-r3" x="976" y="410.4" textLength="12.2" clip-path="url(#terminal-2766044148-line-16)">
</text><text class="terminal-2766044148-r3" x="976" y="434.8" textLength="12.2" clip-path="url(#terminal-2766044148-line-17)">
</text><text class="terminal-2766044148-r3" x="976" y="459.2" textLength="12.2" clip-path="url(#terminal-2766044148-line-18)">
</text><text class="terminal-2766044148-r3" x="976" y="483.6" textLength="12.2" clip-path="url(#terminal-2766044148-line-19)">
</text><text class="terminal-2766044148-r3" x="976" y="508" textLength="12.2" clip-path="url(#terminal-2766044148-line-20)">
</text><text class="terminal-2766044148-r3" x="976" y="532.4" textLength="12.2" clip-path="url(#terminal-2766044148-line-21)">
</text><text class="terminal-2766044148-r3" x="976" y="556.8" textLength="12.2" clip-path="url(#terminal-2766044148-line-22)">
</text><text class="terminal-2766044148-r10" x="0" y="581.2" textLength="36.6" clip-path="url(#terminal-2766044148-line-23)">&#160;⏎&#160;</text><text class="terminal-2766044148-r11" x="36.6" y="581.2" textLength="158.6" clip-path="url(#terminal-2766044148-line-23)">&#160;Open&#160;Dialog&#160;</text>
</g>
</g>
</svg>
'''
# ---
# name: test_multiple_css
'''
<svg class="rich-terminal" viewBox="0 0 994 635.5999999999999" xmlns="http://www.w3.org/2000/svg">

View File

@@ -0,0 +1,34 @@
from textual.app import App, ComposeResult
from textual.screen import Screen, ModalScreen
from textual.widgets import Button, Footer, Header, Label, Input
class Dialog(ModalScreen):
def compose(self) -> ComposeResult:
yield Label("Dialog")
yield Input()
yield Button("OK", id="ok")
def on_button_pressed(self, event: Button.Pressed) -> None:
if event.button.id == "ok":
self.app.pop_screen()
def on_input_submitted(self, event: Input.Submitted) -> None:
self.app.pop_screen() # Never gets here
class ModalApp(App):
BINDINGS = [("enter", "open_dialog", "Open Dialog")]
def compose(self) -> ComposeResult:
yield Header()
yield Label("Hello")
yield Footer()
def action_open_dialog(self) -> None:
self.push_screen(Dialog())
if __name__ == "__main__":
app = ModalApp()
app.run()

View File

@@ -192,6 +192,7 @@ def test_option_list(snap_compare):
assert snap_compare(WIDGET_EXAMPLES_DIR / "option_list_options.py")
assert snap_compare(WIDGET_EXAMPLES_DIR / "option_list_tables.py")
# --- CSS properties ---
# We have a canonical example for each CSS property that is shown in their docs.
# If any of these change, something has likely broken, so snapshot each of them.
@@ -364,3 +365,19 @@ def test_css_hot_reloading(snap_compare):
def test_layer_fix(snap_compare):
# Check https://github.com/Textualize/textual/issues/1358
assert snap_compare(SNAPSHOT_APPS_DIR / "layer_fix.py", press=["d"])
def test_modal_dialog_bindings_input(snap_compare):
# Check https://github.com/Textualize/textual/issues/2194
assert snap_compare(
SNAPSHOT_APPS_DIR / "modal_screen_bindings.py",
press=["enter", "tab", "h", "!", "left", "i", "tab"],
)
def test_modal_dialog_bindings(snap_compare):
# Check https://github.com/Textualize/textual/issues/2194
assert snap_compare(
SNAPSHOT_APPS_DIR / "modal_screen_bindings.py",
press=["enter", "tab", "h", "i", "tab", "enter"],
)

View File

@@ -39,10 +39,8 @@ class NoBindings(App[None]):
async def test_just_app_no_bindings() -> None:
"""An app with no bindings should have no bindings, other than ctrl+c."""
async with NoBindings().run_test() as pilot:
assert list(pilot.app._bindings.keys.keys()) == ["ctrl+c", "tab", "shift+tab"]
assert list(pilot.app._bindings.keys.keys()) == ["ctrl+c"]
assert pilot.app._bindings.get_key("ctrl+c").priority is True
assert pilot.app._bindings.get_key("tab").priority is False
assert pilot.app._bindings.get_key("shift+tab").priority is False
##############################################################################
@@ -63,9 +61,7 @@ class AlphaBinding(App[None]):
async def test_just_app_alpha_binding() -> None:
"""An app with a single binding should have just the one binding."""
async with AlphaBinding().run_test() as pilot:
assert sorted(pilot.app._bindings.keys.keys()) == sorted(
["ctrl+c", "tab", "shift+tab", "a"]
)
assert sorted(pilot.app._bindings.keys.keys()) == sorted(["ctrl+c", "a"])
assert pilot.app._bindings.get_key("ctrl+c").priority is True
assert pilot.app._bindings.get_key("a").priority is True
@@ -87,9 +83,7 @@ class LowAlphaBinding(App[None]):
async def test_just_app_low_priority_alpha_binding() -> None:
"""An app with a single low-priority binding should have just the one binding."""
async with LowAlphaBinding().run_test() as pilot:
assert sorted(pilot.app._bindings.keys.keys()) == sorted(
["ctrl+c", "tab", "shift+tab", "a"]
)
assert sorted(pilot.app._bindings.keys.keys()) == sorted(["ctrl+c", "a"])
assert pilot.app._bindings.get_key("ctrl+c").priority is True
assert pilot.app._bindings.get_key("a").priority is False