Accessibility: a set value can reach a field's number past the rail

The range screen readers get for a dialog slider takes in a field's number
past the rail's end, and the set-value action now accepts that range too.
This commit is contained in:
dotCooCoo
2026-11-04 21:37:57 +07:00
committed by bflatastic
parent 0417367532
commit d1e69239a7
2 changed files with 31 additions and 6 deletions
+25 -2
View File
@@ -2018,9 +2018,10 @@ fn the_blob_brush_tip_preview_is_a_named_slider_with_arrow_keys() {
/// Screen readers can set the Blob Brush tip preview's Angle to a number: the slider offers the
/// set-value action, a fraction rounds to the whole degrees the field shows, a number past -180° to
/// 180° sets the nearer end, the last request in a frame wins, and a value that is not a finite
/// number leaves the Angle as it is.
/// number leaves the Angle as it is. On a field past its rail's end, a set value can go as far as
/// the field's number.
#[test]
fn screen_readers_set_the_blob_brush_angle() {
fn screen_readers_set_a_dialog_slider_number() {
use egui::accesskit::{Action, ActionData, ActionRequest, TreeId};
let mut app = app();
app.run("tool.options", json!({"tool": "blobBrush"})).unwrap();
@@ -2055,6 +2056,28 @@ fn screen_readers_set_the_blob_brush_angle() {
assert_eq!(angle(&app), -90.0, "not a number");
f.run(&mut app, vec![set(30.0), set(f64::INFINITY)]);
assert_eq!(angle(&app), -90.0, "the last request is not a finite number");
// A field past its rail's end extends the range screen readers get, and a set value can go
// that far: Transform Each's Horizontal Scale at 300% on a rail that ends at 200%.
app.ui.dialog = None;
app.run("shape.rectangle", json!({"x": 10, "y": 10, "width": 50, "height": 50})).unwrap();
crate::menus::invoke(&mut app, "object.transformEach", json!({}));
app.ui.dialog.as_mut().expect("Transform Each is open").fields.insert("scaleH".into(), json!(300));
let scale = |app: &VectorcraftApp| app.ui.dialog.as_ref().expect("Transform Each is open").f64("scaleH", f64::NAN);
f.run(&mut app, vec![]);
let out = f.run(&mut app, vec![]);
let (node, ..) = slider_node(&out, "Horizontal:");
let set = |value: f64| {
egui::Event::AccessKitActionRequest(ActionRequest {
action: Action::SetValue,
target_node: node,
target_tree: TreeId::ROOT,
data: Some(ActionData::NumericValue(value)),
})
};
f.run(&mut app, vec![set(250.0)]);
assert_eq!(scale(&app), 250.0, "within the range screen readers get");
f.run(&mut app, vec![set(400.0)]);
assert_eq!(scale(&app), 250.0, "past 250%, the range's end now");
}
#[test]
+6 -4
View File
@@ -123,8 +123,9 @@ pub fn color_slider_valued(
/// and ↓ lower it to the next whole number, Shift with an arrow, Page Up and Page Down add or take
/// ten, and Home and End set the start and the end of `shown`'s range. Screen readers' increment
/// and decrement actions step the number as → and ← do, and their set-value action sets it within
/// the range, rounded to [`SliderValue::decimals`] (a value that is not a finite number leaves it
/// as it is). A number past the range steps from the range's nearer end, and no step goes past
/// the range they get (the range, extended to a number past its end), rounded to
/// [`SliderValue::decimals`] (a value that is not a finite number leaves it as it is). A number
/// past the range steps from the range's nearer end, and no step goes past
/// either end. With `shown` `None` (a blank field), or
/// in a disabled `ui`, the keys and actions leave the rail as it is. Returns the new value from 0
/// to 1 that a click or a drag gives, its phase, and the number in `shown`'s units that a key or
@@ -247,7 +248,7 @@ fn rail_step(ui: &Ui, id: egui::Id, focused: bool, s: &SliderValue) -> Option<f6
ui.input(|i| {
let n = i.num_accesskit_action_requests(id, Action::Increment) as f64 - i.num_accesskit_action_requests(id, Action::Decrement) as f64;
v = by(v, n).clamp(lo, hi);
// The last set-value request this frame, within the range, which also replaces a number past it.
// The last set-value request this frame.
let set = i
.accesskit_action_requests(id, Action::SetValue)
.filter_map(|request| match request.data {
@@ -259,7 +260,8 @@ fn rail_step(ui: &Ui, id: egui::Id, focused: bool, s: &SliderValue) -> Option<f6
if let Some(value) = set {
// The number the field would show, as `SliderValue::field` reads it.
let shown = s.decimals.map_or(value, |d| field_text(value, "", d).parse().unwrap_or(value));
v = shown.clamp(lo, hi);
// The range screen readers get (`slider_info`), which takes in a number past the rail.
v = shown.clamp(lo.min(s.value), hi.max(s.value));
end = true;
exact = true;
}