Fix allow_focus

This commit is contained in:
Will McGugan
2025-04-11 16:17:37 +01:00
parent 5fc41eb58b
commit 13be373949
5 changed files with 86 additions and 2 deletions

View File

@@ -16,6 +16,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
- Fixed footer / key panel not updating when keymaps are applied https://github.com/Textualize/textual/pull/5724
- Fixed alignment not being applied when there are min and max limits on dimensions https://github.com/Textualize/textual/pull/5732
- Fixed issues with OptionList scrollbar not updating https://github.com/Textualize/textual/pull/5736
- Fixed allow_focus method not overriding can_focus
### Changed

View File

@@ -303,7 +303,7 @@ class ChangingThemeApp(App[None]):
widget.__class__.__name__,
(
partial(self.set_focus, widget)
if widget.can_focus
if widget.allow_focus()
else lambda: None
),
f"Focus on {widget.__class__.__name__}",

View File

@@ -378,7 +378,7 @@ class Widget(DOMNode):
"hover": lambda widget: widget.mouse_hover,
"focus": lambda widget: widget.has_focus,
"blur": lambda widget: not widget.has_focus,
"can-focus": lambda widget: widget.can_focus,
"can-focus": lambda widget: widget.allow_focus(),
"disabled": lambda widget: widget.is_disabled,
"enabled": lambda widget: not widget.is_disabled,
"dark": lambda widget: widget.app.current_theme.dark,

View File

@@ -3894,6 +3894,7 @@ def test_notifications_markup(snap_compare):
assert snap_compare(ToastApp())
def test_option_list_size_when_options_removed(snap_compare):
"""Regression test for https://github.com/Textualize/textual/issues/5728
@@ -3935,6 +3936,7 @@ def test_option_list_size_when_options_cleared(snap_compare):
assert snap_compare(OptionListApp(), press=["x"])
def test_alignment_with_auto_and_min_height(snap_compare):
"""Regression test for https://github.com/Textualize/textual/issues/5608
You should see a blue label that is centered both horizontally and vertically
@@ -3962,3 +3964,41 @@ def test_alignment_with_auto_and_min_height(snap_compare):
yield Label("centered")
assert snap_compare(AlignmentApp())
def test_allow_focus(snap_compare):
"""Regression test for https://github.com/Textualize/textual/issues/5609
You should see two placeholders split vertically.
The top should have the label "FOCUSABLE", and have a heavy red border.
The bottom should have the label "NON FOCUSABLE" and have the default border.
"""
class Focusable(Placeholder, can_focus=False):
"""Override can_focus from False to True"""
def allow_focus(self) -> bool:
return True
class NonFocusable(Placeholder, can_focus=True):
"""Override can_focus from True to False"""
def allow_focus(self) -> bool:
return False
class FocusApp(App):
CSS = """
Placeholder {
height: 1fr;
}
*:can-focus {
border: heavy red;
}
"""
def compose(self) -> ComposeResult:
yield Focusable("FOCUSABLE")
yield NonFocusable("NON FOCUSABLE")
assert snap_compare(FocusApp())

View File

@@ -2,6 +2,7 @@ from textual.app import App, ComposeResult
from textual.containers import Container, ScrollableContainer
from textual.widget import Widget
from textual.widgets import Button, Label
from textual.widgets._placeholder import Placeholder
class Focusable(Widget, can_focus=True):
@@ -464,3 +465,45 @@ async def test_get_focusable_widget_at() -> None:
await pilot.click("#egg")
# Confirm nothing focused
assert app.screen.focused is None
async def test_allow_focus_override():
"""Test that allow_focus() method override can_focus."""
class Focusable(Placeholder, can_focus=False):
"""Override can_focus from False to True"""
def allow_focus(self) -> bool:
return True
class NonFocusable(Placeholder, can_focus=True):
"""Override can_focus from True to False"""
def allow_focus(self) -> bool:
return False
class FocusApp(App):
CSS = """
Placeholder {
height: 1fr;
}
*:can-focus {
border: heavy red;
}
"""
def compose(self) -> ComposeResult:
yield Focusable("FOCUSABLE")
yield NonFocusable("NON FOCUSABLE")
app = FocusApp()
async with app.run_test():
# Default should be focused
assert isinstance(app.focused, Focusable)
# Attempt to focus non focusable
app.query(NonFocusable).focus()
# Unable to focus NonFocusable
assert isinstance(app.focused, Focusable)
# Check focus chain
assert app.screen.focus_chain == [app.query_one(Focusable)]