Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions src/textual/dom.py
Original file line number Diff line number Diff line change
Expand Up @@ -492,6 +492,10 @@ def trap_focus(self, trap_focus: bool = True) -> None:
trap_focus: `True` to trap focus. `False` to restore default behavior.
"""
self._trap_focus = trap_focus
try:
self.screen._focus_chain_cache = None
except Exception:
pass

def run_worker(
self,
Expand Down Expand Up @@ -1328,6 +1332,10 @@ def _add_children(self, *nodes: Widget) -> None:
node._attach(self)
_append(node)
node._add_children(*node._pending_children)
try:
self.screen._focus_chain_cache = None
except Exception:
pass

WalkType = TypeVar("WalkType", bound="DOMNode")

Expand Down
10 changes: 8 additions & 2 deletions src/textual/screen.py
Original file line number Diff line number Diff line change
Expand Up @@ -326,6 +326,9 @@ def __init__(
self._css_update_count = -1
"""Track updates to CSS."""

self._focus_chain_cache: list[Widget] | None = None
"""Cached focus chain, invalidated on layout or focus change."""

self._layout_widgets: dict[DOMNode, set[Widget]] = {}
"""Widgets whose layout may have changed."""

Expand Down Expand Up @@ -771,8 +774,8 @@ def _select_all_in_widget(self, widget: Widget) -> None:
@property
def focus_chain(self) -> list[Widget]:
"""A list of widgets that may receive focus, in focus order."""
# TODO: Calculating a focus chain is moderately expensive.
# Suspect we can move focus without calculating the entire thing again.
if self._focus_chain_cache is not None:
return self._focus_chain_cache

widgets: list[Widget] = []
add_widget = widgets.append
Expand Down Expand Up @@ -823,6 +826,7 @@ def focus_chain(self) -> list[Widget]:
if node_is_visible and node.allow_focus():
add_widget(node)

self._focus_chain_cache = widgets
return widgets

def _move_focus(
Expand Down Expand Up @@ -1113,6 +1117,7 @@ def set_focus(
# Widget is already focused
return

self._focus_chain_cache = None
focused: Widget | None = None
blurred: Widget | None = None

Expand Down Expand Up @@ -1423,6 +1428,7 @@ async def _on_layout(self, message: messages.Layout) -> None:
break
widget = ancestor

self._focus_chain_cache = None
if layout_required and not self._layout_required:
self._layout_required = True
self.check_idle()
Expand Down