From 718617939e1c4f938f21751f1fdbfe2ec230188f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9B=B7=E7=94=B5=E8=8A=BD=E8=A1=A3?= Date: Mon, 5 Oct 2026 12:34:10 -0400 Subject: [PATCH] =?UTF-8?q?Forms=20=C2=B7=20NumberInput=20label:=20the=20n?= =?UTF-8?q?ame=20assistive=20technology=20reads,=20drawn=20nowhere?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- AGENTS.md | 1 + src/forms/number.rs | 12 ++++++++++++ src/forms/text/mod.rs | 9 +++++++++ 3 files changed, 22 insertions(+) diff --git a/AGENTS.md b/AGENTS.md index 25b37ed..c17caf9 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -262,3 +262,4 @@ Ely GPUI Component. A component library for GPUI, in light and dark. - 2026-10-04: chapter 44, Rendering, holds `RenderSurface` alone. gpui at 1a28cff paints `RenderImage`s everywhere, `CVPixelBuffer`s (NV12) on macOS's Metal renderer alone, and shares no device or shader hook, so shader, camera and GPU views are the host's, feeding the surface, and zero-copy or Metal, OpenGL, Vulkan and Direct3D surfaces are blocked (`tasks/ch44.md`). CI tests the crate and builds the gallery on Linux, Windows and macOS, arm64 and x86_64. - 2026-10-05: an overlay its owner removes without calling `Close` still hands focus back: `take_focus` records each holder and what it took focus from, `give_back` forgets it, and when the focused element leaves the tree the root scope walks back past every gone holder to the focus before them all, else takes it; nested overlays removed together return to what the outer one took focus from. Before, a dialog whose action set its owner's state to closed left focus on the root, and keys reached nothing until a click. - 2026-10-05: `Select::label` and `TextInput::label` name the control for assistive technology without drawing anything; a text input with neither label nor placeholder had no name, and a select read only its value, so a field named by a description list beside it was anonymous in the AX tree. +- 2026-10-05: `NumberInput::label` names the field for assistive technology without drawing it, through `TextInput::set_label` on the field's inner input, which lives across frames; a number field named only by a description list beside it was anonymous in the AX tree. diff --git a/src/forms/number.rs b/src/forms/number.rs index f51bd29..48ded53 100644 --- a/src/forms/number.rs +++ b/src/forms/number.rs @@ -224,6 +224,7 @@ pub struct NumberInput { on_change: Option, on_commit: Option, scrub_label: Option, + label: SharedString, } impl NumberInput { @@ -241,9 +242,16 @@ impl NumberInput { on_change: None, on_commit: None, scrub_label: None, + label: SharedString::default(), } } + /// The name assistive technology reads, drawn nowhere. + pub fn label(mut self, text: impl Into) -> Self { + self.label = text.into(); + self + } + pub fn range(mut self, min: f64, max: f64) -> Self { assert!(min <= max, "number range {min}..{max} is empty"); self.min = min; @@ -316,6 +324,10 @@ impl RenderOnce for NumberInput { numeric.limits = (min, max, step, self.precision); }); let input = state.read(cx).input.clone(); + if input.read(cx).label_text() != &self.label { + let label = self.label.clone(); + input.update(cx, |input, cx| input.set_label(label, cx)); + } let shown = format::number(value, self.precision, Separators::EN); let editing = input.read(cx).focus().is_focused(window); if !editing && input.read(cx).text() != shown { diff --git a/src/forms/text/mod.rs b/src/forms/text/mod.rs index ee3dde7..b16247b 100644 --- a/src/forms/text/mod.rs +++ b/src/forms/text/mod.rs @@ -204,6 +204,10 @@ impl TextInput { &self.placeholder } + pub fn label_text(&self) -> &SharedString { + &self.label + } + pub fn selection(&self) -> Range { self.selection.clone() } @@ -221,6 +225,11 @@ impl TextInput { cx.notify(); } + pub fn set_label(&mut self, text: impl Into, cx: &mut Context) { + self.label = text.into(); + cx.notify(); + } + pub fn set_masked(&mut self, masked: bool, cx: &mut Context) { self.masked = masked; cx.notify();