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 <daniloleal09@gmail.com>
This commit is contained in:
Jake GoandDanilo Leal authored and GitHub committed 2026-01-15 13:55:20 +00:00
1 parent b8bbe163fc
commit 955e8983d1
1 file changed
+30
+30
View File
@@ -220,6 +220,7 @@ pub struct ContextMenu {
end_slot_action: Option<Box<dyn Action>>,
key_context: SharedString,
_on_blur_subscription: Subscription,
_on_window_deactivate_subscription: Subscription,
keep_open_on_confirm: bool,
fixed_width: Option<DefiniteLength>,
main_menu: Option<Entity<ContextMenu>>,
@@ -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,