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>
This commit is contained in:
@@ -6,6 +6,42 @@ ratio/diff ver `git show <sha>`.
|
|||||||
|
|
||||||
## 2026-05-10
|
## 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
|
### feat(yahweh-widget-theme-switcher): control para ciclar themes en runtime
|
||||||
Iter 6. Cierra el ciclo del theme: ya teníamos paleta themed +
|
Iter 6. Cierra el ciclo del theme: ya teníamos paleta themed +
|
||||||
widgets que la consumen, faltaba el control UI para rotar entre
|
widgets que la consumen, faltaba el control UI para rotar entre
|
||||||
|
|||||||
@@ -122,14 +122,28 @@ impl TextInput {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Render for 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 theme = Theme::global(cx).clone();
|
||||||
let is_empty = self.text.is_empty();
|
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 {
|
let display: SharedString = if is_empty {
|
||||||
self.placeholder.clone()
|
self.placeholder.clone()
|
||||||
} else {
|
} else if is_focused {
|
||||||
// Cursor siempre al final — sin movimiento de cursor.
|
// 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))
|
SharedString::from(format!("{}|", self.text))
|
||||||
|
} else {
|
||||||
|
SharedString::from(self.text.clone())
|
||||||
};
|
};
|
||||||
let text_color = if is_empty {
|
let text_color = if is_empty {
|
||||||
theme.fg_disabled
|
theme.fg_disabled
|
||||||
@@ -147,7 +161,7 @@ impl Render for TextInput {
|
|||||||
.min_w(px(200.0))
|
.min_w(px(200.0))
|
||||||
.bg(theme.bg_panel.clone())
|
.bg(theme.bg_panel.clone())
|
||||||
.border_1()
|
.border_1()
|
||||||
.border_color(theme.accent_strong)
|
.border_color(border_color)
|
||||||
.rounded(px(4.0))
|
.rounded(px(4.0))
|
||||||
.text_size(px(13.0))
|
.text_size(px(13.0))
|
||||||
.text_color(text_color)
|
.text_color(text_color)
|
||||||
|
|||||||
Reference in New Issue
Block a user