Skip to content

Commit

Permalink
fix(text area)!: stop escape shifting focus if default tab behaviour (#…
Browse files Browse the repository at this point in the history
…4125)

* fix(text area): stop escape shifting focus if default tab behaviour

* fix recent update to changelog

* address review feedback

* update changelog
  • Loading branch information
TomJGooding authored Feb 7, 2024
1 parent 5d6c61a commit e27c41c
Show file tree
Hide file tree
Showing 4 changed files with 75 additions and 9 deletions.
18 changes: 12 additions & 6 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,7 @@ 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/).

## [0.48.2] - 2024-02-02

### Fixed

- Fixed a hang in the Linux driver when connected to a pipe https://github.com/Textualize/textual/issues/4104
- Fixed broken `OptionList` `Option.id` mappings https://github.com/Textualize/textual/issues/4101
## Unreleased

### Added

Expand All @@ -20,6 +15,17 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
- Added DOMNode.action_toggle https://github.com/Textualize/textual/pull/4075
- Added Worker.cancelled_event https://github.com/Textualize/textual/pull/4075

### Fixed

- Breaking change: `TextArea` will not use `Escape` to shift focus if the `tab_behaviour` is the default https://github.com/Textualize/textual/issues/4110

## [0.48.2] - 2024-02-02

### Fixed

- Fixed a hang in the Linux driver when connected to a pipe https://github.com/Textualize/textual/issues/4104
- Fixed broken `OptionList` `Option.id` mappings https://github.com/Textualize/textual/issues/4101

### Changed

- Breaking change: keyboard navigation in `RadioSet`, `ListView`, `OptionList`, and `SelectionList`, no longer allows highlighting disabled items https://github.com/Textualize/textual/issues/3881
Expand Down
4 changes: 3 additions & 1 deletion docs/widgets/text_area.md
Original file line number Diff line number Diff line change
Expand Up @@ -283,11 +283,13 @@ This immediately updates the appearance of the `TextArea`:
```{.textual path="docs/examples/widgets/text_area_custom_theme.py" columns="42" lines="8"}
```

### Tab behaviour
### Tab and Escape behaviour

Pressing the ++tab++ key will shift focus to the next widget in your application by default.
This matches how other widgets work in Textual.

To have ++tab++ insert a `\t` character, set the `tab_behaviour` attribute to the string value `"indent"`.
While in this mode, you can shift focus by pressing the ++escape++ key.

### Indentation

Expand Down
7 changes: 5 additions & 2 deletions src/textual/widgets/_text_area.py
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,6 @@ class TextArea(ScrollView, can_focus=True):
"""

BINDINGS = [
Binding("escape", "screen.focus_next", "Shift Focus", show=False),
# Cursor movement
Binding("up", "cursor_up", "cursor up", show=False),
Binding("down", "cursor_down", "cursor down", show=False),
Expand Down Expand Up @@ -213,7 +212,6 @@ class TextArea(ScrollView, can_focus=True):
"""
| Key(s) | Description |
| :- | :- |
| escape | Focus on the next item. |
| up | Move the cursor up. |
| down | Move the cursor down. |
| left | Move the cursor left. |
Expand Down Expand Up @@ -1213,6 +1211,11 @@ async def _on_key(self, event: events.Key) -> None:
"enter": "\n",
}
if self.tab_behaviour == "indent":
if key == "escape":
event.stop()
event.prevent_default()
self.screen.focus_next()
return
if self.indent_type == "tabs":
insert_values["tab"] = "\t"
else:
Expand Down
55 changes: 55 additions & 0 deletions tests/text_area/test_escape_binding.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
from textual.app import App, ComposeResult
from textual.screen import ModalScreen
from textual.widgets import Button, TextArea


class TextAreaDialog(ModalScreen):
BINDINGS = [("escape", "dismiss")]

def compose(self) -> ComposeResult:
yield TextArea(
tab_behaviour="focus", # the default
)
yield Button("Submit")


class TextAreaDialogApp(App):
def on_mount(self) -> None:
self.push_screen(TextAreaDialog())


async def test_escape_key_when_tab_behaviour_is_focus():
"""Regression test for https://github.com/Textualize/textual/issues/4110
When the `tab_behaviour` of TextArea is the default to shift focus,
pressing <Escape> should not shift focus but instead skip and allow any
parent bindings to run.
"""

app = TextAreaDialogApp()
async with app.run_test() as pilot:
# Sanity check
assert isinstance(pilot.app.screen, TextAreaDialog)
assert isinstance(pilot.app.focused, TextArea)

# Pressing escape should dismiss the dialog screen, not focus the button
await pilot.press("escape")
assert not isinstance(pilot.app.screen, TextAreaDialog)


async def test_escape_key_when_tab_behaviour_is_indent():
"""When the `tab_behaviour` of TextArea is indent rather than switch focus,
pressing <Escape> should instead shift focus.
"""

app = TextAreaDialogApp()
async with app.run_test() as pilot:
# Sanity check
assert isinstance(pilot.app.screen, TextAreaDialog)
assert isinstance(pilot.app.focused, TextArea)

pilot.app.query_one(TextArea).tab_behaviour = "indent"
# Pressing escape should focus the button, not dismiss the dialog screen
await pilot.press("escape")
assert isinstance(pilot.app.screen, TextAreaDialog)
assert isinstance(pilot.app.focused, Button)

0 comments on commit e27c41c

Please sign in to comment.