mirror of
https://github.com/Textualize/textual.git
synced 2025-10-17 02:38:12 +03:00
Command palette events (#4600)
* Add some events for the command palette * Sending open, close, and highlighted events from CommandPalette -> App * Docstrings * Changelog * Use Union for old python support * Docstrings
This commit is contained in:
@@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
|
||||
|
||||
## Unreleased
|
||||
|
||||
### Added
|
||||
|
||||
- Added Command Palette Opened, Closed, and OptionHighlighted events https://github.com/Textualize/textual/pull/4600
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed DataTable cursor flicker on scroll https://github.com/Textualize/textual/pull/4598
|
||||
|
||||
@@ -34,6 +34,7 @@ from .binding import Binding, BindingType
|
||||
from .containers import Horizontal, Vertical
|
||||
from .events import Click, Mount
|
||||
from .fuzzy import Matcher
|
||||
from .message import Message
|
||||
from .reactive import var
|
||||
from .screen import Screen, _SystemModalScreen
|
||||
from .timer import Timer
|
||||
@@ -541,6 +542,24 @@ class CommandPalette(_SystemModalScreen[CallbackType]):
|
||||
_PALETTE_ID: Final[str] = "--command-palette"
|
||||
"""The internal ID for the command palette."""
|
||||
|
||||
@dataclass
|
||||
class OptionHighlighted(Message):
|
||||
"""Posted to App when an option is highlighted in the command palette."""
|
||||
|
||||
highlighted_event: OptionList.OptionHighlighted
|
||||
"""The option highlighted event from the OptionList within the command palette."""
|
||||
|
||||
@dataclass
|
||||
class Opened(Message):
|
||||
"""Posted to App when the command palette is opened."""
|
||||
|
||||
@dataclass
|
||||
class Closed(Message):
|
||||
"""Posted to App when the command palette is closed."""
|
||||
|
||||
option_selected: bool
|
||||
"""True if an option was selected, False if the palette was closed without selecting an option."""
|
||||
|
||||
def __init__(self) -> None:
|
||||
"""Initialise the command palette."""
|
||||
super().__init__(id=self._PALETTE_ID)
|
||||
@@ -623,10 +642,13 @@ class CommandPalette(_SystemModalScreen[CallbackType]):
|
||||
"""
|
||||
if self.get_widget_at(event.screen_x, event.screen_y)[0] is self:
|
||||
self._cancel_gather_commands()
|
||||
self.app.post_message(CommandPalette.Closed(option_selected=False))
|
||||
self.dismiss()
|
||||
|
||||
def _on_mount(self, _: Mount) -> None:
|
||||
"""Configure the command palette once the DOM is ready."""
|
||||
|
||||
self.app.post_message(CommandPalette.Opened())
|
||||
self._calling_screen = self.app.screen_stack[-2]
|
||||
|
||||
match_style = self.get_component_rich_style(
|
||||
@@ -1085,12 +1107,14 @@ class CommandPalette(_SystemModalScreen[CallbackType]):
|
||||
# ...we should return it to the parent screen and let it
|
||||
# decide what to do with it (hopefully it'll run it).
|
||||
self._cancel_gather_commands()
|
||||
self.app.post_message(CommandPalette.Closed(option_selected=True))
|
||||
self.dismiss(self._selected_command.command)
|
||||
|
||||
@on(OptionList.OptionHighlighted)
|
||||
def _stop_event_leak(self, event: OptionList.OptionHighlighted) -> None:
|
||||
"""Stop any unused events so they don't leak to the application."""
|
||||
event.stop()
|
||||
self.app.post_message(CommandPalette.OptionHighlighted(highlighted_event=event))
|
||||
|
||||
def _action_escape(self) -> None:
|
||||
"""Handle a request to escape out of the command palette."""
|
||||
@@ -1098,6 +1122,7 @@ class CommandPalette(_SystemModalScreen[CallbackType]):
|
||||
self._list_visible = False
|
||||
else:
|
||||
self._cancel_gather_commands()
|
||||
self.app.post_message(CommandPalette.Closed(option_selected=False))
|
||||
self.dismiss()
|
||||
|
||||
def _action_command_list(self, action: str) -> None:
|
||||
|
||||
75
tests/command_palette/test_events.py
Normal file
75
tests/command_palette/test_events.py
Normal file
@@ -0,0 +1,75 @@
|
||||
from typing import Union
|
||||
from unittest import mock
|
||||
|
||||
from textual import on
|
||||
from textual.app import App
|
||||
from textual.command import CommandPalette, Hit, Hits, Provider
|
||||
|
||||
CommandPaletteEvent = Union[
|
||||
CommandPalette.Opened, CommandPalette.Closed, CommandPalette.OptionHighlighted
|
||||
]
|
||||
|
||||
|
||||
class SimpleSource(Provider):
|
||||
async def search(self, query: str) -> Hits:
|
||||
def goes_nowhere_does_nothing() -> None:
|
||||
pass
|
||||
|
||||
yield Hit(1, query, goes_nowhere_does_nothing, query)
|
||||
|
||||
|
||||
class AppWithActiveCommandPalette(App[None]):
|
||||
COMMANDS = {SimpleSource}
|
||||
|
||||
def __init__(self) -> None:
|
||||
super().__init__()
|
||||
self.events: list[CommandPaletteEvent] = []
|
||||
|
||||
def on_mount(self) -> None:
|
||||
self.action_command_palette()
|
||||
|
||||
@on(CommandPalette.Opened)
|
||||
@on(CommandPalette.Closed)
|
||||
@on(CommandPalette.OptionHighlighted)
|
||||
def record_event(
|
||||
self,
|
||||
event: CommandPaletteEvent,
|
||||
) -> None:
|
||||
self.events.append(event)
|
||||
|
||||
|
||||
async def test_command_palette_opened_event():
|
||||
app = AppWithActiveCommandPalette()
|
||||
async with app.run_test():
|
||||
assert app.events == [CommandPalette.Opened()]
|
||||
|
||||
|
||||
async def test_command_palette_closed_event():
|
||||
app = AppWithActiveCommandPalette()
|
||||
async with app.run_test() as pilot:
|
||||
await pilot.press("escape")
|
||||
assert app.events == [CommandPalette.Opened(), CommandPalette.Closed(False)]
|
||||
|
||||
|
||||
async def test_command_palette_closed_event_value():
|
||||
app = AppWithActiveCommandPalette()
|
||||
async with app.run_test() as pilot:
|
||||
await pilot.press("a")
|
||||
await pilot.press("down")
|
||||
await pilot.press("enter")
|
||||
assert app.events == [
|
||||
CommandPalette.Opened(),
|
||||
CommandPalette.OptionHighlighted(mock.ANY),
|
||||
CommandPalette.Closed(True),
|
||||
]
|
||||
|
||||
|
||||
async def test_command_palette_option_highlighted_event():
|
||||
app = AppWithActiveCommandPalette()
|
||||
async with app.run_test() as pilot:
|
||||
await pilot.press("a")
|
||||
await pilot.press("down")
|
||||
assert app.events == [
|
||||
CommandPalette.Opened(),
|
||||
CommandPalette.OptionHighlighted(mock.ANY),
|
||||
]
|
||||
Reference in New Issue
Block a user