Menus · button rows: a reader presses a button through Click and hears the marked one; the panel is a menu
CI / platforms (windows-latest) (push) Failing after 10s
CI / crate (push) Failing after 10m45s
CI / site (push) Successful in 27s
CI / platforms (ubuntu-24.04) (push) Canceled after 0s

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
ZacharyZhang-NYandClaude Opus 5.5 committed 2026-10-13 21:25:18 -04:00
1 parent ed75621a6d
commit 1b8d67839a
3 files changed
+59 -27

No files matched your search

+32 -18
View File
@@ -1,6 +1,9 @@
use std::rc::Rc;
use gpui::{
AnyElement, App, Div, ElementId, Entity, FontWeight, InteractiveElement, IntoElement,
MouseButton, ParentElement, Role, SharedString, Stateful, Styled, div, prelude::*,
MouseButton, ParentElement, Role, SharedString, Stateful, Styled, accesskit::Action, div,
prelude::*,
};
use super::{
@@ -86,9 +89,9 @@ fn row(item: &MenuItem, ix: usize, current: bool, cx: &App) -> Stateful<Div> {
/// Button `column` of row `ix`, named by its label.
pub(super) fn button(
item: &MenuItem,
ix: usize,
column: usize,
(ix, column): (usize, usize),
current: bool,
press: Run,
cx: &App,
) -> Stateful<Div> {
let theme = cx.theme();
@@ -113,11 +116,23 @@ pub(super) fn button(
.rounded(theme.radius(Radius::Md))
.border_1()
.border_color(colors.border)
.aria_selected(current)
.when(current, |button| button.aria_active_descendant())
.when(current && !item.disabled, |button| button.bg(colors.hover))
.when(!item.disabled, |button| button.cursor_pointer())
.when(item.disabled, |button| {
button.aria_description(i18n::text(cx, "state.unavailable", &[]))
})
.when(!item.disabled, |button| {
let read = press.clone();
button
.cursor_pointer()
.on_mouse_down(MouseButton::Left, move |_, window, cx| {
window.prevent_default();
cx.stop_propagation();
press(window, cx);
})
.on_a11y_action(Action::Click, move |_, window, cx| read(window, cx))
})
.child(Icon::new(icon).size(IconSize::Sm).color(fg))
.tooltip(Tooltip::rich(move |_, cx| {
div()
@@ -174,20 +189,18 @@ pub(super) fn panel(
let (hover, click, close, menu) =
(state.clone(), state.clone(), close.clone(), menu.clone());
let hover_menu = menu.clone();
button(item, ix, column, current, cx).when(!item.disabled, |button| {
button
.on_mouse_move(move |_, _, cx| {
let open = hover.read(cx);
let marked = open.levels.get(depth).map(|l| (l.at, l.column));
if marked != Some((Some(ix), column)) {
mark(&hover, &hover_menu, depth, (ix, column), false, cx);
}
})
.on_mouse_down(MouseButton::Left, move |_, window, cx| {
window.prevent_default();
cx.stop_propagation();
choose(&click, &menu, depth, (ix, column), &close, window, cx);
})
let press: Run = Rc::new(move |window, cx| {
choose(&click, &menu, depth, (ix, column), &close, window, cx)
});
let drawn = button(item, (ix, column), current, press, cx);
drawn.when(!item.disabled, |button| {
button.on_mouse_move(move |_, _, cx| {
let open = hover.read(cx);
let marked = open.levels.get(depth).map(|l| (l.at, l.column));
if marked != Some((Some(ix), column)) {
mark(&hover, &hover_menu, depth, (ix, column), false, cx);
}
})
})
}))
.into_any_element(),
@@ -230,6 +243,7 @@ pub(super) fn panel(
}
});
surface((id.clone(), format!("panel-{depth}")), cx)
.role(Role::Menu)
.relative()
// Presses here are the menu's, not its host's.
.on_mouse_down(MouseButton::Left, |_, window, cx| {
+26 -8
View File
@@ -214,16 +214,34 @@ fn a_disabled_button_says_it_is_unavailable(cx: &mut TestAppContext) {
Theme::init(cx);
let off = MenuItem::new("Copy").icon(IconName::Copy).disabled(true);
let on = MenuItem::new("Cut").icon(IconName::Scissors);
let read = |item: &MenuItem, cx: &mut App| {
let mut node = gpui::accesskit::Node::new(gpui::Role::Button);
gpui::Element::write_a11y_info(
&super::super::draw::button(item, 0, 0, false, cx),
&mut node,
);
node.description().map(str::to_owned)
};
let read =
|item: &MenuItem, cx: &mut App| node(item, false, cx).description().map(str::to_owned);
let expected = crate::i18n::text(cx, "state.unavailable", &[]).to_string();
assert_eq!(read(&off, cx), Some(expected));
assert_eq!(read(&on, cx), None);
});
}
/// The accessibility node `item` draws as a menu button.
fn node(item: &MenuItem, current: bool, cx: &mut App) -> gpui::accesskit::Node {
let mut node = gpui::accesskit::Node::new(gpui::Role::Button);
let press: crate::forms::Run = std::rc::Rc::new(|_, _| {});
let drawn = super::super::draw::button(item, (0, 0), current, press, cx);
gpui::Element::write_a11y_info(&drawn, &mut node);
node
}
#[gpui::test]
fn a_button_offers_its_press_and_says_it_is_marked(cx: &mut TestAppContext) {
cx.update(|cx| {
Theme::init(cx);
let cut = MenuItem::new("Cut").icon(IconName::Scissors);
let off = MenuItem::new("Copy").icon(IconName::Copy).disabled(true);
let click = gpui::accesskit::Action::Click;
let marked = node(&cut, true, cx);
assert!(marked.supports_action(click), "a reader can press it");
assert_eq!(marked.is_selected(), Some(true));
assert_eq!(node(&cut, false, cx).is_selected(), Some(false));
assert!(!node(&off, false, cx).supports_action(click), "off");
});
}