Skip to content

Commit db3a675

Browse files
authored
Merge pull request #5737 from Textualize/focusable
Fix allow_focus method
2 parents 2c16c10 + cd22309 commit db3a675

File tree

6 files changed

+239
-3
lines changed

6 files changed

+239
-3
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
1616
- Fixed footer / key panel not updating when keymaps are applied https://github.com/Textualize/textual/pull/5724
1717
- Fixed alignment not being applied when there are min and max limits on dimensions https://github.com/Textualize/textual/pull/5732
1818
- Fixed issues with OptionList scrollbar not updating https://github.com/Textualize/textual/pull/5736
19+
- Fixed allow_focus method not overriding `can_focus()` https://github.com/Textualize/textual/pull/5737
1920

2021
### Changed
2122

examples/theme_sandbox.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -303,7 +303,7 @@ def action_widget_search(self) -> None:
303303
widget.__class__.__name__,
304304
(
305305
partial(self.set_focus, widget)
306-
if widget.can_focus
306+
if widget.allow_focus()
307307
else lambda: None
308308
),
309309
f"Focus on {widget.__class__.__name__}",

src/textual/widget.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -378,7 +378,7 @@ class Widget(DOMNode):
378378
"hover": lambda widget: widget.mouse_hover,
379379
"focus": lambda widget: widget.has_focus,
380380
"blur": lambda widget: not widget.has_focus,
381-
"can-focus": lambda widget: widget.can_focus,
381+
"can-focus": lambda widget: widget.allow_focus(),
382382
"disabled": lambda widget: widget.is_disabled,
383383
"enabled": lambda widget: not widget.is_disabled,
384384
"dark": lambda widget: widget.app.current_theme.dark,
@@ -2111,7 +2111,7 @@ def focusable(self) -> bool:
21112111
"""Can this widget currently be focused?"""
21122112
return (
21132113
not self.loading
2114-
and self.can_focus
2114+
and self.allow_focus()
21152115
and self.visible
21162116
and not self._self_or_ancestors_disabled
21172117
)
Loading

tests/snapshot_tests/test_snapshots.py

+40
Original file line numberDiff line numberDiff line change
@@ -3894,6 +3894,7 @@ def on_mount(self) -> None:
38943894

38953895
assert snap_compare(ToastApp())
38963896

3897+
38973898
def test_option_list_size_when_options_removed(snap_compare):
38983899
"""Regression test for https://github.com/Textualize/textual/issues/5728
38993900
@@ -3935,6 +3936,7 @@ def action_clear_options(self) -> None:
39353936

39363937
assert snap_compare(OptionListApp(), press=["x"])
39373938

3939+
39383940
def test_alignment_with_auto_and_min_height(snap_compare):
39393941
"""Regression test for https://github.com/Textualize/textual/issues/5608
39403942
You should see a blue label that is centered both horizontally and vertically
@@ -3962,3 +3964,41 @@ def compose(self) -> ComposeResult:
39623964
yield Label("centered")
39633965

39643966
assert snap_compare(AlignmentApp())
3967+
3968+
3969+
def test_allow_focus(snap_compare):
3970+
"""Regression test for https://github.com/Textualize/textual/issues/5609
3971+
3972+
You should see two placeholders split vertically.
3973+
The top should have the label "FOCUSABLE", and have a heavy red border.
3974+
The bottom should have the label "NON FOCUSABLE" and have the default border.
3975+
"""
3976+
3977+
class Focusable(Placeholder, can_focus=False):
3978+
"""Override can_focus from False to True"""
3979+
3980+
def allow_focus(self) -> bool:
3981+
return True
3982+
3983+
class NonFocusable(Placeholder, can_focus=True):
3984+
"""Override can_focus from True to False"""
3985+
3986+
def allow_focus(self) -> bool:
3987+
return False
3988+
3989+
class FocusApp(App):
3990+
CSS = """
3991+
Placeholder {
3992+
height: 1fr;
3993+
}
3994+
*:can-focus {
3995+
border: heavy red;
3996+
}
3997+
3998+
"""
3999+
4000+
def compose(self) -> ComposeResult:
4001+
yield Focusable("FOCUSABLE")
4002+
yield NonFocusable("NON FOCUSABLE")
4003+
4004+
assert snap_compare(FocusApp())

tests/test_focus.py

+43
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
from textual.containers import Container, ScrollableContainer
33
from textual.widget import Widget
44
from textual.widgets import Button, Label
5+
from textual.widgets._placeholder import Placeholder
56

67

78
class Focusable(Widget, can_focus=True):
@@ -464,3 +465,45 @@ def compose(self) -> ComposeResult:
464465
await pilot.click("#egg")
465466
# Confirm nothing focused
466467
assert app.screen.focused is None
468+
469+
470+
async def test_allow_focus_override():
471+
"""Test that allow_focus() method override can_focus."""
472+
473+
class Focusable(Placeholder, can_focus=False):
474+
"""Override can_focus from False to True"""
475+
476+
def allow_focus(self) -> bool:
477+
return True
478+
479+
class NonFocusable(Placeholder, can_focus=True):
480+
"""Override can_focus from True to False"""
481+
482+
def allow_focus(self) -> bool:
483+
return False
484+
485+
class FocusApp(App):
486+
CSS = """
487+
Placeholder {
488+
height: 1fr;
489+
}
490+
*:can-focus {
491+
border: heavy red;
492+
}
493+
494+
"""
495+
496+
def compose(self) -> ComposeResult:
497+
yield Focusable("FOCUSABLE")
498+
yield NonFocusable("NON FOCUSABLE")
499+
500+
app = FocusApp()
501+
async with app.run_test():
502+
# Default should be focused
503+
assert isinstance(app.focused, Focusable)
504+
# Attempt to focus non focusable
505+
app.query(NonFocusable).focus()
506+
# Unable to focus NonFocusable
507+
assert isinstance(app.focused, Focusable)
508+
# Check focus chain
509+
assert app.screen.focus_chain == [app.query_one(Focusable)]

0 commit comments

Comments
 (0)