git.lucas.co / cce-mail
mail client (IMAP/SMTP)
git clone https://git.lucas.co/cce-mail.git

commita82cac33ea1968aef0a25518f42e4c0cdee7de78
parent38be86c6fd
authorLucas Galante <[email protected]>
date2026-08-30 19:59
fix: the open bar dropdowns and the context menu take the keyboard

Escape closes an open account/mail/folder dropdown (arrows move the
hover, Enter selects), and Escape dismisses a visible context menu -
both were mouse-only: every key propagate in handle_key_input was
gated on compose or search state that an open dropdown never
satisfies, so no key ever reached one (the cce-files bug), and the
context menu had no key check at all. Dropdowns are drained exactly
like their mouse events; the freshly-opened accounts reload stays
mouse-only, since a key cannot open a closed dropdown.

Co-Authored-By: Claude Fable 5 <[email protected]>

 src/main.rs | 63 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 63 insertions(+)

diff --git a/src/main.rs b/src/main.rs
index a597258..89b50bd 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -4629,6 +4629,69 @@ impl Application for ClearEmailApp {
         // Read before ctx: detail_body_geom takes &self whole.
         let (_, body_h) = self.detail_body_geom();
 
+        // A visible context menu takes Escape — it was mouse-dismiss only.
+        if event.state == ElementState::Pressed
+            && event.logical_key == Key::Named(cce_ui::widget::NamedKey::Escape)
+            && cce_ui::widget::context_menu::is_visible()
+        {
+            cce_ui::widget::context_menu::hide();
+            self.context_menu_actions.clear();
+            *needs_rebuild = true;
+            self.needs_rebuild = true;
+            return None;
+        }
+
+        // An open bar dropdown takes the keyboard — Escape closes it, arrows
+        // move the hover, Enter selects — routed and drained exactly like its
+        // mouse events (see handle_mouse_input; the freshly-opened accounts
+        // reload stays mouse-only, since a key cannot open a closed
+        // dropdown). No key ever reached these before: every propagate below
+        // is gated on compose/search state that an open dropdown never
+        // satisfies (the cce-files bug).
+        if self.account_dropdown.open {
+            if self.ui_context.propagate_event(&kev, self.account_dropdown.id()) {
+                if self.account_dropdown.take_change() {
+                    let idx = self.account_dropdown.selected;
+                    if idx < self.accounts.len() {
+                        if idx != self.selected_account_idx {
+                            msg_out = Some(AppMessage::SelectAccount(idx));
+                        }
+                    } else {
+                        // The trailing "Manage Accounts…" pseudo-entry.
+                        self.account_dropdown.selected = self.selected_account_idx;
+                        msg_out = Some(AppMessage::ManageAccounts);
+                    }
+                }
+                *needs_rebuild = true;
+                self.needs_rebuild = true;
+                return msg_out;
+            }
+        } else if self.mail_menu.open {
+            if self.ui_context.propagate_event(&kev, self.mail_menu.id()) {
+                if self.mail_menu.take_change() {
+                    msg_out = match self.mail_menu.selected {
+                        0 => Some(AppMessage::ComposeNew),
+                        1 => Some(AppMessage::SyncNow),
+                        _ => Some(AppMessage::Quit),
+                    };
+                }
+                *needs_rebuild = true;
+                self.needs_rebuild = true;
+                return msg_out;
+            }
+        } else if self.folder_dropdown.open {
+            if self.ui_context.propagate_event(&kev, self.folder_dropdown.id()) {
+                if self.folder_dropdown.take_change() {
+                    if let Some(f) = self.folders.get(self.folder_dropdown.selected) {
+                        msg_out = Some(AppMessage::SwitchFolder(f.tag.clone()));
+                    }
+                }
+                *needs_rebuild = true;
+                self.needs_rebuild = true;
+                return msg_out;
+            }
+        }
+
         // Arrow keys move the selection and Delete removes it. Handled up
         // here, ahead of the ui_context borrow and everything routed through
         // it: the list would otherwise eat Up/Down to scroll itself, and