tawasuyu

tawasuyu/brahman

Rust 582TOML 222Markdown 73JSON 198630 Definiciones

feat(yahweh-widget-text-input): focus-aware border + caret sólo on focus

Iter 7 (mini-iter — el text-input ya estaba themed, faltaba el
polish de focus visibility). Antes el border era siempre
accent_strong y el caret | siempre presente — imposible distinguir
cuál input está activo en un form con varios fields.

- Border focus-aware: focused → accent_strong; blur → border (slot
  tenue del theme).
- Caret | sólo on focus; sin focus se muestra texto plano (reduce
  ruido visual).
- render usa window.is_focused(focus_handle) para chequear.

Sin cambios en API pública. Tests downstream verdes.

Limitación: caret estático (no parpadea). Iter futura si emerge
la necesidad de animation timer via cx.spawn.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

fc4da751ca68c21d75cd248477f3a9133cf5b146 · por Sergio · (hace 5 meses) · padre 8fdef818c · ver el árbol en este commit

Delta semántico · 1

crates/modules/ui_engine/widgets/text_input/src/lib.rs

2 archivos · +54 −4

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 7648706..09d790a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,6 +6,42 @@ ratio/diff ver `git show <sha>`.
 
 ## 2026-05-10
 
+### feat(yahweh-widget-text-input): focus-aware border + caret sólo on focus
+Iter 7 (mini-iter — el text-input ya estaba themed, faltaba sólo
+el polish de focus visibility). Antes el border era siempre
+`accent_strong` y el caret `|` siempre estaba presente — imposible
+distinguir cuál input está activo en un form con varios fields.
+
+Cambios en `yahweh-widget-text-input`:
+- **Border focus-aware**: cuando el input está focused, border =
+  `theme.accent_strong` (color vivo). Cuando no, border =
+  `theme.border` (color tenue del chrome). Se obtiene via
+  `self.focus_handle.is_focused(window)`.
+- **Caret `|` sólo on focus**: cuando el input no tiene focus, se
+  muestra el texto plano sin caret. Reduce el "ruido visual" en
+  forms con muchos fields.
+- `render` ahora usa el `Window` arg (antes `_w`) para chequear
+  focus.
+
+Sin cambios en API pública — todo es interno al `render`. El
+binario no requiere migración.
+
+Tests: sin cambios (los tests del crate son struct constructors,
+no rendering). Tests downstream del widget (`yahweh-widget-meta-form`,
+`nakui-ui`) siguen verdes — el cambio es backward compatible.
+
+Beneficio operativo:
+- Forms con 5+ fields ahora son navegables: el usuario ve cuál
+  input recibe sus teclas via el border highlighted.
+- Cambio de theme afecta también a inputs (ya estaban themed; ahora
+  además respetan el `accent_strong` específico del preset
+  cuando focused, vs el `border` cuando no).
+
+Limitación pendiente: el caret `|` literal no parpadea (no hay
+animation timer). Cuando emerja la necesidad, agregar via
+`cx.spawn` con un loop de toggle. Por ahora el caret estático on
+focus es suficiente signal.
+
 ### feat(yahweh-widget-theme-switcher): control para ciclar themes en runtime
 Iter 6. Cierra el ciclo del theme: ya teníamos paleta themed +
 widgets que la consumen, faltaba el control UI para rotar entre
diff --git a/crates/modules/ui_engine/widgets/text_input/src/lib.rs b/crates/modules/ui_engine/widgets/text_input/src/lib.rs
index 638b40c..806f47c 100644
--- a/crates/modules/ui_engine/widgets/text_input/src/lib.rs
+++ b/crates/modules/ui_engine/widgets/text_input/src/lib.rs
@@ -122,14 +122,28 @@ impl TextInput {
 }
 
 impl Render for TextInput {
-    fn render(&mut self, _w: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
+    fn render(&mut self, w: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
         let theme = Theme::global(cx).clone();
         let is_empty = self.text.is_empty();
+        // Border-color depende del focus: focused → accent (señal
+        // clara de "vas a tipear acá"); blur → border (silencioso).
+        // Sin esto era imposible saber qué input estaba activo en
+        // un form con varios fields.
+        let is_focused = self.focus_handle.is_focused(w);
+        let border_color = if is_focused {
+            theme.accent_strong
+        } else {
+            theme.border
+        };
         let display: SharedString = if is_empty {
             self.placeholder.clone()
-        } else {
-            // Cursor siempre al final — sin movimiento de cursor.
+        } else if is_focused {
+            // Caret al final, sólo cuando el input tiene focus —
+            // así el usuario ve dónde va a aparecer el siguiente
+            // char. Inputs sin focus no muestran caret (es ruido).
             SharedString::from(format!("{}|", self.text))
+        } else {
+            SharedString::from(self.text.clone())
         };
         let text_color = if is_empty {
             theme.fg_disabled
@@ -147,7 +161,7 @@ impl Render for TextInput {
             .min_w(px(200.0))
             .bg(theme.bg_panel.clone())
             .border_1()
-            .border_color(theme.accent_strong)
+            .border_color(border_color)
             .rounded(px(4.0))
             .text_size(px(13.0))
             .text_color(text_color)