diff --git a/src/ui/code_editor.rs b/src/ui/code_editor.rs index 04eed513..ef461f2c 100644 --- a/src/ui/code_editor.rs +++ b/src/ui/code_editor.rs @@ -3443,6 +3443,8 @@ impl Tty7App { let muted = cx.theme().muted_foreground; let close = div() .id(("editor-file-tab-close", pos)) + .role(gpui::Role::Button) + .aria_label(format!("{} {name}", t(L10nKey::Close))) .flex_none() .size(px(slot)) .flex() @@ -3474,6 +3476,12 @@ impl Tty7App { }); div() .id(("editor-file-tab", pos)) + .role(gpui::Role::Tab) + .aria_label(match dirty { + true => format!("{name}, {}", t(L10nKey::SettingsUnsaved)), + false => name.to_string(), + }) + .aria_selected(active) .group(group.clone()) .occlude() .flex_none() diff --git a/src/ui/file_tree.rs b/src/ui/file_tree.rs index c32f0ad6..ad1ae8d4 100644 --- a/src/ui/file_tree.rs +++ b/src/ui/file_tree.rs @@ -2084,6 +2084,20 @@ impl Tty7App { } }), ) + // A row opens on the press, not the click, so it has no click + // handler for assistive technology's "press" to reach. Give it + // the same act directly, or VoiceOver could select a file and + // never open it. + .on_a11y_action(gpui::AccessibleAction::Click, { + let path = path.clone(); + let app = cx.entity().downgrade(); + move |_, window, cx| { + let _ = app.update(cx, |this, cx| { + this.file_tree.focus_handle.focus(window, cx); + this.file_tree_activate(&path, is_dir, window, cx); + }); + } + }) // The menu acts on the row it was opened on, so that row is the // one lit while it is up (#942) — selected without being opened, // the way Explorer and every editor's tree do it. diff --git a/src/ui/right_panel.rs b/src/ui/right_panel.rs index 39930d91..3f8e0067 100644 --- a/src/ui/right_panel.rs +++ b/src/ui/right_panel.rs @@ -1072,6 +1072,16 @@ impl Tty7App { _ => t(L10nKey::CmdCopy), }; + // "cwd, /tmp/project": the row as one line. The path drawn is split + // and may be elided; this is the whole of it. + let a11y_name = format!( + "{}, {}", + row.label, + match &row.value { + InfoValue::Text(v) | InfoValue::Path(v) => v.clone(), + InfoValue::Diff { added, removed, .. } => format!("+{added} −{removed}"), + } + ); let value = match row.value { // A path identifies a pane by its last segment, and plain // truncation eats exactly that: a deep checkout read @@ -1212,6 +1222,8 @@ impl Tty7App { h_flex() .id(id.clone()) + .role(gpui::Role::Label) + .aria_label(a11y_name) .group(id) .relative() .items_center() diff --git a/src/ui/settings/hosts.rs b/src/ui/settings/hosts.rs index ca23dd03..d52c62f0 100644 --- a/src/ui/settings/hosts.rs +++ b/src/ui/settings/hosts.rs @@ -447,6 +447,12 @@ impl Tty7App { let head = div() .id(SharedString::from(format!("ssh-host-{}", id.as_u128()))) + .role(gpui::Role::Button) + .aria_label(match address.is_empty() { + true => title.clone(), + false => format!("{title}, {address}"), + }) + .aria_expanded(open) .h(px(40.)) .px(px(10.)) .flex() diff --git a/src/ui/settings/shortcuts.rs b/src/ui/settings/shortcuts.rs index 5fe163d2..ce6e5a1e 100644 --- a/src/ui/settings/shortcuts.rs +++ b/src/ui/settings/shortcuts.rs @@ -159,6 +159,17 @@ impl Tty7App { (_, Some((_, spec, _))) => vec![spec.clone()], _ => keys, }; + // "New Tab, ⌘T": the action and what it is bound to, the way + // the row reads — a screen reader says ⌘ as "command". + let a11y_keys = match shown.is_empty() { + true => "—".to_string(), + false => shown + .iter() + .map(|spec| crate::ui::keymap::key_tokens(spec).join("")) + .collect::>() + .join(" "), + }; + let a11y_name = format!("{label}, {a11y_keys}"); let modified = overridden.contains(&action) && !is_recording; let ring = if is_recording { vec![kit::ring(tk.fg, 1., true)] @@ -175,6 +186,8 @@ impl Tty7App { let record_action = action.clone(); let keys_button = h_flex() .id(SharedString::from(format!("kb-{action}"))) + .role(gpui::Role::Button) + .aria_label(a11y_name) .h(px(26.)) .min_w(px(72.)) .pl(px(8.))