From 955e8983d1f4fcbc7beda5f8331c64d22fac954c Mon Sep 17 00:00:00 2001 From: Jake Go Date: Thu, 15 Jan 2026 08:55:20 -0500 Subject: [PATCH] ui: Dismiss context menus when window loses focus (#46866) Context menus close when the window becomes inactive (e.g., clicking to another window or app). Previously, menus would remain visible until the window regained focus. This aligns Zed's context menu behavior with native macOS apps, where NSMenu automatically dismisses when the window loses focus. Users expect menus to close when switching windows or apps. Uses `observe_window_activation` to detect when the window becomes inactive and calls `cancel` to dismiss the menu. Release Notes: - Fixed context menus dismiss/cancel when the window loses focus --------- Co-authored-by: Danilo Leal --- crates/ui/src/components/context_menu.rs | 30 ++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/crates/ui/src/components/context_menu.rs b/crates/ui/src/components/context_menu.rs index 4372ebd821..97f6624d1e 100644 --- a/crates/ui/src/components/context_menu.rs +++ b/crates/ui/src/components/context_menu.rs @@ -220,6 +220,7 @@ pub struct ContextMenu { end_slot_action: Option>, key_context: SharedString, _on_blur_subscription: Subscription, + _on_window_deactivate_subscription: Subscription, keep_open_on_confirm: bool, fixed_width: Option, main_menu: Option>, @@ -295,6 +296,12 @@ impl ContextMenu { this.cancel(&menu::Cancel, window, cx) }, ); + let _on_window_deactivate_subscription = + cx.observe_window_activation(window, |this: &mut ContextMenu, window, cx| { + if !window.is_window_active() { + this.cancel(&menu::Cancel, window, cx); + } + }); window.refresh(); f( @@ -309,6 +316,7 @@ impl ContextMenu { end_slot_action: None, key_context: "menu".into(), _on_blur_subscription, + _on_window_deactivate_subscription, keep_open_on_confirm: false, fixed_width: None, main_menu: None, @@ -372,6 +380,12 @@ impl ContextMenu { this.cancel(&menu::Cancel, window, cx) }, ); + let _on_window_deactivate_subscription = + cx.observe_window_activation(window, |this: &mut ContextMenu, window, cx| { + if !window.is_window_active() { + this.cancel(&menu::Cancel, window, cx); + } + }); window.refresh(); (builder.clone())( @@ -386,6 +400,7 @@ impl ContextMenu { end_slot_action: None, key_context: "menu".into(), _on_blur_subscription, + _on_window_deactivate_subscription, keep_open_on_confirm: true, fixed_width: None, main_menu: None, @@ -455,6 +470,14 @@ impl ContextMenu { this.cancel(&menu::Cancel, window, cx) }, ), + _on_window_deactivate_subscription: cx.observe_window_activation( + window, + |this: &mut ContextMenu, window, cx| { + if !window.is_window_active() { + this.cancel(&menu::Cancel, window, cx); + } + }, + ), keep_open_on_confirm: false, fixed_width: None, main_menu: None, @@ -1206,6 +1229,12 @@ impl ContextMenu { window, |_this: &mut ContextMenu, _window, _cx| {}, ); + let _on_window_deactivate_subscription = + cx.observe_window_activation(window, |this: &mut ContextMenu, window, cx| { + if !window.is_window_active() { + this.cancel(&menu::Cancel, window, cx); + } + }); let mut menu = ContextMenu { builder: None, @@ -1218,6 +1247,7 @@ impl ContextMenu { end_slot_action: None, key_context: "menu".into(), _on_blur_subscription, + _on_window_deactivate_subscription, keep_open_on_confirm: false, fixed_width: None, documentation_aside: None,