From 6be1a68250777ad4d43b00c5ac380bd672a7b9a5 Mon Sep 17 00:00:00 2001 From: Jason Lee Date: Thu, 28 May 2026 19:08:17 +0800 Subject: [PATCH 1/5] feat(a11y): implement AccessKit accessibility support for all UI components Adds ARIA roles and accessibility attributes to all major components, leveraging the AccessKit integration shipped in the latest GPUI update. Each component now exposes semantic role, state, and label information to assistive technologies (VoiceOver, NVDA, AT-SPI). Component coverage: - Button: Role::Button / Role::Link, aria_label, aria_selected - Toggle / ToggleGroup: Role::Button + aria_toggled, Role::Toolbar - Checkbox: Role::CheckBox + aria_toggled + aria_label - Radio / RadioGroup: Role::RadioButton + aria_selected + position/size_of_set, Role::RadioGroup - Input: Role::TextInput / Role::MultilineTextInput - NumberInput: Role::SpinButton + aria_numeric_value - Slider: Role::Slider + aria_numeric_value + min/max + Increment/Decrement actions - Tab / TabBar: Role::Tab + aria_selected, Role::TabList - PopupMenu / AppMenuBar: Role::Menu, Role::MenuBar - MenuItem: Role::MenuItem + aria_selected - Dialog: Role::Dialog (configurable); AlertDialog: Role::AlertDialog - Alert: Role::Alert - Progress: Role::ProgressIndicator + aria_numeric_value + min/max - List / ListItem: Role::List + Role::ListItem + position/size_of_set + aria_selected - Table / TableHeader / TableBody / TableRow / TableHead / TableCell: Role::Table, Role::RowGroup, Role::Row + aria_row_index, Role::ColumnHeader / Role::Cell + aria_column_index - Accordion: Role::Group; AccordionItem header: Role::Button + aria_expanded - GroupBox: Role::Group - Combobox: Role::ComboBox + aria_expanded - DropdownButton: Role::Group - Stepper / StepperItem: Role::Group, Role::ListItem + aria_position_in_set - BreadcrumbItem: Role::Link / Role::ListItem - FormField: Role::Group - Root: Role::Application (window-level application landmark) Also adds: - SliderState::min_value() / max_value() / step_value() accessors - Dialog::a11y_role field for role override - Radio::position_in_set / size_of_set fields populated by RadioGroup - script/run-story-macos: launch Story as a signed macOS .app bundle for VoiceOver testing Co-Authored-By: Claude Sonnet 4.6 --- crates/ui/src/accordion.rs | 5 ++- crates/ui/src/alert.rs | 5 ++- crates/ui/src/breadcrumb.rs | 7 ++- crates/ui/src/button/button.rs | 13 +++++- crates/ui/src/button/dropdown_button.rs | 7 +-- crates/ui/src/button/toggle.rs | 11 ++++- crates/ui/src/checkbox.rs | 10 ++++- crates/ui/src/combobox.rs | 6 ++- crates/ui/src/dialog/alert_dialog.rs | 1 + crates/ui/src/dialog/dialog.rs | 13 +++++- crates/ui/src/form/field.rs | 6 ++- crates/ui/src/group_box.rs | 4 +- crates/ui/src/input/input.rs | 9 +++- crates/ui/src/input/number_input.rs | 9 +++- crates/ui/src/list/list.rs | 9 +++- crates/ui/src/menu/app_menu_bar.rs | 3 +- crates/ui/src/menu/menu_item.rs | 6 ++- crates/ui/src/menu/popup_menu.rs | 5 ++- crates/ui/src/progress/progress.rs | 8 +++- crates/ui/src/radio.rs | 25 +++++++++-- crates/ui/src/root.rs | 5 ++- crates/ui/src/slider.rs | 60 ++++++++++++++++++++++--- crates/ui/src/stepper/item.rs | 6 ++- crates/ui/src/stepper/stepper.rs | 6 ++- crates/ui/src/tab/tab.rs | 4 +- crates/ui/src/tab/tab_bar.rs | 7 +-- crates/ui/src/table/table.rs | 14 +++++- script/run-story-macos | 45 +++++++++++++++++++ 28 files changed, 258 insertions(+), 51 deletions(-) create mode 100755 script/run-story-macos diff --git a/crates/ui/src/accordion.rs b/crates/ui/src/accordion.rs index 6c8d907b2b..1795f83428 100644 --- a/crates/ui/src/accordion.rs +++ b/crates/ui/src/accordion.rs @@ -2,7 +2,7 @@ use std::{cell::RefCell, collections::HashSet, rc::Rc, sync::Arc}; use gpui::{ AnyElement, App, ElementId, InteractiveElement as _, IntoElement, ParentElement, RenderOnce, - SharedString, StatefulInteractiveElement as _, Styled, Window, div, + Role, SharedString, StatefulInteractiveElement as _, Styled, Window, div, prelude::FluentBuilder as _, rems, }; @@ -88,6 +88,7 @@ impl RenderOnce for Accordion { v_flex() .id(self.id) + .role(Role::Group) .size_full() .when(self.bordered, |this| this.gap_y_2()) .children( @@ -240,6 +241,8 @@ impl RenderOnce for AccordionItem { .child( h_flex() .id(self.index) + .role(Role::Button) + .aria_expanded(self.open) .justify_between() .gap_3() .map(|this| match self.size { diff --git a/crates/ui/src/alert.rs b/crates/ui/src/alert.rs index dab867bfe6..7ceb3a9f2d 100644 --- a/crates/ui/src/alert.rs +++ b/crates/ui/src/alert.rs @@ -2,8 +2,8 @@ use std::rc::Rc; use gpui::{ App, ClickEvent, ElementId, Empty, Hsla, InteractiveElement, IntoElement, ParentElement as _, - RenderOnce, SharedString, StatefulInteractiveElement, StyleRefinement, Styled, Window, div, - prelude::FluentBuilder as _, px, rems, transparent_white, + RenderOnce, Role, SharedString, StatefulInteractiveElement, StyleRefinement, Styled, Window, + div, prelude::FluentBuilder as _, px, rems, transparent_white, }; use crate::{ @@ -189,6 +189,7 @@ impl RenderOnce for Alert { h_flex() .id(self.id) + .role(Role::Alert) .w_full() .text_color(fg) .bg(bg) diff --git a/crates/ui/src/breadcrumb.rs b/crates/ui/src/breadcrumb.rs index 328c110fbd..f579a26289 100644 --- a/crates/ui/src/breadcrumb.rs +++ b/crates/ui/src/breadcrumb.rs @@ -2,7 +2,7 @@ use std::rc::Rc; use gpui::{ div, prelude::FluentBuilder as _, App, ClickEvent, ElementId, InteractiveElement as _, - IntoElement, ParentElement, RenderOnce, SharedString, StatefulInteractiveElement, + IntoElement, ParentElement, RenderOnce, Role, SharedString, StatefulInteractiveElement, StyleRefinement, Styled, Window, }; @@ -92,6 +92,11 @@ impl RenderOnce for BreadcrumbItem { fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement { div() .id(self.id) + .role(if self.on_click.is_some() && !self.disabled { + Role::Link + } else { + Role::ListItem + }) .child(self.label) .text_color(cx.theme().muted_foreground) .when(self.is_last, |this| this.text_color(cx.theme().foreground)) diff --git a/crates/ui/src/button/button.rs b/crates/ui/src/button/button.rs index 0da2553915..26cda784a8 100644 --- a/crates/ui/src/button/button.rs +++ b/crates/ui/src/button/button.rs @@ -10,8 +10,8 @@ use crate::{ use gpui::{ AnyElement, App, Background, ClickEvent, Corners, Div, Edges, ElementId, Hsla, InteractiveElement, Interactivity, IntoElement, MouseButton, ParentElement, Pixels, RenderOnce, - SharedString, Stateful, StatefulInteractiveElement as _, StyleRefinement, Styled, Window, div, - prelude::FluentBuilder as _, px, relative, transparent_white, + Role, SharedString, Stateful, StatefulInteractiveElement as _, StyleRefinement, Styled, Window, + div, prelude::FluentBuilder as _, px, relative, transparent_white, }; #[derive(Default, Clone, Copy)] @@ -461,6 +461,15 @@ impl RenderOnce for Button { }; self.base + .role(if self.variant.is_link() { + Role::Link + } else { + Role::Button + }) + .when_some(self.label.as_ref(), |this, label| { + this.aria_label(label.clone()) + }) + .aria_selected(self.selected) .when(!self.disabled, |this| { this.track_focus( &focus_handle diff --git a/crates/ui/src/button/dropdown_button.rs b/crates/ui/src/button/dropdown_button.rs index b64b633f64..6f02799160 100644 --- a/crates/ui/src/button/dropdown_button.rs +++ b/crates/ui/src/button/dropdown_button.rs @@ -1,8 +1,8 @@ use gpui::Corners; use gpui::{ - Anchor, App, Context, Edges, ElementId, InteractiveElement as _, IntoElement, - ParentElement, RenderOnce, SharedString, StyleRefinement, Styled, Window, div, - prelude::FluentBuilder, + Anchor, App, Context, Edges, ElementId, InteractiveElement as _, IntoElement, ParentElement, + RenderOnce, Role, SharedString, StatefulInteractiveElement as _, StyleRefinement, Styled, + Window, div, prelude::FluentBuilder, }; use crate::{ @@ -159,6 +159,7 @@ impl RenderOnce for DropdownButton { div() .id(self.id) + .role(Role::Group) .h_flex() .refine_style(&self.style) .when_some(self.button, |this, button| { diff --git a/crates/ui/src/button/toggle.rs b/crates/ui/src/button/toggle.rs index 9afe36ff66..6ab303fbc2 100644 --- a/crates/ui/src/button/toggle.rs +++ b/crates/ui/src/button/toggle.rs @@ -2,8 +2,8 @@ use std::{cell::Cell, rc::Rc}; use gpui::{ AnyElement, App, Corners, Edges, ElementId, InteractiveElement, IntoElement, ParentElement, - RenderOnce, SharedString, StatefulInteractiveElement, StyleRefinement, Styled, Window, div, - prelude::FluentBuilder as _, + RenderOnce, Role, SharedString, StatefulInteractiveElement, StyleRefinement, Styled, Toggled, + Window, div, prelude::FluentBuilder as _, }; use smallvec::{SmallVec, smallvec}; @@ -156,6 +156,12 @@ impl RenderOnce for Toggle { div() .id(self.id) + .role(Role::Button) + .aria_toggled(if checked { Toggled::True } else { Toggled::False }) + .when_some( + self.tooltip.text.as_ref().map(|(text, _)| text.clone()), + |this, label| this.aria_label(label), + ) .flex() .flex_row() .items_center() @@ -309,6 +315,7 @@ impl RenderOnce for ToggleGroup { h_flex() .id(self.id) + .role(Role::Toolbar) .when(!self.segmented, |this| this.gap_2()) .refine_style(&self.style) .children(self.items.into_iter().enumerate().map({ diff --git a/crates/ui/src/checkbox.rs b/crates/ui/src/checkbox.rs index 0dae528d69..7ad21250e6 100644 --- a/crates/ui/src/checkbox.rs +++ b/crates/ui/src/checkbox.rs @@ -6,8 +6,8 @@ use crate::{ }; use gpui::{ Animation, AnimationExt, AnyElement, App, Div, ElementId, InteractiveElement, IntoElement, - ParentElement, RenderOnce, SharedString, StatefulInteractiveElement, StyleRefinement, Styled, - Window, div, prelude::FluentBuilder as _, px, relative, rems, svg, + ParentElement, RenderOnce, Role, SharedString, StatefulInteractiveElement, StyleRefinement, + Styled, Toggled, Window, div, prelude::FluentBuilder as _, px, relative, rems, svg, }; /// A Checkbox element. @@ -222,6 +222,12 @@ impl RenderOnce for Checkbox { div().child( self.base .id(self.id.clone()) + .role(Role::CheckBox) + .aria_toggled(if checked { Toggled::True } else { Toggled::False }) + .when_some( + self.label.as_ref().map(|l| l.get_text(cx)), + |this, label| this.aria_label(label), + ) .when(!self.disabled, |this| { this.track_focus( &focus_handle diff --git a/crates/ui/src/combobox.rs b/crates/ui/src/combobox.rs index cea5324621..6474c58f33 100644 --- a/crates/ui/src/combobox.rs +++ b/crates/ui/src/combobox.rs @@ -1,7 +1,7 @@ use gpui::{ AnyElement, App, Bounds, ClickEvent, Context, DismissEvent, Edges, ElementId, Entity, EventEmitter, FocusHandle, Focusable, Hsla, InteractiveElement, IntoElement, KeyBinding, - Length, MouseDownEvent, ParentElement, Pixels, Render, RenderOnce, SharedString, + Length, MouseDownEvent, ParentElement, Pixels, Render, RenderOnce, Role, SharedString, StatefulInteractiveElement, StyleRefinement, Styled, Window, anchored, deferred, div, prelude::FluentBuilder, px, rems, }; @@ -881,8 +881,12 @@ where } }); + let is_open = self.state.read(cx).state.open; + div() .id(self.id.clone()) + .role(Role::ComboBox) + .aria_expanded(is_open) .key_context(CONTEXT) .when(!disabled, |this| { this.track_focus(&focus_handle.tab_stop(true)) diff --git a/crates/ui/src/dialog/alert_dialog.rs b/crates/ui/src/dialog/alert_dialog.rs index f855d2f16a..3640eac009 100644 --- a/crates/ui/src/dialog/alert_dialog.rs +++ b/crates/ui/src/dialog/alert_dialog.rs @@ -275,6 +275,7 @@ impl AlertDialog { self.base .button_props(button_props.clone()) + .alert_dialog_role() .when(has_header, |this| { this.header( DialogHeader::new().child( diff --git a/crates/ui/src/dialog/dialog.rs b/crates/ui/src/dialog/dialog.rs index 04c650bfa1..3d253d9f8f 100644 --- a/crates/ui/src/dialog/dialog.rs +++ b/crates/ui/src/dialog/dialog.rs @@ -3,8 +3,9 @@ use std::{rc::Rc, sync::LazyLock, time::Duration}; use gpui::{ Animation, AnimationExt as _, AnyElement, App, Bounds, BoxShadow, ClickEvent, Edges, FocusHandle, Hsla, InteractiveElement, IntoElement, KeyBinding, MouseButton, ParentElement, - Pixels, Point, RenderOnce, SharedString, StyleRefinement, Styled, Window, WindowControlArea, - actions, anchored, div, hsla, point, prelude::FluentBuilder, px, + Pixels, Point, RenderOnce, Role, SharedString, StatefulInteractiveElement as _, StyleRefinement, + Styled, Window, WindowControlArea, actions, anchored, div, hsla, point, prelude::FluentBuilder, + px, }; use rust_i18n::t; @@ -207,6 +208,7 @@ pub struct Dialog { pub(crate) footer: Option, pub(crate) content_builder: Option, pub(crate) props: DialogProps, + pub(crate) a11y_role: Role, button_props: DialogButtonProps, @@ -238,6 +240,7 @@ impl Dialog { children: Vec::new(), layer_ix: 0, button_props: DialogButtonProps::default(), + a11y_role: Role::Dialog, } } @@ -286,6 +289,11 @@ impl Dialog { self } + pub(crate) fn alert_dialog_role(mut self) -> Self { + self.a11y_role = Role::AlertDialog; + self + } + /// Sets the callback for when the dialog is closed. /// /// Called after [`Self::on_ok`] or [`Self::on_cancel`] callback. @@ -522,6 +530,7 @@ impl RenderOnce for Dialog { .child( v_flex() .id(layer_ix) + .role(self.a11y_role) .track_focus(&self.focus_handle) .focus_trap(format!("dialog-{}", layer_ix), &self.focus_handle) .bg(cx.theme().tokens.background) diff --git a/crates/ui/src/form/field.rs b/crates/ui/src/form/field.rs index 838d212f64..309127bef7 100644 --- a/crates/ui/src/form/field.rs +++ b/crates/ui/src/form/field.rs @@ -2,8 +2,9 @@ use std::rc::Rc; use gpui::{ AlignItems, AnyElement, AnyView, App, Axis, Div, Element, ElementId, InteractiveElement as _, - IntoElement, ParentElement, Pixels, Rems, RenderOnce, SharedString, StyleRefinement, Styled, - Window, div, prelude::FluentBuilder as _, px, + IntoElement, ParentElement, Pixels, Rems, RenderOnce, Role, SharedString, + StatefulInteractiveElement as _, StyleRefinement, Styled, Window, div, + prelude::FluentBuilder as _, px, }; use crate::{ActiveTheme as _, AxisExt, Size, StyledExt, h_flex, v_flex}; @@ -280,6 +281,7 @@ impl RenderOnce for Field { // This warp for aligning the Label + Input wrap_div(layout) .id(self.id) + .role(Role::Group) .gap(inner_gap) .when_some(self.align_items, |this, align| { this.map(|this| match align { diff --git a/crates/ui/src/group_box.rs b/crates/ui/src/group_box.rs index fd59de1595..02c9ed8243 100644 --- a/crates/ui/src/group_box.rs +++ b/crates/ui/src/group_box.rs @@ -1,6 +1,7 @@ use gpui::{ AnyElement, App, Background, ElementId, InteractiveElement as _, IntoElement, ParentElement, - RenderOnce, StyleRefinement, Styled, Window, div, prelude::FluentBuilder, relative, + RenderOnce, Role, StatefulInteractiveElement as _, StyleRefinement, Styled, Window, div, + prelude::FluentBuilder, relative, }; use smallvec::SmallVec; @@ -139,6 +140,7 @@ impl RenderOnce for GroupBox { div().child( v_flex() .id(self.id.unwrap_or("group-box".into())) + .role(Role::Group) .w_full() .when(has_paddings, |this| this.gap_3()) .when(!has_paddings, |this| this.gap_4()) diff --git a/crates/ui/src/input/input.rs b/crates/ui/src/input/input.rs index c86c896b1d..97dc5a9023 100644 --- a/crates/ui/src/input/input.rs +++ b/crates/ui/src/input/input.rs @@ -3,8 +3,8 @@ use std::rc::Rc; use gpui::prelude::FluentBuilder as _; use gpui::{ AnyElement, App, DefiniteLength, Edges, EdgesRefinement, Entity, Hsla, InteractiveElement as _, - IntoElement, MouseButton, ParentElement as _, Rems, RenderOnce, StyleRefinement, Styled, - TextAlign, Window, div, px, relative, + IntoElement, MouseButton, ParentElement as _, Rems, RenderOnce, Role, + StatefulInteractiveElement as _, StyleRefinement, Styled, TextAlign, Window, div, px, relative, }; use crate::button::{Button, ButtonVariants as _}; @@ -289,6 +289,11 @@ impl RenderOnce for Input { div() .id(("input", self.state.entity_id())) + .role(if state.mode.is_multi_line() { + Role::MultilineTextInput + } else { + Role::TextInput + }) .flex() .key_context(crate::input::CONTEXT) .track_focus(&state.focus_handle.clone()) diff --git a/crates/ui/src/input/number_input.rs b/crates/ui/src/input/number_input.rs index 589b3edc36..9890d67c67 100644 --- a/crates/ui/src/input/number_input.rs +++ b/crates/ui/src/input/number_input.rs @@ -5,8 +5,9 @@ use gpui::Corners; use gpui::Window; use gpui::{AnyElement, App, Context, Edges, Entity, EventEmitter, FocusHandle, Focusable}; use gpui::{ - InteractiveElement, IntoElement, KeyBinding, ParentElement, RenderOnce, SharedString, - StyleRefinement, Styled, TextAlign, actions, prelude::FluentBuilder as _, + InteractiveElement, IntoElement, KeyBinding, ParentElement, RenderOnce, Role, SharedString, + StatefulInteractiveElement as _, StyleRefinement, Styled, TextAlign, actions, + prelude::FluentBuilder as _, }; use crate::{Disableable, IconName, Sizable, Size, StyledExt as _, button::Button, h_flex}; @@ -292,8 +293,12 @@ impl RenderOnce for NumberInput { }); } + let numeric_value = self.state.read(cx).value().parse::().ok(); + h_flex() .id(("number-input", self.state.entity_id())) + .role(Role::SpinButton) + .when_some(numeric_value, |this, v| this.aria_numeric_value(v)) .key_context(CONTEXT) .on_action(window.listener_for(&self.state, InputState::on_action_increment)) .on_action(window.listener_for(&self.state, InputState::on_action_decrement)) diff --git a/crates/ui/src/list/list.rs b/crates/ui/src/list/list.rs index 4b114aa250..8016c3d649 100644 --- a/crates/ui/src/list/list.rs +++ b/crates/ui/src/list/list.rs @@ -14,7 +14,7 @@ use crate::{Icon, IndexPath, Selectable, Sizable, StyledExt}; use crate::{VirtualListScrollHandle, list::ListDelegate, v_virtual_list}; use gpui::{ App, AvailableSpace, ClickEvent, Context, DefiniteLength, EdgesRefinement, EventEmitter, - ListSizingBehavior, RenderOnce, ScrollStrategy, SharedString, StatefulInteractiveElement, + ListSizingBehavior, RenderOnce, Role, ScrollStrategy, SharedString, StatefulInteractiveElement, StyleRefinement, Subscription, px, size, }; use gpui::{ @@ -463,8 +463,14 @@ where .unwrap_or(false); let id = SharedString::from(format!("list-item-{}", ix)); + let total_items = self.rows_cache.items_count(); + div() .id(id) + .role(Role::ListItem) + .aria_position_in_set(ix.row + 1) + .aria_size_of_set(total_items) + .aria_selected(selected) .w_full() .relative() .overflow_hidden() @@ -754,6 +760,7 @@ where div() .id("list") + .role(Role::List) .size_full() .refine_style(&self.style) .child(self.state.clone()) diff --git a/crates/ui/src/menu/app_menu_bar.rs b/crates/ui/src/menu/app_menu_bar.rs index ee3d600fb5..ac2f12e06b 100644 --- a/crates/ui/src/menu/app_menu_bar.rs +++ b/crates/ui/src/menu/app_menu_bar.rs @@ -9,7 +9,7 @@ use crate::{ use gpui::{ App, AppContext as _, ClickEvent, Context, DismissEvent, Entity, FocusHandle, Focusable, InteractiveElement as _, IntoElement, KeyBinding, MouseButton, OwnedMenu, ParentElement, - Render, SharedString, StatefulInteractiveElement, Styled, Subscription, Window, anchored, + Render, Role, SharedString, StatefulInteractiveElement, Styled, Subscription, Window, anchored, deferred, div, prelude::FluentBuilder, px, }; @@ -120,6 +120,7 @@ impl Render for AppMenuBar { fn render(&mut self, _: &mut Window, cx: &mut Context) -> impl IntoElement { h_flex() .id("app-menu-bar") + .role(Role::MenuBar) .key_context(CONTEXT) .on_action(cx.listener(Self::on_move_left)) .on_action(cx.listener(Self::on_move_right)) diff --git a/crates/ui/src/menu/menu_item.rs b/crates/ui/src/menu/menu_item.rs index d590a3aae1..f32ff33f8f 100644 --- a/crates/ui/src/menu/menu_item.rs +++ b/crates/ui/src/menu/menu_item.rs @@ -1,8 +1,8 @@ use crate::{ActiveTheme, Disableable, StyledExt, h_flex}; use gpui::{ AnyElement, App, ClickEvent, ElementId, InteractiveElement, IntoElement, MouseButton, - ParentElement, RenderOnce, SharedString, StatefulInteractiveElement as _, StyleRefinement, - Styled, Window, prelude::FluentBuilder as _, + ParentElement, RenderOnce, Role, SharedString, StatefulInteractiveElement as _, + StyleRefinement, Styled, Window, prelude::FluentBuilder as _, }; use smallvec::SmallVec; @@ -86,6 +86,8 @@ impl RenderOnce for MenuItemElement { fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement { h_flex() .id(self.id) + .role(Role::MenuItem) + .aria_selected(self.selected) .group(&self.group_name) .gap_x_1() .py_1() diff --git a/crates/ui/src/menu/popup_menu.rs b/crates/ui/src/menu/popup_menu.rs index 78d32b8903..505b11714a 100644 --- a/crates/ui/src/menu/popup_menu.rs +++ b/crates/ui/src/menu/popup_menu.rs @@ -7,8 +7,8 @@ use crate::{Side, Size, StyledExt, kbd::Kbd}; use gpui::{ Action, Anchor, AnyElement, App, AppContext, Bounds, Context, DismissEvent, Edges, Entity, EventEmitter, FocusHandle, Focusable, InteractiveElement, IntoElement, KeyBinding, - ParentElement, Pixels, Render, ScrollHandle, SharedString, StatefulInteractiveElement, Styled, - WeakEntity, Window, anchored, div, prelude::FluentBuilder, px, rems, + ParentElement, Pixels, Render, Role, ScrollHandle, SharedString, StatefulInteractiveElement, + Styled, WeakEntity, Window, anchored, div, prelude::FluentBuilder, px, rems, }; use gpui::{ClickEvent, Half, MouseDownEvent, OwnedMenuItem, Point, Subscription}; @@ -1306,6 +1306,7 @@ impl Render for PopupMenu { v_flex() .id("popup-menu") + .role(Role::Menu) .key_context(CONTEXT) .track_focus(&self.focus_handle) .on_action(cx.listener(Self::select_up)) diff --git a/crates/ui/src/progress/progress.rs b/crates/ui/src/progress/progress.rs index 46140d0dd4..4ffae3068d 100644 --- a/crates/ui/src/progress/progress.rs +++ b/crates/ui/src/progress/progress.rs @@ -1,8 +1,8 @@ use crate::{ActiveTheme, Sizable, Size, StyledExt}; use gpui::{ Animation, AnimationExt as _, App, Background, ElementId, Hsla, InteractiveElement as _, - IntoElement, ParentElement, RenderOnce, StyleRefinement, Styled, Window, div, ease_in_out, - prelude::FluentBuilder, px, relative, + IntoElement, ParentElement, RenderOnce, Role, StatefulInteractiveElement as _, StyleRefinement, + Styled, Window, div, ease_in_out, prelude::FluentBuilder, px, relative, }; use instant::Duration; @@ -96,6 +96,10 @@ impl RenderOnce for Progress { div() .id(self.id) + .role(Role::ProgressIndicator) + .aria_numeric_value(value as f64) + .aria_min_numeric_value(0.0) + .aria_max_numeric_value(100.0) .w_full() .relative() .h(height) diff --git a/crates/ui/src/radio.rs b/crates/ui/src/radio.rs index 72408e4348..8e9c2cc651 100644 --- a/crates/ui/src/radio.rs +++ b/crates/ui/src/radio.rs @@ -6,8 +6,8 @@ use crate::{ }; use gpui::{ AnyElement, App, Axis, Div, ElementId, InteractiveElement, IntoElement, ParentElement, - RenderOnce, SharedString, StatefulInteractiveElement, StyleRefinement, Styled, Window, div, - prelude::FluentBuilder, px, relative, rems, + RenderOnce, Role, SharedString, StatefulInteractiveElement, StyleRefinement, Styled, Window, + div, prelude::FluentBuilder, px, relative, rems, }; /// A Radio element. @@ -27,6 +27,8 @@ pub struct Radio { size: Size, on_click: Option>, tooltip: ComponentTooltip, + position_in_set: Option, + size_of_set: Option, } impl Radio { @@ -45,6 +47,8 @@ impl Radio { size: Size::default(), on_click: None, tooltip: ComponentTooltip::default(), + position_in_set: None, + size_of_set: None, } } @@ -157,6 +161,18 @@ impl RenderOnce for Radio { div().child( self.base .id(self.id.clone()) + .role(Role::RadioButton) + .aria_selected(self.checked) + .when_some( + self.label.as_ref().map(|l| l.get_text(cx)), + |this, label| this.aria_label(label), + ) + .when_some(self.position_in_set, |this, pos| { + this.aria_position_in_set(pos) + }) + .when_some(self.size_of_set, |this, size| { + this.aria_size_of_set(size) + }) .when(!self.disabled, |this| { this.track_focus( &focus_handle @@ -351,7 +367,8 @@ impl RenderOnce for RadioGroup { h_flex().w_full().flex_wrap() }; - let mut container = div().id(self.id); + let total = self.radios.len(); + let mut container = div().id(self.id).role(Role::RadioGroup); *container.style() = self.style; container.child( @@ -360,6 +377,8 @@ impl RenderOnce for RadioGroup { let checked = selected_ix == Some(ix); radio.id = ix.into(); + radio.position_in_set = Some(ix + 1); + radio.size_of_set = Some(total); radio.disabled(disabled).checked(checked).when_some( on_click.clone(), |this, on_click| { diff --git a/crates/ui/src/root.rs b/crates/ui/src/root.rs index bc072b9798..13f1030638 100644 --- a/crates/ui/src/root.rs +++ b/crates/ui/src/root.rs @@ -13,8 +13,8 @@ use crate::{ use gpui::{ Anchor, AnyView, App, AppContext, Bounds, ClipboardItem, Context, DefiniteLength, ElementId, Entity, EntityId, FocusHandle, Hitbox, InteractiveElement, IntoElement, KeyBinding, - ParentElement as _, Pixels, Render, StyleRefinement, Styled, WeakEntity, WeakFocusHandle, - Window, actions, div, prelude::FluentBuilder as _, + ParentElement as _, Pixels, Render, Role, StatefulInteractiveElement as _, StyleRefinement, + Styled, WeakEntity, WeakFocusHandle, Window, actions, div, prelude::FluentBuilder as _, }; use std::{any::TypeId, collections::HashMap, rc::Rc}; @@ -542,6 +542,7 @@ impl Render for Root { let inner = div() .id("root") + .role(Role::Application) .key_context(CONTEXT) .on_action(cx.listener(Self::on_action_tab)) .on_action(cx.listener(Self::on_action_tab_prev)) diff --git a/crates/ui/src/slider.rs b/crates/ui/src/slider.rs index 8d9c65d632..364146f098 100644 --- a/crates/ui/src/slider.rs +++ b/crates/ui/src/slider.rs @@ -2,11 +2,11 @@ use std::ops::Range; use crate::{ActiveTheme, AxisExt, ElementExt, StyledExt, h_flex}; use gpui::{ - Along, App, AppContext as _, Axis, Background, Bounds, Context, Corners, DefiniteLength, - DragMoveEvent, Empty, Entity, EntityId, EventEmitter, InteractiveElement, IntoElement, IsZero, - MouseButton, MouseDownEvent, ParentElement as _, Pixels, Point, Render, RenderOnce, - StatefulInteractiveElement as _, StyleRefinement, Styled, Window, div, - prelude::FluentBuilder as _, px, relative, + AccessibleAction, Along, App, AppContext as _, Axis, Background, Bounds, Context, Corners, + DefiniteLength, DragMoveEvent, Empty, Entity, EntityId, EventEmitter, InteractiveElement, + IntoElement, IsZero, MouseButton, MouseDownEvent, Orientation, ParentElement as _, Pixels, + Point, Render, RenderOnce, Role, StatefulInteractiveElement as _, StyleRefinement, Styled, + Window, div, prelude::FluentBuilder as _, px, relative, }; #[derive(Clone)] @@ -290,6 +290,21 @@ impl SliderState { self.value } + /// Get the minimum value. + pub fn min_value(&self) -> f32 { + self.min + } + + /// Get the maximum value. + pub fn max_value(&self) -> f32 { + self.max + } + + /// Get the step value. + pub fn step_value(&self) -> f32 { + self.step + } + /// Converts a value between 0.0 and 1.0 to a value between the minimum and maximum value, /// depending on the chosen scale. fn percentage_to_value(&self, percentage: f32) -> f32 { @@ -562,8 +577,43 @@ impl RenderOnce for Slider { radius.bottom_right = px(0.); } + let slider_min = state.min_value() as f64; + let slider_max = state.max_value() as f64; + let _slider_step = state.step_value() as f64; + let slider_value = state.value().end() as f64; + let slider_state_ref = self.state.clone(); + div() .id(("slider", self.state.entity_id())) + .role(Role::Slider) + .aria_numeric_value(slider_value) + .aria_min_numeric_value(slider_min) + .aria_max_numeric_value(slider_max) + .aria_orientation(if axis.is_vertical() { + Orientation::Vertical + } else { + Orientation::Horizontal + }) + .on_a11y_action(AccessibleAction::Increment, { + let state = slider_state_ref.clone(); + move |_, window, cx| { + state.update(cx, |state, cx| { + let new_val = + (state.value().end() + state.step_value()).min(state.max_value()); + state.set_value(new_val, window, cx); + }); + } + }) + .on_a11y_action(AccessibleAction::Decrement, { + let state = slider_state_ref.clone(); + move |_, window, cx| { + state.update(cx, |state, cx| { + let new_val = + (state.value().end() - state.step_value()).max(state.min_value()); + state.set_value(new_val, window, cx); + }); + } + }) .flex() .flex_1() .items_center() diff --git a/crates/ui/src/stepper/item.rs b/crates/ui/src/stepper/item.rs index 6e1278a28f..f26fc90cae 100644 --- a/crates/ui/src/stepper/item.rs +++ b/crates/ui/src/stepper/item.rs @@ -1,7 +1,7 @@ use gpui::{ AnyElement, App, Axis, ClickEvent, Half, InteractiveElement as _, IntoElement, ParentElement, - Pixels, RenderOnce, StyleRefinement, Styled, Window, div, prelude::FluentBuilder as _, px, - relative, + Pixels, RenderOnce, Role, StatefulInteractiveElement as _, StyleRefinement, Styled, Window, + div, prelude::FluentBuilder as _, px, relative, }; use crate::{ @@ -123,6 +123,8 @@ impl RenderOnce for StepperItem { div() .id(("stepper-item", self.step)) + .role(Role::ListItem) + .aria_position_in_set(self.step + 1) .relative() .when(self.layout.is_horizontal(), |this| this.h_flex()) .when(self.layout.is_vertical(), |this| this.v_flex()) diff --git a/crates/ui/src/stepper/stepper.rs b/crates/ui/src/stepper/stepper.rs index 8a51fca7e0..021324680e 100644 --- a/crates/ui/src/stepper/stepper.rs +++ b/crates/ui/src/stepper/stepper.rs @@ -1,8 +1,9 @@ use std::rc::Rc; use gpui::{ - App, Axis, ElementId, InteractiveElement as _, IntoElement, ParentElement, RenderOnce, - StyleRefinement, Styled, Window, div, prelude::FluentBuilder as _, + App, Axis, ElementId, InteractiveElement as _, IntoElement, ParentElement, RenderOnce, Role, + StatefulInteractiveElement as _, StyleRefinement, Styled, Window, div, + prelude::FluentBuilder as _, }; use crate::{AxisExt, Sizable, Size, StyledExt as _, stepper::StepperItem}; @@ -111,6 +112,7 @@ impl RenderOnce for Stepper { let total_items = self.items.len(); div() .id(self.id) + .role(Role::Group) .w_full() .when(self.layout.is_horizontal(), |this| this.h_flex()) .when(self.layout.is_vertical(), |this| this.v_flex()) diff --git a/crates/ui/src/tab/tab.rs b/crates/ui/src/tab/tab.rs index d33e68c4c6..d88227db61 100644 --- a/crates/ui/src/tab/tab.rs +++ b/crates/ui/src/tab/tab.rs @@ -4,7 +4,7 @@ use crate::{ActiveTheme, Icon, IconName, Selectable, Sizable, Size, StyledExt, h use gpui::prelude::FluentBuilder as _; use gpui::{ AnyElement, App, Background, ClickEvent, Div, Edges, Hsla, InteractiveElement, IntoElement, - MouseButton, ParentElement, Pixels, RenderOnce, SharedString, StatefulInteractiveElement, + MouseButton, ParentElement, Pixels, RenderOnce, Role, SharedString, StatefulInteractiveElement, Styled, Window, div, px, relative, }; @@ -628,6 +628,8 @@ impl RenderOnce for Tab { self.base .id(self.ix) + .role(Role::Tab) + .aria_selected(self.selected) .relative() .flex() .flex_wrap() diff --git a/crates/ui/src/tab/tab_bar.rs b/crates/ui/src/tab/tab_bar.rs index 8a0d0bd6ba..a369da58e1 100644 --- a/crates/ui/src/tab/tab_bar.rs +++ b/crates/ui/src/tab/tab_bar.rs @@ -2,9 +2,9 @@ use std::{cell::RefCell, rc::Rc, time::Duration}; use gpui::{ Anchor, Animation, AnimationExt as _, AnyElement, App, Background, Bounds, Div, Edges, - ElementId, InteractiveElement, IntoElement, ParentElement, Pixels, RenderOnce, ScrollHandle, - SharedString, Stateful, StatefulInteractiveElement as _, StyleRefinement, Styled, Window, div, - prelude::FluentBuilder as _, px, + ElementId, InteractiveElement, IntoElement, ParentElement, Pixels, RenderOnce, Role, + ScrollHandle, SharedString, Stateful, StatefulInteractiveElement as _, StyleRefinement, Styled, + Window, div, prelude::FluentBuilder as _, px, }; use rust_i18n::t; use smallvec::SmallVec; @@ -409,6 +409,7 @@ impl RenderOnce for TabBar { let on_click = self.on_click.clone(); self.base + .role(Role::TabList) .group("tab-bar") .relative() .flex() diff --git a/crates/ui/src/table/table.rs b/crates/ui/src/table/table.rs index a74f2789b9..940c9a330e 100644 --- a/crates/ui/src/table/table.rs +++ b/crates/ui/src/table/table.rs @@ -1,6 +1,7 @@ use gpui::{ - AnyElement, App, InteractiveElement as _, IntoElement, ParentElement, Pixels, RenderOnce, - StyleRefinement, Styled, TextAlign, Window, div, prelude::FluentBuilder as _, px, relative, + AnyElement, App, InteractiveElement as _, IntoElement, ParentElement, Pixels, RenderOnce, Role, + StatefulInteractiveElement as _, StyleRefinement, Styled, TextAlign, Window, div, + prelude::FluentBuilder as _, px, relative, }; use crate::{ActiveTheme as _, AnyChildElement, ChildElement, Sizable, Size, StyledExt as _}; @@ -88,6 +89,7 @@ impl RenderOnce for Table { fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement { div() .id(("table", self.ix)) + .role(Role::Table) .w_full() .text_sm() .overflow_hidden() @@ -160,6 +162,7 @@ impl RenderOnce for TableHeader { fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement { div() .id(("table-header", self.ix)) + .role(Role::RowGroup) .w_full() .bg(cx.theme().tokens.table_head) .text_color(cx.theme().table_head_foreground) @@ -233,6 +236,7 @@ impl RenderOnce for TableBody { fn render(self, _: &mut Window, _: &mut App) -> impl IntoElement { div() .id(("table-body", self.ix)) + .role(Role::RowGroup) .w_full() .refine_style(&self.style) .children( @@ -375,6 +379,8 @@ impl RenderOnce for TableRow { fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement { div() .id(("table-row", self.ix)) + .role(Role::Row) + .aria_row_index(self.ix + 1) .w_full() .flex() .flex_row() @@ -464,6 +470,8 @@ impl RenderOnce for TableHead { div() .id(("table-head", self.ix)) + .role(Role::ColumnHeader) + .aria_column_index(self.ix + 1) .flex() .items_center() .when(self.style.size.width.is_none(), |this| { @@ -556,6 +564,8 @@ impl RenderOnce for TableCell { div() .id(("table-cell", self.ix)) + .role(Role::Cell) + .aria_column_index(self.ix + 1) .flex() .items_center() .when(self.style.size.width.is_none(), |this| { diff --git a/script/run-story-macos b/script/run-story-macos new file mode 100755 index 0000000000..326d51bbf4 --- /dev/null +++ b/script/run-story-macos @@ -0,0 +1,45 @@ +#!/bin/bash +# Build and launch the Story gallery for AccessKit / VoiceOver testing. +# Run from the repo root: +# +# ./script/run-story-macos +# +# We set up a proper .app bundle (so macOS and VoiceOver see the correct +# bundle ID) but execute the binary directly instead of going through +# LaunchServices (`open`). Using `open` causes the macOS bundle lifecycle +# to conflict with GPUI's async window-open (cx.spawn), leading to a +# 100% CPU busy-loop. Direct execution behaves identically to `cargo run`. + +set -euxo pipefail + +APP=/tmp/GPUIComponentStory.app +BIN=gpui-component-story + +cargo build -p gpui-component-story --release + +mkdir -p "$APP/Contents/MacOS" +cp "target/release/$BIN" "$APP/Contents/MacOS/" + +cat > "$APP/Contents/Info.plist" <<'PLIST' + + + + + CFBundleExecutablegpui-component-story + CFBundleIdentifiercom.longbridge.gpui-component-story + CFBundleNameGPUI Component Story + CFBundleVersion1.0 + CFBundlePackageTypeAPPL + NSPrincipalClassNSApplication + NSHighResolutionCapable + + +PLIST + +codesign --sign - --force "$APP" + +# Execute the binary directly — same as `cargo run`. +# Do NOT use `open "$APP"`: LaunchServices triggers a macOS bundle lifecycle +# that conflicts with GPUI's deferred window creation (cx.spawn), causing +# the run loop to spin at 100% CPU. +exec "$APP/Contents/MacOS/$BIN" "$@" From aa8c7bfe4c7cf89e27a0656dd4d1afe26e8a513a Mon Sep 17 00:00:00 2001 From: Jason Lee Date: Mon, 6 Jul 2026 18:20:31 +0800 Subject: [PATCH 2/5] . --- Cargo.lock | 522 ++++++----------------- Cargo.toml | 10 +- crates/story/src/main.rs | 2 +- crates/story/src/stories/button_story.rs | 16 +- 4 files changed, 148 insertions(+), 402 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 89e351115d..dd8d8e926b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,9 +4,9 @@ version = 4 [[package]] name = "accesskit" -version = "0.24.0" +version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5351dcebb14b579ccab05f288596b2ae097005be7ee50a7c3d4ca9d0d5a66f6a" +checksum = "d3b7f7f85a7e5f68090000ed7622545829afd484d210358702ae4cb97dd0c320" dependencies = [ "uuid", ] @@ -27,9 +27,9 @@ dependencies = [ [[package]] name = "accesskit_consumer" -version = "0.35.0" +version = "0.36.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53cf47daed85312e763fbf85ceca136e0d7abc68e0a7e12abe11f48172bc3b10" +checksum = "25e0d7e25d06f4dc21d1774d67146e9e80d6789216cbd4d1e88185b0095dba60" dependencies = [ "accesskit", "hashbrown 0.16.1", @@ -37,9 +37,9 @@ dependencies = [ [[package]] name = "accesskit_consumer" -version = "0.36.0" +version = "0.37.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25e0d7e25d06f4dc21d1774d67146e9e80d6789216cbd4d1e88185b0095dba60" +checksum = "f950720ce064757a1b629caad3a408e8d2c63bb01f29b8a3ff8daa331053ffeb" dependencies = [ "accesskit", "hashbrown 0.16.1", @@ -67,11 +67,11 @@ checksum = "b016ca8db0ea0ea2ceff29a9d6240391492d960716aa471967c00e8cc8cb197c" dependencies = [ "accesskit", "accesskit_atspi_common", - "async-channel 2.5.0", + "async-channel", "async-executor", "async-task", "atspi", - "futures-lite 2.6.1", + "futures-lite", "futures-util", "serde", "zbus", @@ -79,12 +79,12 @@ dependencies = [ [[package]] name = "accesskit_windows" -version = "0.32.1" +version = "0.33.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eff7009f1a532e917d66970a1e80c965140c6cfbbabbdde3d64e5431e6c78e21" +checksum = "36e93ac7bf50b964f1cbb75f741629a4e950571baa1ef1274457ab5a80d9bcc2" dependencies = [ "accesskit", - "accesskit_consumer 0.35.0", + "accesskit_consumer 0.37.0", "hashbrown 0.16.1", "static_assertions", "windows 0.62.2", @@ -296,23 +296,12 @@ version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "435a87a52755b8f27fcf321ac4f04b2802e337c8c4872923137471ec39c37532" dependencies = [ - "event-listener 5.4.1", + "event-listener", "event-listener-strategy", "futures-core", "pin-project-lite", ] -[[package]] -name = "async-channel" -version = "1.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81953c529336010edd6d8e358f886d9581267795c61b19475b71314bffa46d35" -dependencies = [ - "concurrent-queue", - "event-listener 2.5.3", - "futures-core", -] - [[package]] name = "async-channel" version = "2.5.0" @@ -345,8 +334,8 @@ checksum = "c96bf972d85afc50bf5ab8fe2d54d1586b4e0b46c97c50a0c9e71e2f7bcd812a" dependencies = [ "async-task", "concurrent-queue", - "fastrand 2.4.1", - "futures-lite 2.6.1", + "fastrand", + "futures-lite", "pin-project-lite", "slab", ] @@ -359,22 +348,7 @@ checksum = "8034a681df4aed8b8edbd7fbe472401ecf009251c8b40556b304567052e294c5" dependencies = [ "async-lock", "blocking", - "futures-lite 2.6.1", -] - -[[package]] -name = "async-global-executor" -version = "2.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05b1b633a2115cd122d73b955eadd9916c18c8f510ec9cd1686404c60ad1c29c" -dependencies = [ - "async-channel 2.5.0", - "async-executor", - "async-io", - "async-lock", - "blocking", - "futures-lite 2.6.1", - "once_cell", + "futures-lite", ] [[package]] @@ -387,7 +361,7 @@ dependencies = [ "cfg-if", "concurrent-queue", "futures-io", - "futures-lite 2.6.1", + "futures-lite", "parking", "polling", "rustix 1.1.4", @@ -401,7 +375,7 @@ version = "3.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "290f7f2596bd5b78a9fec8088ccd89180d7f9f55b94b0576823bbbdc72ee8311" dependencies = [ - "event-listener 5.4.1", + "event-listener", "event-listener-strategy", "pin-project-lite", ] @@ -414,7 +388,7 @@ checksum = "b948000fad4873c1c9339d60f2623323a0cfd3816e5181033c6a5cb68b2accf7" dependencies = [ "async-io", "blocking", - "futures-lite 2.6.1", + "futures-lite", ] [[package]] @@ -423,15 +397,15 @@ version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc50921ec0055cdd8a16de48773bfeec5c972598674347252c0399676be7da75" dependencies = [ - "async-channel 2.5.0", + "async-channel", "async-io", "async-lock", "async-signal", "async-task", "blocking", "cfg-if", - "event-listener 5.4.1", - "futures-lite 2.6.1", + "event-listener", + "futures-lite", "rustix 1.1.4", ] @@ -464,47 +438,6 @@ dependencies = [ "windows-sys 0.61.2", ] -[[package]] -name = "async-std" -version = "1.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c8e079a4ab67ae52b7403632e4618815d6db36d2a010cfe41b02c1b1578f93b" -dependencies = [ - "async-channel 1.9.0", - "async-global-executor", - "async-io", - "async-lock", - "async-process", - "crossbeam-utils", - "futures-channel", - "futures-core", - "futures-io", - "futures-lite 2.6.1", - "gloo-timers", - "kv-log-macro", - "log", - "memchr", - "once_cell", - "pin-project-lite", - "pin-utils", - "slab", - "wasm-bindgen-futures", -] - -[[package]] -name = "async-tar" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1937db2d56578aa3919b9bdb0e5100693fd7d1c0f145c53eb81fbb03e217550" -dependencies = [ - "async-std", - "filetime", - "libc", - "pin-project", - "redox_syscall 0.2.16", - "xattr", -] - [[package]] name = "async-task" version = "4.7.1" @@ -522,19 +455,6 @@ dependencies = [ "syn 2.0.117", ] -[[package]] -name = "async_zip" -version = "0.0.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d8c50d65ce1b0e0cb65a785ff615f78860d7754290647d3b983208daa4f85e6" -dependencies = [ - "async-compression", - "crc32fast", - "futures-lite 2.6.1", - "pin-project", - "thiserror 2.0.18", -] - [[package]] name = "atk" version = "0.18.2" @@ -757,7 +677,7 @@ dependencies = [ "lazycell", "libc", "mach", - "nix 0.19.1", + "nix", "num-traits", "uom", "winapi", @@ -772,7 +692,7 @@ dependencies = [ "bitflags 2.11.1", "cexpr", "clang-sys", - "itertools 0.13.0", + "itertools 0.11.0", "log", "prettyplease", "proc-macro2", @@ -900,10 +820,10 @@ version = "1.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e83f8d02be6967315521be875afa792a316e28d57b5a2d401897e2a7921b7f21" dependencies = [ - "async-channel 2.5.0", + "async-channel", "async-task", "futures-io", - "futures-lite 2.6.1", + "futures-lite", "piper", ] @@ -1276,7 +1196,7 @@ dependencies = [ [[package]] name = "collections" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed?rev=1d217ee39d381ac101b7cf49d3d22451ac1093fe#1d217ee39d381ac101b7cf49d3d22451ac1093fe" +source = "git+https://github.com/zed-industries/zed#159246f0083787124160620118ac35df5f0f3754" dependencies = [ "gpui_util", "indexmap", @@ -1311,16 +1231,6 @@ dependencies = [ "memchr", ] -[[package]] -name = "command-fds" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b60b5124979fccd9addd89d8b97a1d6eebb4950694520c75ddd722535ea443f" -dependencies = [ - "nix 0.31.2", - "thiserror 2.0.18", -] - [[package]] name = "compression-codecs" version = "0.4.37" @@ -1329,7 +1239,6 @@ checksum = "eb7b51a7d9c967fc26773061ba86150f19c50c0d65c887cb1fbe295fd16619b7" dependencies = [ "bzip2", "compression-core", - "deflate64", "flate2", "memchr", ] @@ -1795,12 +1704,6 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "be1e0bca6c3637f992fc1cc7cbc52a78c1ef6db076dbf1059c4323d6a2048376" -[[package]] -name = "deflate64" -version = "0.1.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac6b926516df9c60bfa16e107b21086399f8285a44ca9711344b9e553c5146e2" - [[package]] name = "deranged" version = "0.5.8" @@ -1849,7 +1752,7 @@ dependencies = [ [[package]] name = "derive_refineable" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed?rev=1d217ee39d381ac101b7cf49d3d22451ac1093fe#1d217ee39d381ac101b7cf49d3d22451ac1093fe" +source = "git+https://github.com/zed-industries/zed#159246f0083787124160620118ac35df5f0f3754" dependencies = [ "proc-macro2", "quote", @@ -2163,12 +2066,6 @@ dependencies = [ "num-traits", ] -[[package]] -name = "event-listener" -version = "2.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" - [[package]] name = "event-listener" version = "5.4.1" @@ -2186,7 +2083,7 @@ version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93" dependencies = [ - "event-listener 5.4.1", + "event-listener", "pin-project-lite", ] @@ -2216,15 +2113,6 @@ dependencies = [ "rand 0.8.6", ] -[[package]] -name = "fastrand" -version = "1.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" -dependencies = [ - "instant", -] - [[package]] name = "fastrand" version = "2.4.1" @@ -2396,7 +2284,7 @@ version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbc773e24e02d4ddd8395fd30dc147524273a83e54e0f312d986ea30de5f5646" dependencies = [ - "roxmltree", + "roxmltree 0.20.0", ] [[package]] @@ -2524,7 +2412,7 @@ checksum = "175cd8cca9e1d45b87f18ffa75088f2099e3c4fe5e2f83e42de112560bea8ea6" dependencies = [ "fixedbitset", "futures-core", - "futures-lite 2.6.1", + "futures-lite", "pin-project", "smallvec", ] @@ -2552,28 +2440,13 @@ version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" -[[package]] -name = "futures-lite" -version = "1.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49a9d51ce47660b1e808d3c990b4709f2f415d928835a17dfd16991515c46bce" -dependencies = [ - "fastrand 1.9.0", - "futures-core", - "futures-io", - "memchr", - "parking", - "pin-project-lite", - "waker-fn", -] - [[package]] name = "futures-lite" version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f78e10609fe0e0b3f4157ffab1876319b5b0db102a2c60dc4626306dc46b44ad" dependencies = [ - "fastrand 2.4.1", + "fastrand", "futures-core", "futures-io", "parking", @@ -2785,16 +2658,6 @@ dependencies = [ "wasip3", ] -[[package]] -name = "gif" -version = "0.13.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ae047235e33e2829703574b54fdec96bfbad892062d97fed2f76022287de61b" -dependencies = [ - "color_quant", - "weezl", -] - [[package]] name = "gif" version = "0.14.2" @@ -2931,18 +2794,6 @@ dependencies = [ "walkdir", ] -[[package]] -name = "gloo-timers" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbb143cf96099802033e0d4f4963b19fd2e0b728bcf076cd9cf7f6634f092994" -dependencies = [ - "futures-channel", - "futures-core", - "js-sys", - "wasm-bindgen", -] - [[package]] name = "glow" version = "0.17.0" @@ -2986,7 +2837,7 @@ dependencies = [ "log", "presser", "thiserror 2.0.18", - "windows 0.62.2", + "windows 0.58.0", ] [[package]] @@ -3012,11 +2863,11 @@ dependencies = [ [[package]] name = "gpui" version = "0.2.2" -source = "git+https://github.com/zed-industries/zed?rev=1d217ee39d381ac101b7cf49d3d22451ac1093fe#1d217ee39d381ac101b7cf49d3d22451ac1093fe" +source = "git+https://github.com/zed-industries/zed#159246f0083787124160620118ac35df5f0f3754" dependencies = [ "accesskit", "anyhow", - "async-channel 2.5.0", + "async-channel", "async-task", "backtrace", "bindgen", @@ -3099,7 +2950,7 @@ version = "0.5.2" dependencies = [ "aho-corasick", "anyhow", - "async-channel 2.5.0", + "async-channel", "chrono", "core-text", "enum-iterator", @@ -3258,7 +3109,7 @@ dependencies = [ [[package]] name = "gpui_linux" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed?rev=1d217ee39d381ac101b7cf49d3d22451ac1093fe#1d217ee39d381ac101b7cf49d3d22451ac1093fe" +source = "git+https://github.com/zed-industries/zed#159246f0083787124160620118ac35df5f0f3754" dependencies = [ "accesskit", "accesskit_unix", @@ -3273,6 +3124,7 @@ dependencies = [ "filedescriptor", "futures", "gpui", + "gpui_util", "gpui_wgpu", "http_client", "image", @@ -3291,7 +3143,6 @@ dependencies = [ "strum", "swash", "url", - "util", "uuid", "wayland-backend", "wayland-client", @@ -3309,7 +3160,7 @@ dependencies = [ [[package]] name = "gpui_macos" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed?rev=1d217ee39d381ac101b7cf49d3d22451ac1093fe#1d217ee39d381ac101b7cf49d3d22451ac1093fe" +source = "git+https://github.com/zed-industries/zed#159246f0083787124160620118ac35df5f0f3754" dependencies = [ "accesskit", "accesskit_macos", @@ -3331,6 +3182,7 @@ dependencies = [ "foreign-types", "futures", "gpui", + "gpui_util", "image", "itertools 0.14.0", "libc", @@ -3348,7 +3200,6 @@ dependencies = [ "semver", "smallvec", "strum", - "util", "uuid", "zed-font-kit", ] @@ -3356,7 +3207,7 @@ dependencies = [ [[package]] name = "gpui_macros" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed?rev=1d217ee39d381ac101b7cf49d3d22451ac1093fe#1d217ee39d381ac101b7cf49d3d22451ac1093fe" +source = "git+https://github.com/zed-industries/zed#159246f0083787124160620118ac35df5f0f3754" dependencies = [ "heck 0.5.0", "proc-macro2", @@ -3367,7 +3218,7 @@ dependencies = [ [[package]] name = "gpui_platform" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed?rev=1d217ee39d381ac101b7cf49d3d22451ac1093fe#1d217ee39d381ac101b7cf49d3d22451ac1093fe" +source = "git+https://github.com/zed-industries/zed#159246f0083787124160620118ac35df5f0f3754" dependencies = [ "console_error_panic_hook", "gpui", @@ -3380,7 +3231,7 @@ dependencies = [ [[package]] name = "gpui_shared_string" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed?rev=1d217ee39d381ac101b7cf49d3d22451ac1093fe#1d217ee39d381ac101b7cf49d3d22451ac1093fe" +source = "git+https://github.com/zed-industries/zed#159246f0083787124160620118ac35df5f0f3754" dependencies = [ "schemars", "serde", @@ -3390,16 +3241,17 @@ dependencies = [ [[package]] name = "gpui_util" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed?rev=1d217ee39d381ac101b7cf49d3d22451ac1093fe#1d217ee39d381ac101b7cf49d3d22451ac1093fe" +source = "git+https://github.com/zed-industries/zed#159246f0083787124160620118ac35df5f0f3754" dependencies = [ "anyhow", "log", + "which", ] [[package]] name = "gpui_web" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed?rev=1d217ee39d381ac101b7cf49d3d22451ac1093fe#1d217ee39d381ac101b7cf49d3d22451ac1093fe" +source = "git+https://github.com/zed-industries/zed#159246f0083787124160620118ac35df5f0f3754" dependencies = [ "anyhow", "console_error_panic_hook", @@ -3423,7 +3275,7 @@ dependencies = [ [[package]] name = "gpui_wgpu" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed?rev=1d217ee39d381ac101b7cf49d3d22451ac1093fe#1d217ee39d381ac101b7cf49d3d22451ac1093fe" +source = "git+https://github.com/zed-industries/zed#159246f0083787124160620118ac35df5f0f3754" dependencies = [ "anyhow", "bytemuck", @@ -3452,15 +3304,17 @@ dependencies = [ [[package]] name = "gpui_windows" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed?rev=1d217ee39d381ac101b7cf49d3d22451ac1093fe#1d217ee39d381ac101b7cf49d3d22451ac1093fe" +source = "git+https://github.com/zed-industries/zed#159246f0083787124160620118ac35df5f0f3754" dependencies = [ "accesskit", "accesskit_windows", "anyhow", "collections", + "dunce", "etagere", "futures", "gpui", + "gpui_util", "image", "itertools 0.14.0", "log", @@ -3468,7 +3322,6 @@ dependencies = [ "rand 0.9.4", "raw-window-handle", "smallvec", - "util", "uuid", "windows 0.61.3", "windows-core 0.61.2", @@ -3767,12 +3620,10 @@ dependencies = [ [[package]] name = "http_client" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed?rev=1d217ee39d381ac101b7cf49d3d22451ac1093fe#1d217ee39d381ac101b7cf49d3d22451ac1093fe" +source = "git+https://github.com/zed-industries/zed#159246f0083787124160620118ac35df5f0f3754" dependencies = [ "anyhow", "async-compression", - "async-fs", - "async-tar", "bytes", "derive_more 2.1.1", "futures", @@ -3783,16 +3634,13 @@ dependencies = [ "serde", "serde_json", "serde_urlencoded", - "sha2", - "tempfile", "url", - "util", ] [[package]] name = "http_client_tls" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed?rev=1d217ee39d381ac101b7cf49d3d22451ac1093fe#1d217ee39d381ac101b7cf49d3d22451ac1093fe" +source = "git+https://github.com/zed-industries/zed#159246f0083787124160620118ac35df5f0f3754" dependencies = [ "rustls", "rustls-platform-verifier", @@ -3873,7 +3721,7 @@ dependencies = [ "js-sys", "log", "wasm-bindgen", - "windows-core 0.62.2", + "windows-core 0.58.0", ] [[package]] @@ -4026,7 +3874,7 @@ dependencies = [ "byteorder-lite", "color_quant", "exr", - "gif 0.14.2", + "gif", "image-webp", "moxcms", "num-traits", @@ -4036,8 +3884,8 @@ dependencies = [ "rayon", "rgb", "tiff", - "zune-core 0.5.1", - "zune-jpeg 0.5.15", + "zune-core", + "zune-jpeg", ] [[package]] @@ -4052,9 +3900,9 @@ dependencies = [ [[package]] name = "imagesize" -version = "0.13.0" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edcd27d72f2f071c64249075f42e205ff93c9a4c5f6c6da53e79ed9f9832c285" +checksum = "09e54e57b4c48b40f7aec75635392b12b3421fa26fe8b4332e63138ed278459c" [[package]] name = "imgref" @@ -4366,24 +4214,16 @@ dependencies = [ [[package]] name = "kurbo" -version = "0.11.3" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c62026ae44756f8a599ba21140f350303d4f08dcdcc71b5ad9c9bb8128c13c62" +checksum = "4b60dfc32f652b926df6192e55525b16d186c69d47876c3ead4da5cc9f8450e2" dependencies = [ "arrayvec", "euclid", + "polycool", "smallvec", ] -[[package]] -name = "kv-log-macro" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0de8b303297635ad57c9f5059fd9cee7a47f8e8daa09df0fcd07dd39fb22977f" -dependencies = [ - "log", -] - [[package]] name = "lazy_static" version = "1.5.0" @@ -4805,7 +4645,7 @@ dependencies = [ [[package]] name = "media" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed?rev=1d217ee39d381ac101b7cf49d3d22451ac1093fe#1d217ee39d381ac101b7cf49d3d22451ac1093fe" +source = "git+https://github.com/zed-industries/zed#159246f0083787124160620118ac35df5f0f3754" dependencies = [ "anyhow", "bindgen", @@ -4912,8 +4752,9 @@ dependencies = [ [[package]] name = "naga" -version = "29.0.3" -source = "git+https://github.com/zed-industries/wgpu.git?rev=357a0c56e0070480ad9daea5d2eaa83150b79e88#357a0c56e0070480ad9daea5d2eaa83150b79e88" +version = "29.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2bf919621e7975acb27d881bae2fb993e0d45c8e0446e85e6272971e00dc8df" dependencies = [ "arrayvec", "bit-set 0.9.1", @@ -4986,30 +4827,6 @@ dependencies = [ "libc", ] -[[package]] -name = "nix" -version = "0.29.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" -dependencies = [ - "bitflags 2.11.1", - "cfg-if", - "cfg_aliases", - "libc", -] - -[[package]] -name = "nix" -version = "0.31.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d6d0705320c1e6ba1d912b5e37cf18071b6c2e9b7fa8215a1e8a7651966f5d3" -dependencies = [ - "bitflags 2.11.1", - "cfg-if", - "cfg_aliases", - "libc", -] - [[package]] name = "no_std_io2" version = "0.9.3" @@ -5620,7 +5437,7 @@ dependencies = [ "cipher", "digest", "endi", - "futures-lite 2.6.1", + "futures-lite", "futures-util", "getrandom 0.4.2", "hkdf", @@ -5797,7 +5614,7 @@ checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" [[package]] name = "perf" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed?rev=1d217ee39d381ac101b7cf49d3d22451ac1093fe#1d217ee39d381ac101b7cf49d3d22451ac1093fe" +source = "git+https://github.com/zed-industries/zed#159246f0083787124160620118ac35df5f0f3754" dependencies = [ "collections", "serde", @@ -5944,7 +5761,7 @@ version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "135ace3a761e564ec88c03a77317a7c6b80bb7f7135ef2544dbe054243b89737" dependencies = [ - "fastrand 2.4.1", + "fastrand", "phf_shared 0.13.1", ] @@ -6056,12 +5873,6 @@ version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" -[[package]] -name = "pin-utils" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" - [[package]] name = "piper" version = "0.2.5" @@ -6069,7 +5880,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c835479a4443ded371d6c535cbfd8d31ad92c5d23ae9770a61bc155e4992a3c1" dependencies = [ "atomic-waker", - "fastrand 2.4.1", + "fastrand", "futures-io", ] @@ -6137,6 +5948,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2f3a9f18d041e6d0e102a0a46750538147e5e8992d3b4873aaafee2520b00ce3" +[[package]] +name = "polycool" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50596ddc09eb5ad5f75cacd40209568e66df71baf86e1499a0e99c4cff12a5a6" +dependencies = [ + "arrayvec", +] + [[package]] name = "portable-atomic" version = "1.13.1" @@ -6740,15 +6560,6 @@ dependencies = [ "font-types", ] -[[package]] -name = "redox_syscall" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" -dependencies = [ - "bitflags 1.3.2", -] - [[package]] name = "redox_syscall" version = "0.5.18" @@ -6801,7 +6612,7 @@ dependencies = [ [[package]] name = "refineable" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed?rev=1d217ee39d381ac101b7cf49d3d22451ac1093fe#1d217ee39d381ac101b7cf49d3d22451ac1093fe" +source = "git+https://github.com/zed-industries/zed#159246f0083787124160620118ac35df5f0f3754" dependencies = [ "derive_refineable", ] @@ -6853,7 +6664,7 @@ checksum = "19b30a45b0cd0bcca8037f3d0dc3421eaf95327a17cad11964fb8179b4fc4832" [[package]] name = "reqwest_client" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed?rev=1d217ee39d381ac101b7cf49d3d22451ac1093fe#1d217ee39d381ac101b7cf49d3d22451ac1093fe" +source = "git+https://github.com/zed-industries/zed#159246f0083787124160620118ac35df5f0f3754" dependencies = [ "anyhow", "bytes", @@ -6870,11 +6681,11 @@ dependencies = [ [[package]] name = "resvg" -version = "0.45.1" +version = "0.46.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8928798c0a55e03c9ca6c4c6846f76377427d2c1e1f7e6de3c06ae57942df43" +checksum = "b563218631706d614e23059436526d005b50ab5f2d506b55a17eb65c5eb83419" dependencies = [ - "gif 0.13.3", + "gif", "image-webp", "log", "pico-args", @@ -6882,7 +6693,7 @@ dependencies = [ "svgtypes", "tiny-skia", "usvg", - "zune-jpeg 0.4.21", + "zune-jpeg", ] [[package]] @@ -6961,6 +6772,15 @@ version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c20b6793b5c2fa6553b250154b78d6d0db37e72700ae35fad9387a46f487c97" +[[package]] +name = "roxmltree" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1964b10c76125c36f8afe190065a4bf9a87bf324842c05701330bba9f1cacbb" +dependencies = [ + "memchr", +] + [[package]] name = "rust-embed" version = "8.11.0" @@ -7267,7 +7087,7 @@ dependencies = [ [[package]] name = "scheduler" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed?rev=1d217ee39d381ac101b7cf49d3d22451ac1093fe#1d217ee39d381ac101b7cf49d3d22451ac1093fe" +source = "git+https://github.com/zed-industries/zed#159246f0083787124160620118ac35df5f0f3754" dependencies = [ "async-task", "backtrace", @@ -7477,19 +7297,6 @@ dependencies = [ "zmij", ] -[[package]] -name = "serde_json_lenient" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e033097bf0d2b59a62b42c18ebbb797503839b26afdda2c4e1415cb6c813540" -dependencies = [ - "indexmap", - "itoa", - "memchr", - "ryu", - "serde", -] - [[package]] name = "serde_repr" version = "0.1.20" @@ -7695,7 +7502,7 @@ version = "2.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a33bd3e260892199c3ccfc487c88b2da2265080acb316cd920da72fdfd7c599f" dependencies = [ - "async-channel 2.5.0", + "async-channel", "async-executor", "async-fs", "async-io", @@ -7703,7 +7510,7 @@ dependencies = [ "async-net", "async-process", "blocking", - "futures-lite 2.6.1", + "futures-lite", ] [[package]] @@ -7901,7 +7708,7 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "sum_tree" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed?rev=1d217ee39d381ac101b7cf49d3d22451ac1093fe#1d217ee39d381ac101b7cf49d3d22451ac1093fe" +source = "git+https://github.com/zed-industries/zed#159246f0083787124160620118ac35df5f0f3754" dependencies = [ "heapless", "log", @@ -7996,9 +7803,9 @@ checksum = "0193cc4331cfd2f3d2011ef287590868599a2f33c3e69bc22c1a3d3acf9e02fb" [[package]] name = "svgtypes" -version = "0.15.3" +version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68c7541fff44b35860c1a7a47a7cadf3e4a304c457b58f9870d9706ece028afc" +checksum = "695b5790b3131dafa99b3bbfd25a216edb3d216dad9ca208d4657bfb8f2abc3d" dependencies = [ "kurbo", "siphasher 1.0.2", @@ -8157,12 +7964,6 @@ dependencies = [ "slotmap", ] -[[package]] -name = "take-until" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8bdb6fa0dfa67b38c1e66b7041ba9dcf23b99d8121907cd31c807a332f7a0bbb" - [[package]] name = "tao-core-video-sys" version = "0.2.0" @@ -8204,7 +8005,7 @@ version = "3.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" dependencies = [ - "fastrand 2.4.1", + "fastrand", "getrandom 0.4.2", "once_cell", "rustix 1.1.4", @@ -8302,7 +8103,7 @@ dependencies = [ "half", "quick-error 2.0.1", "weezl", - "zune-jpeg 0.5.15", + "zune-jpeg", ] [[package]] @@ -9291,9 +9092,9 @@ dependencies = [ [[package]] name = "usvg" -version = "0.45.1" +version = "0.46.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80be9b06fbae3b8b303400ab20778c80bbaf338f563afe567cf3c9eea17b47ef" +checksum = "e419dff010bb12512b0ae9e3d2f318dfbdf0167fde7eb05465134d4e8756076f" dependencies = [ "base64", "data-url", @@ -9303,7 +9104,7 @@ dependencies = [ "kurbo", "log", "pico-args", - "roxmltree", + "roxmltree 0.21.1", "rustybuzz", "simplecss", "siphasher 1.0.2", @@ -9328,49 +9129,10 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" -[[package]] -name = "util" -version = "0.1.0" -source = "git+https://github.com/zed-industries/zed?rev=1d217ee39d381ac101b7cf49d3d22451ac1093fe#1d217ee39d381ac101b7cf49d3d22451ac1093fe" -dependencies = [ - "anyhow", - "async-fs", - "async_zip", - "collections", - "command-fds", - "dirs", - "dunce", - "futures", - "futures-lite 1.13.0", - "globset", - "gpui_util", - "itertools 0.14.0", - "libc", - "log", - "mach2", - "nix 0.29.0", - "percent-encoding", - "regex", - "rust-embed", - "schemars", - "serde", - "serde_json", - "serde_json_lenient", - "shlex", - "smol", - "take-until", - "tempfile", - "tendril", - "unicase", - "url", - "walkdir", - "which", -] - [[package]] name = "util_macros" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed?rev=1d217ee39d381ac101b7cf49d3d22451ac1093fe#1d217ee39d381ac101b7cf49d3d22451ac1093fe" +source = "git+https://github.com/zed-industries/zed#159246f0083787124160620118ac35df5f0f3754" dependencies = [ "perf", "quote", @@ -9892,8 +9654,9 @@ checksum = "a28ac98ddc8b9274cb41bb4d9d4d5c425b6020c50c46f25559911905610b4a88" [[package]] name = "wgpu" -version = "29.0.3" -source = "git+https://github.com/zed-industries/wgpu.git?rev=357a0c56e0070480ad9daea5d2eaa83150b79e88#357a0c56e0070480ad9daea5d2eaa83150b79e88" +version = "29.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76e8840e1ba2881d4cbb18d2147627a56af426ff064c0401eb0c8410c6325d07" dependencies = [ "arrayvec", "bitflags 2.11.1", @@ -9921,8 +9684,9 @@ dependencies = [ [[package]] name = "wgpu-core" -version = "29.0.3" -source = "git+https://github.com/zed-industries/wgpu.git?rev=357a0c56e0070480ad9daea5d2eaa83150b79e88#357a0c56e0070480ad9daea5d2eaa83150b79e88" +version = "29.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f519832254e56965a9940c4af57dcb75f702b6f6fa4a0b172f685395843a4d7" dependencies = [ "arrayvec", "bit-set 0.9.1", @@ -9953,32 +9717,36 @@ dependencies = [ [[package]] name = "wgpu-core-deps-apple" -version = "29.0.3" -source = "git+https://github.com/zed-industries/wgpu.git?rev=357a0c56e0070480ad9daea5d2eaa83150b79e88#357a0c56e0070480ad9daea5d2eaa83150b79e88" +version = "29.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5e39e26c4c0e07589e67d18546cf79ff45383659fc72fca4dd293358a0347f3" dependencies = [ "wgpu-hal", ] [[package]] name = "wgpu-core-deps-emscripten" -version = "29.0.3" -source = "git+https://github.com/zed-industries/wgpu.git?rev=357a0c56e0070480ad9daea5d2eaa83150b79e88#357a0c56e0070480ad9daea5d2eaa83150b79e88" +version = "29.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01e09be551dc939498bdd5f6b2c66e55ab275dad25825267a08605a80fc9f0af" dependencies = [ "wgpu-hal", ] [[package]] name = "wgpu-core-deps-windows-linux-android" -version = "29.0.3" -source = "git+https://github.com/zed-industries/wgpu.git?rev=357a0c56e0070480ad9daea5d2eaa83150b79e88#357a0c56e0070480ad9daea5d2eaa83150b79e88" +version = "29.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e592c1bbef6ad047647ae6e666ebd8cee7a32bb4544d9700ec96cbf73230257" dependencies = [ "wgpu-hal", ] [[package]] name = "wgpu-hal" -version = "29.0.3" -source = "git+https://github.com/zed-industries/wgpu.git?rev=357a0c56e0070480ad9daea5d2eaa83150b79e88#357a0c56e0070480ad9daea5d2eaa83150b79e88" +version = "29.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97ace1c17727311c22a46e4e3faf56ea6de81af99dcc839bdfb54857b94d448d" dependencies = [ "android_system_properties", "arrayvec", @@ -10030,8 +9798,9 @@ dependencies = [ [[package]] name = "wgpu-naga-bridge" -version = "29.0.3" -source = "git+https://github.com/zed-industries/wgpu.git?rev=357a0c56e0070480ad9daea5d2eaa83150b79e88#357a0c56e0070480ad9daea5d2eaa83150b79e88" +version = "29.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95226013f547544b223281cd16a4fb549aa9dcb562adbda0faae4c73ffbbc161" dependencies = [ "naga", "wgpu-types", @@ -10039,8 +9808,9 @@ dependencies = [ [[package]] name = "wgpu-types" -version = "29.0.3" -source = "git+https://github.com/zed-industries/wgpu.git?rev=357a0c56e0070480ad9daea5d2eaa83150b79e88#357a0c56e0070480ad9daea5d2eaa83150b79e88" +version = "29.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84bf84cd9ca8ca45e2b223a3868f1adf9bfc0c66aeac212e76ee7e40fdadf8f5" dependencies = [ "bitflags 2.11.1", "bytemuck", @@ -10922,15 +10692,6 @@ version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ea6fc2961e4ef194dcbfe56bb845534d0dc8098940c7e5c012a258bfec6701bd" -[[package]] -name = "xattr" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d1526bbe5aaeb5eb06885f4d987bcdfa5e23187055de9b83fe00156a821fabc" -dependencies = [ - "libc", -] - [[package]] name = "xcb" version = "1.7.0" @@ -11068,9 +10829,9 @@ dependencies = [ "async-trait", "blocking", "enumflags2", - "event-listener 5.4.1", + "event-listener", "futures-core", - "futures-lite 2.6.1", + "futures-lite", "hex", "libc", "ordered-stream", @@ -11371,7 +11132,7 @@ dependencies = [ [[package]] name = "zlog" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed?rev=1d217ee39d381ac101b7cf49d3d22451ac1093fe#1d217ee39d381ac101b7cf49d3d22451ac1093fe" +source = "git+https://github.com/zed-industries/zed#159246f0083787124160620118ac35df5f0f3754" dependencies = [ "anyhow", "chrono", @@ -11388,7 +11149,7 @@ checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" [[package]] name = "ztracing" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed?rev=1d217ee39d381ac101b7cf49d3d22451ac1093fe#1d217ee39d381ac101b7cf49d3d22451ac1093fe" +source = "git+https://github.com/zed-industries/zed#159246f0083787124160620118ac35df5f0f3754" dependencies = [ "tracing", "tracing-subscriber", @@ -11399,13 +11160,7 @@ dependencies = [ [[package]] name = "ztracing_macro" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed?rev=1d217ee39d381ac101b7cf49d3d22451ac1093fe#1d217ee39d381ac101b7cf49d3d22451ac1093fe" - -[[package]] -name = "zune-core" -version = "0.4.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f423a2c17029964870cfaabb1f13dfab7d092a62a29a89264f4d36990ca414a" +source = "git+https://github.com/zed-industries/zed#159246f0083787124160620118ac35df5f0f3754" [[package]] name = "zune-core" @@ -11422,22 +11177,13 @@ dependencies = [ "simd-adler32", ] -[[package]] -name = "zune-jpeg" -version = "0.4.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29ce2c8a9384ad323cf564b67da86e21d3cfdff87908bc1223ed5c99bc792713" -dependencies = [ - "zune-core 0.4.12", -] - [[package]] name = "zune-jpeg" version = "0.5.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "27bc9d5b815bc103f142aa054f561d9187d191692ec7c2d1e2b4737f8dbd7296" dependencies = [ - "zune-core 0.5.1", + "zune-core", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index ea55474efe..0bc3fabb25 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,11 +32,11 @@ gpui-component-macros = { path = "crates/macros", version = "0.5.1" } gpui-component-assets = { path = "crates/assets", version = "0.5.1" } story = { path = "crates/story" } -gpui = { git = "https://github.com/zed-industries/zed", rev = "1d217ee39d381ac101b7cf49d3d22451ac1093fe" } -gpui_platform = { git = "https://github.com/zed-industries/zed", rev = "1d217ee39d381ac101b7cf49d3d22451ac1093fe", features = ["font-kit", "x11", "wayland", "runtime_shaders"] } -gpui_web = { git = "https://github.com/zed-industries/zed", rev = "1d217ee39d381ac101b7cf49d3d22451ac1093fe" } -gpui_macros = { git = "https://github.com/zed-industries/zed", rev = "1d217ee39d381ac101b7cf49d3d22451ac1093fe" } -reqwest_client = { git = "https://github.com/zed-industries/zed", rev = "1d217ee39d381ac101b7cf49d3d22451ac1093fe" } +gpui = { git = "https://github.com/zed-industries/zed" } +gpui_platform = { git = "https://github.com/zed-industries/zed", features = ["font-kit", "x11", "wayland", "runtime_shaders"] } +gpui_web = { git = "https://github.com/zed-industries/zed" } +gpui_macros = { git = "https://github.com/zed-industries/zed" } +reqwest_client = { git = "https://github.com/zed-industries/zed" } sum-tree = { version = "0.2.0", package = "zed-sum-tree" } # reqwest = { version = "0.12.15-zed", package = "zed-reqwest" } reqwest = { git = "https://github.com/zed-industries/reqwest.git", rev = "c15662463bda39148ba154100dd44d3fba5873a4", default-features = false, features = [ diff --git a/crates/story/src/main.rs b/crates/story/src/main.rs index e591626cf8..e655c37086 100644 --- a/crates/story/src/main.rs +++ b/crates/story/src/main.rs @@ -1,5 +1,5 @@ use gpui_component_assets::Assets; -use gpui_component_story::{Gallery, init, create_new_window}; +use gpui_component_story::{Gallery, create_new_window, init}; fn main() { let app = gpui_platform::application().with_assets(Assets); diff --git a/crates/story/src/stories/button_story.rs b/crates/story/src/stories/button_story.rs index 749ce0aed3..012aa42823 100644 --- a/crates/story/src/stories/button_story.rs +++ b/crates/story/src/stories/button_story.rs @@ -491,7 +491,7 @@ impl Render for ButtonStory { section("With Dropdown Caret") .max_w_lg() .child( - Button::new("button-outline-1") + Button::new("button-dropdown-caret-primary") .primary() .dropdown_caret(true) .label("Primary Button") @@ -502,7 +502,7 @@ impl Render for ButtonStory { .on_click(Self::on_click), ) .child( - Button::new("button-outline-2") + Button::new("button-dropdown-caret-default") .label("Default Button") .dropdown_caret(true) .disabled(disabled) @@ -523,7 +523,7 @@ impl Render for ButtonStory { .on_click(Self::on_click), ) .child( - Button::new("button-outline-5-ghost") + Button::new("button-dropdown-caret-ghost") .ghost() .dropdown_caret(true) .label("Ghost Button") @@ -534,7 +534,7 @@ impl Render for ButtonStory { .on_click(Self::on_click), ) .child( - Button::new("button-outline-5-link") + Button::new("button-dropdown-caret-link") .link() .dropdown_caret(true) .label("Link Button") @@ -545,7 +545,7 @@ impl Render for ButtonStory { .on_click(Self::on_click), ) .child( - Button::new("button-outline-5-text") + Button::new("button-dropdown-caret-small") .outline() .small() .dropdown_caret(true) @@ -735,7 +735,7 @@ impl Render for ButtonStory { .layout(Axis::Vertical) .disabled(disabled) .child( - Button::new("button-one") + Button::new("button-vertical-one") .label("One") .disabled(disabled) .selected(selected) @@ -743,7 +743,7 @@ impl Render for ButtonStory { .on_click(Self::on_click), ) .child( - Button::new("button-two") + Button::new("button-vertical-two") .label("Two") .disabled(disabled) .selected(selected) @@ -751,7 +751,7 @@ impl Render for ButtonStory { .on_click(Self::on_click), ) .child( - Button::new("button-three") + Button::new("button-vertical-three") .label("Three") .disabled(disabled) .selected(selected) From 7945573fc18baf63d765b8dd6e00d4e4a21e7de6 Mon Sep 17 00:00:00 2001 From: Jason Lee Date: Mon, 6 Jul 2026 18:37:00 +0800 Subject: [PATCH 3/5] . --- crates/ui/src/accordion.rs | 1 - crates/ui/src/group_box.rs | 4 +--- crates/ui/src/menu/menu_item.rs | 21 ++++++++++++++++++ crates/ui/src/menu/popup_menu.rs | 38 +++++++++++++++++++++++++++++++- crates/ui/src/stepper/stepper.rs | 6 ++--- crates/ui/src/tab/tab.rs | 33 +++++++++++++++++++++++++++ 6 files changed, 94 insertions(+), 9 deletions(-) diff --git a/crates/ui/src/accordion.rs b/crates/ui/src/accordion.rs index 1795f83428..00222b967b 100644 --- a/crates/ui/src/accordion.rs +++ b/crates/ui/src/accordion.rs @@ -88,7 +88,6 @@ impl RenderOnce for Accordion { v_flex() .id(self.id) - .role(Role::Group) .size_full() .when(self.bordered, |this| this.gap_y_2()) .children( diff --git a/crates/ui/src/group_box.rs b/crates/ui/src/group_box.rs index 02c9ed8243..fd59de1595 100644 --- a/crates/ui/src/group_box.rs +++ b/crates/ui/src/group_box.rs @@ -1,7 +1,6 @@ use gpui::{ AnyElement, App, Background, ElementId, InteractiveElement as _, IntoElement, ParentElement, - RenderOnce, Role, StatefulInteractiveElement as _, StyleRefinement, Styled, Window, div, - prelude::FluentBuilder, relative, + RenderOnce, StyleRefinement, Styled, Window, div, prelude::FluentBuilder, relative, }; use smallvec::SmallVec; @@ -140,7 +139,6 @@ impl RenderOnce for GroupBox { div().child( v_flex() .id(self.id.unwrap_or("group-box".into())) - .role(Role::Group) .w_full() .when(has_paddings, |this| this.gap_3()) .when(!has_paddings, |this| this.gap_4()) diff --git a/crates/ui/src/menu/menu_item.rs b/crates/ui/src/menu/menu_item.rs index f32ff33f8f..29eea9d158 100644 --- a/crates/ui/src/menu/menu_item.rs +++ b/crates/ui/src/menu/menu_item.rs @@ -10,6 +10,7 @@ use smallvec::SmallVec; pub(crate) struct MenuItemElement { id: ElementId, group_name: SharedString, + aria_label: Option, style: StyleRefinement, disabled: bool, selected: bool, @@ -25,6 +26,7 @@ impl MenuItemElement { Self { id: id.clone(), group_name: group_name.into(), + aria_label: None, style: StyleRefinement::default(), disabled: false, selected: false, @@ -40,6 +42,12 @@ impl MenuItemElement { self } + /// Set the accessible label for the menu item. + pub(crate) fn aria_label(mut self, label: impl Into) -> Self { + self.aria_label = Some(label.into()); + self + } + /// Set the disabled state of the MenuItem. pub(crate) fn disabled(mut self, disabled: bool) -> Self { self.disabled = disabled; @@ -87,6 +95,7 @@ impl RenderOnce for MenuItemElement { h_flex() .id(self.id) .role(Role::MenuItem) + .when_some(self.aria_label, |this, label| this.aria_label(label)) .aria_selected(self.selected) .group(&self.group_name) .gap_x_1() @@ -123,3 +132,15 @@ impl RenderOnce for MenuItemElement { .children(self.children) } } + +#[cfg(test)] +mod tests { + use super::*; + + #[gpui::test] + fn aria_label_sets_accessible_name(_cx: &mut gpui::TestAppContext) { + let item = MenuItemElement::new("open", "menu").aria_label("Open"); + + assert_eq!(item.aria_label, Some("Open".into())); + } +} diff --git a/crates/ui/src/menu/popup_menu.rs b/crates/ui/src/menu/popup_menu.rs index 505b11714a..0d564e54c9 100644 --- a/crates/ui/src/menu/popup_menu.rs +++ b/crates/ui/src/menu/popup_menu.rs @@ -268,6 +268,15 @@ impl PopupMenuItem { _ => false, } } + + fn a11y_label(&self) -> Option { + match self { + PopupMenuItem::Item { label, .. } + | PopupMenuItem::Label(label) + | PopupMenuItem::Submenu { label, .. } => Some(label.clone()), + PopupMenuItem::Separator | PopupMenuItem::ElementItem { .. } => None, + } + } } pub struct PopupMenu { @@ -1107,7 +1116,8 @@ impl PopupMenu { } cx.notify(); - })); + })) + .when_some(item.a11y_label(), |this, label| this.aria_label(label)); match item { PopupMenuItem::Separator => this @@ -1349,3 +1359,29 @@ impl Render for PopupMenu { }) } } + +#[cfg(test)] +mod tests { + use super::*; + + #[gpui::test] + fn popup_menu_item_a11y_label_uses_visible_label(cx: &mut gpui::TestAppContext) { + let submenu = cx.update(|cx| cx.new(|cx| PopupMenu::new(cx))); + + assert_eq!(PopupMenuItem::new("Open").a11y_label(), Some("Open".into())); + assert_eq!( + PopupMenuItem::link("Docs", "https://example.com").a11y_label(), + Some("Docs".into()) + ); + assert_eq!( + PopupMenuItem::label("Recent files").a11y_label(), + Some("Recent files".into()) + ); + assert_eq!( + PopupMenuItem::submenu("More", submenu).a11y_label(), + Some("More".into()) + ); + assert_eq!(PopupMenuItem::separator().a11y_label(), None); + assert_eq!(PopupMenuItem::element(|_, _| div()).a11y_label(), None); + } +} diff --git a/crates/ui/src/stepper/stepper.rs b/crates/ui/src/stepper/stepper.rs index 021324680e..8a51fca7e0 100644 --- a/crates/ui/src/stepper/stepper.rs +++ b/crates/ui/src/stepper/stepper.rs @@ -1,9 +1,8 @@ use std::rc::Rc; use gpui::{ - App, Axis, ElementId, InteractiveElement as _, IntoElement, ParentElement, RenderOnce, Role, - StatefulInteractiveElement as _, StyleRefinement, Styled, Window, div, - prelude::FluentBuilder as _, + App, Axis, ElementId, InteractiveElement as _, IntoElement, ParentElement, RenderOnce, + StyleRefinement, Styled, Window, div, prelude::FluentBuilder as _, }; use crate::{AxisExt, Sizable, Size, StyledExt as _, stepper::StepperItem}; @@ -112,7 +111,6 @@ impl RenderOnce for Stepper { let total_items = self.items.len(); div() .id(self.id) - .role(Role::Group) .w_full() .when(self.layout.is_horizontal(), |this| this.h_flex()) .when(self.layout.is_vertical(), |this| this.v_flex()) diff --git a/crates/ui/src/tab/tab.rs b/crates/ui/src/tab/tab.rs index d88227db61..6d168194be 100644 --- a/crates/ui/src/tab/tab.rs +++ b/crates/ui/src/tab/tab.rs @@ -394,6 +394,7 @@ pub struct Tab { ix: usize, base: Div, pub(super) label: Option, + aria_label: Option, pub(super) icon: Option, prefix: Option, pub(super) tab_bar_prefix: Option, @@ -444,6 +445,7 @@ impl Default for Tab { ix: 0, base: div(), label: None, + aria_label: None, icon: None, tab_bar_prefix: None, children: Vec::new(), @@ -472,6 +474,16 @@ impl Tab { self } + /// Set the accessible label for the tab. + pub fn aria_label(mut self, label: impl Into) -> Self { + self.aria_label = Some(label.into()); + self + } + + fn a11y_label(&self) -> Option { + self.aria_label.clone().or_else(|| self.label.clone()) + } + /// Set icon for the tab. pub fn icon(mut self, icon: impl Into) -> Self { self.icon = Some(icon.into()); @@ -611,6 +623,7 @@ impl RenderOnce for Tab { let inner_margins = self.variant.inner_margins(self.size); let inner_height = self.variant.inner_height(self.size); let height = self.variant.height(self.size); + let aria_label = self.a11y_label(); let segmented_indicator_active = self.variant == TabVariant::Segmented && self.indicator_active; @@ -629,6 +642,7 @@ impl RenderOnce for Tab { self.base .id(self.ix) .role(Role::Tab) + .when_some(aria_label, |this, label| this.aria_label(label)) .aria_selected(self.selected) .relative() .flex() @@ -731,3 +745,22 @@ impl RenderOnce for Tab { }) } } + +#[cfg(test)] +mod tests { + use super::*; + + #[gpui::test] + fn a11y_label_defaults_to_visible_label(_cx: &mut gpui::TestAppContext) { + let tab = Tab::new().label("Account"); + + assert_eq!(tab.a11y_label(), Some("Account".into())); + } + + #[gpui::test] + fn explicit_a11y_label_overrides_visible_label(_cx: &mut gpui::TestAppContext) { + let tab = Tab::new().label("Acct").aria_label("Account settings"); + + assert_eq!(tab.a11y_label(), Some("Account settings".into())); + } +} From 49f798e87efc8fab7016b7a7e59a46e7488242ed Mon Sep 17 00:00:00 2001 From: Jason Lee Date: Mon, 6 Jul 2026 19:00:05 +0800 Subject: [PATCH 4/5] . --- crates/ui/src/button/dropdown_button.rs | 1 - crates/ui/src/form/field.rs | 1 - crates/ui/src/root.rs | 5 ++--- script/run-story-macos | 4 ++-- 4 files changed, 4 insertions(+), 7 deletions(-) diff --git a/crates/ui/src/button/dropdown_button.rs b/crates/ui/src/button/dropdown_button.rs index 6f02799160..025da99509 100644 --- a/crates/ui/src/button/dropdown_button.rs +++ b/crates/ui/src/button/dropdown_button.rs @@ -159,7 +159,6 @@ impl RenderOnce for DropdownButton { div() .id(self.id) - .role(Role::Group) .h_flex() .refine_style(&self.style) .when_some(self.button, |this, button| { diff --git a/crates/ui/src/form/field.rs b/crates/ui/src/form/field.rs index 309127bef7..014796b4c3 100644 --- a/crates/ui/src/form/field.rs +++ b/crates/ui/src/form/field.rs @@ -281,7 +281,6 @@ impl RenderOnce for Field { // This warp for aligning the Label + Input wrap_div(layout) .id(self.id) - .role(Role::Group) .gap(inner_gap) .when_some(self.align_items, |this, align| { this.map(|this| match align { diff --git a/crates/ui/src/root.rs b/crates/ui/src/root.rs index ca4928665a..425fe1b736 100644 --- a/crates/ui/src/root.rs +++ b/crates/ui/src/root.rs @@ -13,8 +13,8 @@ use crate::{ use gpui::{ Anchor, AnyView, App, AppContext, Bounds, ClipboardItem, Context, DefiniteLength, ElementId, Entity, EntityId, FocusHandle, Hitbox, InteractiveElement, IntoElement, KeyBinding, - ParentElement as _, Pixels, Render, Role, StatefulInteractiveElement as _, StyleRefinement, - Styled, WeakEntity, WeakFocusHandle, Window, actions, div, prelude::FluentBuilder as _, + ParentElement as _, Pixels, Render, StyleRefinement, Styled, WeakEntity, WeakFocusHandle, + Window, actions, div, prelude::FluentBuilder as _, }; use std::{any::TypeId, collections::HashMap, rc::Rc}; @@ -553,7 +553,6 @@ impl Render for Root { let inner = div() .id("root") - .role(Role::Application) .key_context(CONTEXT) .on_action(cx.listener(Self::on_action_tab)) .on_action(cx.listener(Self::on_action_tab_prev)) diff --git a/script/run-story-macos b/script/run-story-macos index 326d51bbf4..284c32cf2f 100755 --- a/script/run-story-macos +++ b/script/run-story-macos @@ -15,10 +15,10 @@ set -euxo pipefail APP=/tmp/GPUIComponentStory.app BIN=gpui-component-story -cargo build -p gpui-component-story --release +cargo build -p gpui-component-story mkdir -p "$APP/Contents/MacOS" -cp "target/release/$BIN" "$APP/Contents/MacOS/" +cp "target/debug/$BIN" "$APP/Contents/MacOS/" cat > "$APP/Contents/Info.plist" <<'PLIST' From a71574ec7513c11a47e98dc8659eb1b0cd397a0b Mon Sep 17 00:00:00 2001 From: Jason Lee Date: Mon, 6 Jul 2026 19:13:27 +0800 Subject: [PATCH 5/5] fix: forward macOS accessibility hit tests Co-authored-by: Codex --- Cargo.lock | 56 ++++++++--------- crates/ui/Cargo.toml | 1 + crates/ui/src/button/dropdown_button.rs | 3 +- crates/ui/src/form/field.rs | 5 +- crates/ui/src/lib.rs | 2 + crates/ui/src/macos_accessibility.rs | 81 +++++++++++++++++++++++++ crates/ui/src/root.rs | 3 + 7 files changed, 118 insertions(+), 33 deletions(-) create mode 100644 crates/ui/src/macos_accessibility.rs diff --git a/Cargo.lock b/Cargo.lock index 8ac3028fe9..13b40505da 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -692,7 +692,7 @@ dependencies = [ "bitflags 2.11.1", "cexpr", "clang-sys", - "itertools 0.13.0", + "itertools 0.11.0", "log", "prettyplease", "proc-macro2", @@ -1196,7 +1196,7 @@ dependencies = [ [[package]] name = "collections" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#159246f0083787124160620118ac35df5f0f3754" +source = "git+https://github.com/zed-industries/zed#c545fb67d0ce13e335bff76f7c08986000333f2c" dependencies = [ "gpui_util", "indexmap", @@ -1752,7 +1752,7 @@ dependencies = [ [[package]] name = "derive_refineable" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#159246f0083787124160620118ac35df5f0f3754" +source = "git+https://github.com/zed-industries/zed#c545fb67d0ce13e335bff76f7c08986000333f2c" dependencies = [ "proc-macro2", "quote", @@ -2847,7 +2847,7 @@ dependencies = [ "log", "presser", "thiserror 2.0.18", - "windows 0.62.2", + "windows 0.58.0", ] [[package]] @@ -2873,7 +2873,7 @@ dependencies = [ [[package]] name = "gpui" version = "0.2.2" -source = "git+https://github.com/zed-industries/zed#159246f0083787124160620118ac35df5f0f3754" +source = "git+https://github.com/zed-industries/zed#c545fb67d0ce13e335bff76f7c08986000333f2c" dependencies = [ "accesskit", "anyhow", @@ -3120,7 +3120,7 @@ dependencies = [ [[package]] name = "gpui_linux" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#159246f0083787124160620118ac35df5f0f3754" +source = "git+https://github.com/zed-industries/zed#c545fb67d0ce13e335bff76f7c08986000333f2c" dependencies = [ "accesskit", "accesskit_unix", @@ -3171,7 +3171,7 @@ dependencies = [ [[package]] name = "gpui_macos" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#159246f0083787124160620118ac35df5f0f3754" +source = "git+https://github.com/zed-industries/zed#c545fb67d0ce13e335bff76f7c08986000333f2c" dependencies = [ "accesskit", "accesskit_macos", @@ -3218,7 +3218,7 @@ dependencies = [ [[package]] name = "gpui_macros" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#159246f0083787124160620118ac35df5f0f3754" +source = "git+https://github.com/zed-industries/zed#c545fb67d0ce13e335bff76f7c08986000333f2c" dependencies = [ "heck 0.5.0", "proc-macro2", @@ -3229,7 +3229,7 @@ dependencies = [ [[package]] name = "gpui_platform" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#159246f0083787124160620118ac35df5f0f3754" +source = "git+https://github.com/zed-industries/zed#c545fb67d0ce13e335bff76f7c08986000333f2c" dependencies = [ "console_error_panic_hook", "gpui", @@ -3242,7 +3242,7 @@ dependencies = [ [[package]] name = "gpui_shared_string" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#159246f0083787124160620118ac35df5f0f3754" +source = "git+https://github.com/zed-industries/zed#c545fb67d0ce13e335bff76f7c08986000333f2c" dependencies = [ "schemars", "serde", @@ -3252,7 +3252,7 @@ dependencies = [ [[package]] name = "gpui_util" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#159246f0083787124160620118ac35df5f0f3754" +source = "git+https://github.com/zed-industries/zed#c545fb67d0ce13e335bff76f7c08986000333f2c" dependencies = [ "anyhow", "log", @@ -3262,7 +3262,7 @@ dependencies = [ [[package]] name = "gpui_web" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#159246f0083787124160620118ac35df5f0f3754" +source = "git+https://github.com/zed-industries/zed#c545fb67d0ce13e335bff76f7c08986000333f2c" dependencies = [ "anyhow", "console_error_panic_hook", @@ -3286,7 +3286,7 @@ dependencies = [ [[package]] name = "gpui_wgpu" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#159246f0083787124160620118ac35df5f0f3754" +source = "git+https://github.com/zed-industries/zed#c545fb67d0ce13e335bff76f7c08986000333f2c" dependencies = [ "anyhow", "bytemuck", @@ -3315,7 +3315,7 @@ dependencies = [ [[package]] name = "gpui_windows" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#159246f0083787124160620118ac35df5f0f3754" +source = "git+https://github.com/zed-industries/zed#c545fb67d0ce13e335bff76f7c08986000333f2c" dependencies = [ "accesskit", "accesskit_windows", @@ -3631,7 +3631,7 @@ dependencies = [ [[package]] name = "http_client" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#159246f0083787124160620118ac35df5f0f3754" +source = "git+https://github.com/zed-industries/zed#c545fb67d0ce13e335bff76f7c08986000333f2c" dependencies = [ "anyhow", "async-compression", @@ -3651,7 +3651,7 @@ dependencies = [ [[package]] name = "http_client_tls" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#159246f0083787124160620118ac35df5f0f3754" +source = "git+https://github.com/zed-industries/zed#c545fb67d0ce13e335bff76f7c08986000333f2c" dependencies = [ "rustls", "rustls-platform-verifier", @@ -3732,7 +3732,7 @@ dependencies = [ "js-sys", "log", "wasm-bindgen", - "windows-core 0.62.2", + "windows-core 0.58.0", ] [[package]] @@ -4673,7 +4673,7 @@ dependencies = [ [[package]] name = "media" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#159246f0083787124160620118ac35df5f0f3754" +source = "git+https://github.com/zed-industries/zed#c545fb67d0ce13e335bff76f7c08986000333f2c" dependencies = [ "anyhow", "bindgen", @@ -5084,7 +5084,7 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "680998035259dcfcafe653688bf2aa6d3e2dc05e98be6ab46afb089dc84f1df8" dependencies = [ - "proc-macro-crate 3.5.0", + "proc-macro-crate 1.3.1", "proc-macro2", "quote", "syn 2.0.117", @@ -5642,7 +5642,7 @@ checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" [[package]] name = "perf" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#159246f0083787124160620118ac35df5f0f3754" +source = "git+https://github.com/zed-industries/zed#c545fb67d0ce13e335bff76f7c08986000333f2c" dependencies = [ "collections", "serde", @@ -6640,7 +6640,7 @@ dependencies = [ [[package]] name = "refineable" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#159246f0083787124160620118ac35df5f0f3754" +source = "git+https://github.com/zed-industries/zed#c545fb67d0ce13e335bff76f7c08986000333f2c" dependencies = [ "derive_refineable", ] @@ -6692,7 +6692,7 @@ checksum = "19b30a45b0cd0bcca8037f3d0dc3421eaf95327a17cad11964fb8179b4fc4832" [[package]] name = "reqwest_client" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#159246f0083787124160620118ac35df5f0f3754" +source = "git+https://github.com/zed-industries/zed#c545fb67d0ce13e335bff76f7c08986000333f2c" dependencies = [ "anyhow", "bytes", @@ -7132,7 +7132,7 @@ dependencies = [ [[package]] name = "scheduler" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#159246f0083787124160620118ac35df5f0f3754" +source = "git+https://github.com/zed-industries/zed#c545fb67d0ce13e335bff76f7c08986000333f2c" dependencies = [ "async-task", "backtrace", @@ -7753,7 +7753,7 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "sum_tree" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#159246f0083787124160620118ac35df5f0f3754" +source = "git+https://github.com/zed-industries/zed#c545fb67d0ce13e335bff76f7c08986000333f2c" dependencies = [ "heapless", "log", @@ -9214,7 +9214,7 @@ checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" [[package]] name = "util_macros" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#159246f0083787124160620118ac35df5f0f3754" +source = "git+https://github.com/zed-industries/zed#c545fb67d0ce13e335bff76f7c08986000333f2c" dependencies = [ "perf", "quote", @@ -11214,7 +11214,7 @@ dependencies = [ [[package]] name = "zlog" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#159246f0083787124160620118ac35df5f0f3754" +source = "git+https://github.com/zed-industries/zed#c545fb67d0ce13e335bff76f7c08986000333f2c" dependencies = [ "anyhow", "chrono", @@ -11231,7 +11231,7 @@ checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" [[package]] name = "ztracing" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#159246f0083787124160620118ac35df5f0f3754" +source = "git+https://github.com/zed-industries/zed#c545fb67d0ce13e335bff76f7c08986000333f2c" dependencies = [ "tracing", "tracing-subscriber", @@ -11242,7 +11242,7 @@ dependencies = [ [[package]] name = "ztracing_macro" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#159246f0083787124160620118ac35df5f0f3754" +source = "git+https://github.com/zed-industries/zed#c545fb67d0ce13e335bff76f7c08986000333f2c" [[package]] name = "zune-core" diff --git a/crates/ui/Cargo.toml b/crates/ui/Cargo.toml index c0dcc418e9..a9d4ca51c3 100644 --- a/crates/ui/Cargo.toml +++ b/crates/ui/Cargo.toml @@ -193,6 +193,7 @@ objc2-app-kit = { version = "0.3", features = [ "NSMenu", "NSMenuItem", "NSView", + "NSWindow", "NSResponder", "NSEvent", "NSImage", diff --git a/crates/ui/src/button/dropdown_button.rs b/crates/ui/src/button/dropdown_button.rs index 025da99509..035366f27a 100644 --- a/crates/ui/src/button/dropdown_button.rs +++ b/crates/ui/src/button/dropdown_button.rs @@ -1,8 +1,7 @@ use gpui::Corners; use gpui::{ Anchor, App, Context, Edges, ElementId, InteractiveElement as _, IntoElement, ParentElement, - RenderOnce, Role, SharedString, StatefulInteractiveElement as _, StyleRefinement, Styled, - Window, div, prelude::FluentBuilder, + RenderOnce, SharedString, StyleRefinement, Styled, Window, div, prelude::FluentBuilder, }; use crate::{ diff --git a/crates/ui/src/form/field.rs b/crates/ui/src/form/field.rs index 014796b4c3..838d212f64 100644 --- a/crates/ui/src/form/field.rs +++ b/crates/ui/src/form/field.rs @@ -2,9 +2,8 @@ use std::rc::Rc; use gpui::{ AlignItems, AnyElement, AnyView, App, Axis, Div, Element, ElementId, InteractiveElement as _, - IntoElement, ParentElement, Pixels, Rems, RenderOnce, Role, SharedString, - StatefulInteractiveElement as _, StyleRefinement, Styled, Window, div, - prelude::FluentBuilder as _, px, + IntoElement, ParentElement, Pixels, Rems, RenderOnce, SharedString, StyleRefinement, Styled, + Window, div, prelude::FluentBuilder as _, px, }; use crate::{ActiveTheme as _, AxisExt, Size, StyledExt, h_flex, v_flex}; diff --git a/crates/ui/src/lib.rs b/crates/ui/src/lib.rs index a6fe7f6e99..6c79bf3b13 100644 --- a/crates/ui/src/lib.rs +++ b/crates/ui/src/lib.rs @@ -11,6 +11,8 @@ mod icon; mod index_path; #[cfg(any(feature = "inspector", debug_assertions))] mod inspector; +#[cfg(target_os = "macos")] +mod macos_accessibility; mod root; mod styled; mod time; diff --git a/crates/ui/src/macos_accessibility.rs b/crates/ui/src/macos_accessibility.rs new file mode 100644 index 0000000000..4df64bae30 --- /dev/null +++ b/crates/ui/src/macos_accessibility.rs @@ -0,0 +1,81 @@ +use std::{ffi::CString, ptr::null_mut}; + +use gpui::Window; +use objc2::{ + Message, + encode::{Encode, EncodeArguments, EncodeReturn, Encoding}, + ffi::{class_addMethod, object_getClass}, + msg_send, + runtime::{AnyClass, AnyObject, MethodImplementation, Sel}, + sel, +}; +use objc2_app_kit::{NSView, NSWindow}; +use objc2_foundation::NSPoint; +use raw_window_handle::{HasWindowHandle, RawWindowHandle}; + +pub(crate) fn install_window_hit_test_forwarder(window: &Window) { + let Some(view) = ns_view(window) else { + return; + }; + let Some(window) = view.window() else { + return; + }; + + unsafe { + let class = object_getClass((&*window as *const NSWindow).cast::()); + if !class.is_null() { + add_method( + class.cast_mut(), + sel!(accessibilityHitTest:), + hit_test_forwarder as extern "C" fn(_, _, _) -> _, + ); + } + } +} + +extern "C" fn hit_test_forwarder(this: &NSWindow, _cmd: Sel, point: NSPoint) -> *mut AnyObject { + this.contentView().map_or_else(null_mut, |view| unsafe { + msg_send![&*view, accessibilityHitTest: point] + }) +} + +fn ns_view(window: &Window) -> Option<&NSView> { + let handle = HasWindowHandle::window_handle(window).ok()?; + let RawWindowHandle::AppKit(handle) = handle.as_raw() else { + return None; + }; + unsafe { (handle.ns_view.as_ptr() as *const NSView).as_ref() } +} + +unsafe fn add_method(class: *mut AnyClass, sel: Sel, func: F) +where + T: Message + ?Sized, + F: MethodImplementation, +{ + let encs = F::Arguments::ENCODINGS; + debug_assert_eq!( + count_args(sel), + encs.len(), + "selector {sel:?} argument count does not match method implementation" + ); + + let types = method_type_encoding(&F::Return::ENCODING_RETURN, encs); + let success = unsafe { class_addMethod(class, sel, func.__imp(), types.as_ptr()) }; + + // A false result usually means the class already has this method, for example + // after GPUI grows its own hit-test forwarder. Keep the shim non-invasive. + let _ = success.as_bool(); +} + +fn count_args(sel: Sel) -> usize { + sel.name().to_bytes().iter().filter(|&&c| c == b':').count() +} + +fn method_type_encoding(ret: &Encoding, args: &[Encoding]) -> CString { + let mut types = format!("{ret}{}{}", <*mut AnyObject>::ENCODING, Sel::ENCODING); + for enc in args { + use core::fmt::Write; + write!(&mut types, "{enc}").unwrap(); + } + CString::new(types).unwrap() +} diff --git a/crates/ui/src/root.rs b/crates/ui/src/root.rs index 425fe1b736..3e8ae32e50 100644 --- a/crates/ui/src/root.rs +++ b/crates/ui/src/root.rs @@ -94,6 +94,9 @@ impl ActiveDialog { impl Root { /// Create a new Root view. pub fn new(view: impl Into, window: &mut Window, cx: &mut Context) -> Self { + #[cfg(target_os = "macos")] + crate::macos_accessibility::install_window_hit_test_forwarder(window); + Self { style: StyleRefinement::default(), view: view.into(),