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>
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)