AUTO_FOCUS targets first focusable widget.

Related issues: #2578.
This commit is contained in:
Rodrigo Girão Serrão
2023-05-16 11:27:24 +01:00
parent 6147c28dbf
commit b592ac077a
3 changed files with 24 additions and 5 deletions

View File

@@ -21,6 +21,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
- Fixed `TreeNode.toggle` and `TreeNode.toggle_all` not posting a `Tree.NodeExpanded` or `Tree.NodeCollapsed` message https://github.com/Textualize/textual/issues/2535
- `footer--description` component class was being ignored https://github.com/Textualize/textual/issues/2544
- Pasting empty selection in `Input` would raise an exception https://github.com/Textualize/textual/issues/2563
- `Screen.AUTO_FOCUS` now focuses the first _focusable_ widget that matches the selector https://github.com/Textualize/textual/issues/2578
### Added

View File

@@ -668,11 +668,14 @@ class Screen(Generic[ScreenResultType], Widget):
size = self.app.size
if self.AUTO_FOCUS is not None and self.focused is None:
try:
to_focus = self.query(self.AUTO_FOCUS).first()
focus_candidates = self.query(self.AUTO_FOCUS)
except NoMatches:
pass
else:
self.set_focus(to_focus)
for widget in focus_candidates:
if widget.focusable:
self.set_focus(widget)
break
self._refresh_layout(size, full=True)
self.refresh()

View File

@@ -6,7 +6,7 @@ import pytest
from textual.app import App, ScreenStackError
from textual.screen import Screen
from textual.widgets import Button, Input
from textual.widgets import Button, Input, Label
skip_py310 = pytest.mark.skipif(
sys.version_info.minor == 10 and sys.version_info.major == 3,
@@ -155,8 +155,7 @@ async def test_screens():
async def test_auto_focus():
class MyScreen(Screen[None]):
def compose(self) -> None:
print("composing")
def compose(self):
yield Button()
yield Input(id="one")
yield Input(id="two")
@@ -192,3 +191,19 @@ async def test_auto_focus():
assert app.focused is None
app.pop_screen()
assert app.focused.id == "two"
async def test_auto_focus_skips_non_focusable_widgets():
class MyScreen(Screen[None]):
def compose(self):
yield Label()
yield Button()
class MyApp(App[None]):
def on_mount(self):
self.push_screen(MyScreen())
app = MyApp()
async with app.run_test():
assert app.focused is not None
assert isinstance(app.focused, Button)