Refine modal focus handling: update tab navigation logic and simplify focus setting in SaveConfirmationModal tests

This commit is contained in:
Philip Henning 2025-07-30 14:58:43 +02:00
parent 77d8e647f2
commit 1dd54b0cb5
3 changed files with 12 additions and 66 deletions

View file

@ -839,11 +839,16 @@ class HostsManagerApp(App):
def on_key(self, event) -> None: def on_key(self, event) -> None:
"""Handle key events to override default tab behavior in edit mode.""" """Handle key events to override default tab behavior in edit mode."""
if self.entry_edit_mode and event.key == "tab": # Only handle custom tab navigation if in entry edit mode AND no modal is open
if self.entry_edit_mode and len(self.screen_stack) == 1 and event.key == "tab":
# Prevent default tab behavior and use our custom navigation # Prevent default tab behavior and use our custom navigation
event.prevent_default() event.prevent_default()
self.action_next_field() self.action_next_field()
elif self.entry_edit_mode and event.key == "shift+tab": elif (
self.entry_edit_mode
and len(self.screen_stack) == 1
and event.key == "shift+tab"
):
# Prevent default shift+tab behavior and use our custom navigation # Prevent default shift+tab behavior and use our custom navigation
event.prevent_default() event.prevent_default()
self.action_prev_field() self.action_prev_field()

View file

@ -95,59 +95,11 @@ class SaveConfirmationModal(ModalScreen):
) )
def on_mount(self) -> None: def on_mount(self) -> None:
"""Called when the modal is mounted. Set focus and ensure modal captures input.""" """Called when the modal is mounted. Set focus to the first button."""
# Set focus to the modal screen itself first # Focus on the Save button by default
self.focus()
# Then focus on the Save button
self.call_after_refresh(self._focus_save_button)
def _focus_save_button(self) -> None:
"""Focus the save button after refresh."""
save_button = self.query_one("#save-button", Button) save_button = self.query_one("#save-button", Button)
save_button.focus() save_button.focus()
def on_key(self, event) -> None:
"""Handle key events, ensuring tab navigation works within the modal."""
if event.key == "tab":
# Get all buttons in order
buttons = [
self.query_one("#save-button", Button),
self.query_one("#discard-button", Button),
self.query_one("#cancel-button", Button),
]
# Find currently focused button and move to next
for i, button in enumerate(buttons):
if button.has_focus:
next_button = buttons[(i + 1) % len(buttons)]
next_button.focus()
event.prevent_default()
return
# If no button has focus, focus the first one
buttons[0].focus()
event.prevent_default()
elif event.key == "shift+tab":
# Get all buttons in order
buttons = [
self.query_one("#save-button", Button),
self.query_one("#discard-button", Button),
self.query_one("#cancel-button", Button),
]
# Find currently focused button and move to previous
for i, button in enumerate(buttons):
if button.has_focus:
prev_button = buttons[(i - 1) % len(buttons)]
prev_button.focus()
event.prevent_default()
return
# If no button has focus, focus the last one
buttons[-1].focus()
event.prevent_default()
def on_button_pressed(self, event: Button.Pressed) -> None: def on_button_pressed(self, event: Button.Pressed) -> None:
"""Handle button presses.""" """Handle button presses."""
if event.button.id == "save-button": if event.button.id == "save-button":

View file

@ -56,25 +56,14 @@ class TestSaveConfirmationModal:
modal.dismiss.assert_called_once_with("cancel") modal.dismiss.assert_called_once_with("cancel")
@patch.object(SaveConfirmationModal, "call_after_refresh")
@patch.object(SaveConfirmationModal, "focus")
def test_on_mount_sets_focus(self, mock_focus, mock_call_after_refresh):
"""Test that on_mount sets focus to the modal and schedules button focus."""
modal = SaveConfirmationModal()
modal.on_mount()
mock_focus.assert_called_once()
mock_call_after_refresh.assert_called_once()
@patch.object(SaveConfirmationModal, "query_one") @patch.object(SaveConfirmationModal, "query_one")
def test_focus_save_button(self, mock_query_one): def test_on_mount_sets_focus(self, mock_query_one):
"""Test that _focus_save_button focuses the save button.""" """Test that on_mount sets focus to the save button."""
modal = SaveConfirmationModal() modal = SaveConfirmationModal()
mock_save_button = Mock() mock_save_button = Mock()
mock_query_one.return_value = mock_save_button mock_query_one.return_value = mock_save_button
modal._focus_save_button() modal.on_mount()
mock_query_one.assert_called_once_with("#save-button", Button) mock_query_one.assert_called_once_with("#save-button", Button)
mock_save_button.focus.assert_called_once() mock_save_button.focus.assert_called_once()