Make middle click not mousedown close tabs (#44916)

Closes #44856

This PR also adds an `on_aux_click` interface to the div element that follows MDM standard https://developer.mozilla.org/en-US/docs/Web/API/Element/auxclick_event

Release Notes:

- fix bug where mouse down middle click would close tab instead of full middle click

---------

Co-authored-by: Anthony Eid <anthony@zed.dev>
This commit is contained in:
Zachiah SawyerandAnthony Eid authored and GitHub committed 2026-02-05 09:21:47 +00:00
1 parent bb4327e1e6
commit 0357a4a95d
3 files changed
+72 -10

No files matched your search

+53 -4
View File
@@ -522,6 +522,20 @@ impl Interactivity {
}));
}
/// Bind the given callback to non-primary click events of this element.
/// The imperative API equivalent to [`StatefulInteractiveElement::on_aux_click`].
///
/// See [`Context::listener`](crate::Context::listener) to get access to a view's state from this callback.
pub fn on_aux_click(&mut self, listener: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static)
where
Self: Sized,
{
self.aux_click_listeners
.push(Rc::new(move |event, window, cx| {
listener(event, window, cx)
}));
}
/// On drag initiation, this callback will be used to create a new view to render the dragged value for a
/// drag and drop operation. This API should also be used as the equivalent of 'on drag start' with
/// the [`Self::on_drag_move`] API.
@@ -1190,6 +1204,21 @@ pub trait StatefulInteractiveElement: InteractiveElement {
self
}
/// Bind the given callback to non-primary click events of this element.
/// The fluent API equivalent to [`Interactivity::on_aux_click`].
///
/// See [`Context::listener`](crate::Context::listener) to get access to a view's state from this callback.
fn on_aux_click(
mut self,
listener: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static,
) -> Self
where
Self: Sized,
{
self.interactivity().on_aux_click(listener);
self
}
/// On drag initiation, this callback will be used to create a new view to render the dragged value for a
/// drag and drop operation. This API should also be used as the equivalent of 'on drag start' with
/// the [`InteractiveElement::on_drag_move`] API.
@@ -1622,6 +1651,7 @@ pub struct Interactivity {
pub(crate) drop_listeners: Vec<(TypeId, DropListener)>,
pub(crate) can_drop_predicate: Option<CanDropPredicate>,
pub(crate) click_listeners: Vec<ClickListener>,
pub(crate) aux_click_listeners: Vec<ClickListener>,
pub(crate) drag_listener: Option<(Arc<dyn Any>, DragListener)>,
pub(crate) hover_listener: Option<Box<dyn Fn(&bool, &mut Window, &mut App)>>,
pub(crate) tooltip_builder: Option<TooltipBuilder>,
@@ -1815,6 +1845,7 @@ impl Interactivity {
|| !self.mouse_down_listeners.is_empty()
|| !self.mouse_move_listeners.is_empty()
|| !self.click_listeners.is_empty()
|| !self.aux_click_listeners.is_empty()
|| !self.scroll_wheel_listeners.is_empty()
|| self.drag_listener.is_some()
|| !self.drop_listeners.is_empty()
@@ -2237,6 +2268,7 @@ impl Interactivity {
let mut drag_listener = mem::take(&mut self.drag_listener);
let drop_listeners = mem::take(&mut self.drop_listeners);
let click_listeners = mem::take(&mut self.click_listeners);
let aux_click_listeners = mem::take(&mut self.aux_click_listeners);
let can_drop_predicate = mem::take(&mut self.can_drop_predicate);
if !drop_listeners.is_empty() {
@@ -2273,7 +2305,10 @@ impl Interactivity {
}
if let Some(element_state) = element_state {
if !click_listeners.is_empty() || drag_listener.is_some() {
if !click_listeners.is_empty()
|| !aux_click_listeners.is_empty()
|| drag_listener.is_some()
{
let pending_mouse_down = element_state
.pending_mouse_down
.get_or_insert_with(Default::default)
@@ -2287,9 +2322,10 @@ impl Interactivity {
window.on_mouse_event({
let pending_mouse_down = pending_mouse_down.clone();
let hitbox = hitbox.clone();
let has_aux_click_listeners = !aux_click_listeners.is_empty();
move |event: &MouseDownEvent, phase, window, _cx| {
if phase == DispatchPhase::Bubble
&& event.button == MouseButton::Left
&& (event.button == MouseButton::Left || has_aux_click_listeners)
&& hitbox.is_hovered(window)
{
*pending_mouse_down.borrow_mut() = Some(event.clone());
@@ -2311,6 +2347,7 @@ impl Interactivity {
&& !cx.has_active_drag()
&& (event.position - mouse_down.position).magnitude() > DRAG_THRESHOLD
&& let Some((drag_value, drag_listener)) = drag_listener.take()
&& mouse_down.button == MouseButton::Left
{
*clicked_state.borrow_mut() = ElementClickedState::default();
let cursor_offset = event.position - hitbox.origin;
@@ -2387,12 +2424,24 @@ impl Interactivity {
// Fire click handlers during the bubble phase.
DispatchPhase::Bubble => {
if let Some(mouse_down) = captured_mouse_down.take() {
let btn = mouse_down.button;
let mouse_click = ClickEvent::Mouse(MouseClickEvent {
down: mouse_down,
up: event.clone(),
});
for listener in &click_listeners {
listener(&mouse_click, window, cx);
match btn {
MouseButton::Left => {
for listener in &click_listeners {
listener(&mouse_click, window, cx);
}
}
_ => {
for listener in &aux_click_listeners {
listener(&mouse_click, window, cx);
}
}
}
}
}
+13
View File
@@ -284,6 +284,19 @@ impl ClickEvent {
}
}
/// Returns if this was a middle click
///
/// `Keyboard`: false
/// `Mouse`: Whether the middle button was pressed and released
pub fn is_middle_click(&self) -> bool {
match self {
ClickEvent::Keyboard(_) => false,
ClickEvent::Mouse(event) => {
event.down.button == MouseButton::Middle && event.up.button == MouseButton::Middle
}
}
}
/// Returns whether the click was a standard click
///
/// `Keyboard`: Always true
+6 -6
View File
@@ -2790,8 +2790,6 @@ impl Pane {
let item_handle = item.boxed_clone();
move |pane: &mut Self, event: &ClickEvent, window, cx| {
if event.click_count() > 1 {
// On double-click, dispatch the Rename action (when available)
// instead of just activating the item.
pane.unpreview_item_if_preview(item_id);
let extra_actions = item_handle.tab_extra_context_menu_actions(window, cx);
if let Some((_, action)) = extra_actions
@@ -2809,10 +2807,12 @@ impl Pane {
pane.activate_item(ix, true, true, window, cx)
}
}))
// TODO: This should be a click listener with the middle mouse button instead of a mouse down listener.
.on_mouse_down(
MouseButton::Middle,
cx.listener(move |pane, _event, window, cx| {
.on_aux_click(
cx.listener(move |pane: &mut Self, event: &ClickEvent, window, cx| {
if !event.is_middle_click() {
return;
}
pane.close_item_by_id(item_id, SaveIntent::Close, window, cx)
.detach_and_log_err(cx);
}),