Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 13 additions & 2 deletions crates/base/src/slider.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ use gpui::{
AccessibleAction, Along, AnyElement, App, AppContext as _, Axis, Bounds, Context, Div,
DragMoveEvent, Empty, Entity, EntityId, EventEmitter, HitboxBehavior, InteractiveElement,
IntoElement, MouseButton, MouseDownEvent, Orientation, ParentElement, Pixels, Point, Render,
RenderOnce, Role, StatefulInteractiveElement, StyleRefinement, Styled, TouchDragEvent,
TouchPhase, Window, div, prelude::FluentBuilder as _, px,
RenderOnce, Role, SharedString, StatefulInteractiveElement, StyleRefinement, Styled,
TouchDragEvent, TouchPhase, Window, div, prelude::FluentBuilder as _, px,
};

use crate::{element_ext::ElementExt, geometry::AxisExt};
Expand Down Expand Up @@ -443,6 +443,7 @@ pub struct Slider {
state: Entity<SliderState>,
axis: Axis,
disabled: bool,
accessibility_label: Option<SharedString>,
base: Div,
children: Vec<AnyElement>,
}
Expand All @@ -453,6 +454,7 @@ impl Slider {
state: state.clone(),
axis: Axis::Horizontal,
disabled: false,
accessibility_label: None,
base: div(),
children: Vec::new(),
}
Expand All @@ -477,6 +479,12 @@ impl Slider {
self.disabled = disabled;
self
}

/// Sets the label exposed to accessibility clients.
pub fn accessibility_label(mut self, label: impl Into<SharedString>) -> Self {
self.accessibility_label = Some(label.into());
self
}
}

impl ParentElement for Slider {
Expand All @@ -502,6 +510,9 @@ impl RenderOnce for Slider {
.id(("slider", entity_id))
.test_support()
.role(Role::Slider)
.when_some(self.accessibility_label, |this, label| {
this.aria_label(label)
})
.aria_numeric_value(state.value().end() as f64)
.aria_min_numeric_value(state.min_value() as f64)
.aria_max_numeric_value(state.max_value() as f64)
Expand Down
13 changes: 12 additions & 1 deletion crates/component/src/slider.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use gpui_base::{Slider as BaseSlider, SliderIndicator, SliderThumb, SliderTrack,
use gpui::{
App, Axis, Background, Corners, DefiniteLength, ElementId, Entity, EntityId, Hsla,
InteractiveElement as _, IntoElement, MouseButton, ParentElement as _, Pixels, RenderOnce,
StatefulInteractiveElement as _, StyleRefinement, Styled, Window, div,
SharedString, StatefulInteractiveElement as _, StyleRefinement, Styled, Window, div,
prelude::FluentBuilder as _, px, relative,
};

Expand Down Expand Up @@ -88,6 +88,7 @@ pub struct Slider {
axis: Axis,
style: StyleRefinement,
disabled: bool,
accessibility_label: Option<SharedString>,
reverse: bool,
}

Expand All @@ -99,6 +100,7 @@ impl Slider {
state: state.clone(),
style: StyleRefinement::default(),
disabled: false,
accessibility_label: None,
reverse: false,
}
}
Expand All @@ -121,6 +123,12 @@ impl Slider {
self
}

/// Sets the label exposed to accessibility clients.
pub fn accessibility_label(mut self, label: impl Into<SharedString>) -> Self {
self.accessibility_label = Some(label.into());
self
}

/// Reverse the filled (highlighted) side of the track, default: false.
///
/// By default the track is filled from the min end to the thumb. With
Expand Down Expand Up @@ -261,6 +269,9 @@ impl RenderOnce for Slider {
BaseSlider::new(&self.state)
.axis(axis)
.disabled(self.disabled)
.when_some(self.accessibility_label, |this, label| {
this.accessibility_label(label)
})
.flex()
.flex_1()
.items_center()
Expand Down
76 changes: 73 additions & 3 deletions crates/kit/tests/integration/slider_steps.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
use crate::common;

use gpui_kit::base::Slider as BaseSlider;
use gpui_kit::component::slider::{Slider, SliderEvent, SliderState, SliderValue};
use gpui_kit::test::{TestEventExt, TestWindowExt};
use gpui_kit::test::{TestEventExt, TestQueryExt, TestWindowExt};
use gpui_kit::{
AppContext, Context, Entity, MouseButton, Subscription, TestAppContext, Window, div, point,
prelude::*, px, size,
AppContext, Context, Entity, MouseButton, Role, Subscription, TestAppContext, Window, div,
point, prelude::*, px, size,
};
use std::{cell::RefCell, rc::Rc};

Expand Down Expand Up @@ -153,3 +154,72 @@ fn disabled_slider_gesture_neither_changes_value_nor_emits_events(cx: &mut TestA
cx.run_until_parked();
assert!(events.borrow().is_empty());
}

#[gpui_kit::test]
fn slider_accessibility_labels_follow_the_existing_nodes(cx: &mut TestAppContext) {
struct LabeledSliders {
base: Entity<SliderState>,
component: Entity<SliderState>,
labels: Option<(&'static str, &'static str)>,
}

impl Render for LabeledSliders {
fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
div()
.flex()
.flex_col()
.p_8()
.w(px(264.))
.child(
BaseSlider::new(&self.base)
.w_full()
.h(px(24.))
.when_some(self.labels, |slider, (label, _)| {
slider.accessibility_label(label)
}),
)
.child(
Slider::new(&self.component).when_some(self.labels, |slider, (_, label)| {
slider.accessibility_label(label)
}),
)
}
}

cx.update(gpui_kit::init);
let base = cx.new(|_| SliderState::new().default_value(20.));
let component = cx.new(|_| SliderState::new().default_value(40.));
let (handle, content) = common::open_window(cx, Some(size(px(400.), px(240.))), |_, cx| {
cx.new(|_| LabeledSliders {
base: base.clone(),
component: component.clone(),
labels: None,
})
});

for labels in [
None,
Some(("Corner radius", "Padding scale")),
Some(("圆角", "内边距")),
None,
] {
common::update_content(handle, &content, cx, |view, _, cx| {
view.labels = labels;
cx.notify();
})
.unwrap();
cx.update_window(handle.into(), |_, window, cx| {
window.render_frame(cx);
assert_eq!(window.find_all_by_role(Role::Slider).len(), 2);
for (state, label) in [
(&base, labels.map(|labels| labels.0)),
(&component, labels.map(|labels| labels.1)),
] {
let slider = window.find(("slider", state.entity_id()));
assert_eq!(slider.role(), Some(Role::Slider));
assert_eq!(slider.label(), label);
}
})
.unwrap();
}
}
7 changes: 6 additions & 1 deletion website/base/primitives/slider.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,12 @@ The command above supplies application initialization, window creation, and shar

## Accessibility

Expose label, current value, and bounds; support keyboard increments.
Slider exposes its value, bounds, step and orientation, and handles accessible Increment and Decrement. Name it with `.accessibility_label(...)`; this does not add a visible label.

```rust
Slider::new(&slider_state)
.accessibility_label("Corner radius")
```

## Notes

Expand Down
9 changes: 9 additions & 0 deletions website/component/slider.md
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,15 @@ Slider::new(&slider_state)
.disabled(true)
```

### Accessibility

Use `.accessibility_label(...)` to name the existing Slider accessibility node. This does not add a visible label.

```rust
Slider::new(&slider_state)
.accessibility_label("Corner radius")
```

### Custom Styling

```rust
Expand Down
2 changes: 1 addition & 1 deletion website/docs/accessibility.md
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@ The following controls expose useful semantics from their actual state:
| Input | Text input role chosen from its content type, name, non-sensitive value, and an accessible `SetValue` action when not disabled. Masked and password values are withheld. |
| Select | ComboBox role, name, committed value, expanded state, and an accessible activation path. |
| Tab, tab list | Tab and TabList roles; tabs report selection and, when supplied, position in the set. |
| Slider, Progress | Numeric value and range; Slider handles accessible Increment and Decrement. Indeterminate Progress omits its value. |
| Slider, Progress | Name, numeric value and range; Slider handles accessible Increment and Decrement. Indeterminate Progress omits its value. |
| Table | Table, row, header and cell roles with indices and optional counts. Name the Table root. |

For a labeled button, the visible label is the accessible name by default. Name an icon-only button explicitly:
Expand Down
7 changes: 6 additions & 1 deletion website/zh-CN/base/primitives/slider.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,12 @@ SliderState 持久保存值和范围,拖动及键盘操作更新它。

## 可访问性

暴露范围、当前值和方向,并保留方向键、Page Up/Down 等键盘操作。
Slider 暴露当前值、范围、步长和方向,并处理无障碍 Increment、Decrement 操作。用 `.accessibility_label(...)` 设置名称;这不会添加可见标签。

```rust
Slider::new(&slider_state)
.accessibility_label("Corner radius")
```

## 注意事项

Expand Down
9 changes: 9 additions & 0 deletions website/zh-CN/component/slider.md
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,15 @@ Slider::new(&slider_state)
.disabled(true)
```

### 可访问性

使用 `.accessibility_label(...)` 为现有 Slider 无障碍节点设置名称。这不会添加可见标签。

```rust
Slider::new(&slider_state)
.accessibility_label("Corner radius")
```

### 自定义样式

```rust
Expand Down
2 changes: 1 addition & 1 deletion website/zh-CN/docs/accessibility.md
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@ fn main() {
| Input | 根据内容类型选择的输入 role、名称、非敏感值;未禁用时提供 `SetValue` 操作。遮罩和密码值不会暴露。 |
| Select | ComboBox role、名称、已提交的值、展开状态及无障碍激活路径。 |
| Tab、tab list | Tab 与 TabList role;Tab 报告选中状态,也可报告它在集合中的位置。 |
| Slider、Progress | 数值和范围;Slider 处理无障碍 Increment、Decrement 操作。不确定进度的 Progress 不报告数值。 |
| Slider、Progress | 名称、数值和范围;Slider 处理无障碍 Increment、Decrement 操作。不确定进度的 Progress 不报告数值。 |
| Table | Table、row、header、cell role、索引及可选的计数。应给 Table 根节点命名。 |

带文字的按钮默认用可见文字作为无障碍名称。纯图标按钮应显式命名:
Expand Down
Loading