From bf4c635e598729b154a45064e29780f55fc9894c Mon Sep 17 00:00:00 2001 From: Manuel Cillero Date: Mon, 17 Aug 2026 05:28:12 +0200 Subject: [PATCH] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20(pagetop):=20Sustituye=20`?= =?UTF-8?q?Attr`=20por=20`Option`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `Attr` no aportaba nada sobre `Option` directo: `Getters` ya trata los campos `Option` como caso especial (`Option<&T>`, o `Option` con `#[getters(copy)]`) y `#[builder_fn]` funciona igual sobre ellos. --- .../src/theme/bs/form/input.rs | 14 +- .../src/theme/bs/form/select.rs | 10 +- .../src/theme/bs/form/textarea.rs | 12 +- src/base/component/button.rs | 4 +- src/base/component/form/check.rs | 6 +- src/base/component/form/checkbox.rs | 4 +- src/base/component/form/component.rs | 2 +- src/base/component/form/hidden.rs | 4 +- src/base/component/form/input.rs | 46 +++--- src/base/component/form/number.rs | 40 +++--- src/base/component/form/radio.rs | 6 +- src/base/component/form/range.rs | 40 +++--- src/base/component/form/select.rs | 28 ++-- src/base/component/form/textarea.rs | 44 +++--- src/base/component/intro.rs | 4 +- src/base/component/pager.rs | 4 +- src/base/component/table/column.rs | 2 +- src/base/component/table/props.rs | 2 +- src/core/component/children.rs | 8 +- src/html.rs | 2 +- src/html/attr.rs | 134 +++--------------- 21 files changed, 171 insertions(+), 245 deletions(-) diff --git a/extensions/pagetop-bootsier/src/theme/bs/form/input.rs b/extensions/pagetop-bootsier/src/theme/bs/form/input.rs index c5451ea0..6de0698e 100644 --- a/extensions/pagetop-bootsier/src/theme/bs/form/input.rs +++ b/extensions/pagetop-bootsier/src/theme/bs/form/input.rs @@ -63,9 +63,9 @@ pub(crate) fn render(field: &Field, cx: &mut Context) -> Result Result Result Result { option - value=(opt.value().as_str().unwrap_or("")) + value=(opt.value().as_deref().unwrap_or("")) selected[*opt.selected()] disabled[*opt.disabled()] { @@ -111,7 +111,7 @@ pub(crate) fn render(field: &Field, cx: &mut Context) -> Result Result Result { // En `setup()` se garantiza que `name` e `id` están definidos antes del renderizado. - let name = self.name().get().unwrap(); + let name = self.name().as_deref().unwrap(); let container_id = self.id().unwrap(); Ok(html! { @@ -162,8 +162,8 @@ impl Component for Field { type="checkbox" id=(&item_id) class="form-check-input" - name=(&name) - value=[item.value().get()] + name=(name) + value=[item.value().as_deref()] checked[*item.checked()] disabled[*item.disabled() || *self.disabled()]; label class="form-check-label" for=(&item_id) { diff --git a/src/base/component/form/checkbox.rs b/src/base/component/form/checkbox.rs index 4d1378b3..3f173394 100644 --- a/src/base/component/form/checkbox.rs +++ b/src/base/component/form/checkbox.rs @@ -98,7 +98,7 @@ impl Component for Checkbox { async fn prepare(&self, cx: &mut Context) -> Result { // En `setup()` se garantiza que `name` e `id` están definidos antes del renderizado. - let name = self.name().get().unwrap(); + let name = self.name().as_deref().unwrap(); let container_id = self.id().unwrap(); let checkbox_id = util::join!(&container_id, "-checkbox"); @@ -111,7 +111,7 @@ impl Component for Checkbox { role=[is_switch.then_some("switch")] id=(&checkbox_id) class="form-check-input" - name=(&name) + name=(name) value="true" checked[*self.checked()] autofocus[*self.autofocus()] diff --git a/src/base/component/form/component.rs b/src/base/component/form/component.rs index bad71b8c..ea5082ec 100644 --- a/src/base/component/form/component.rs +++ b/src/base/component/form/component.rs @@ -81,7 +81,7 @@ impl Component for Form { (self.props()) action=[self.action().try_resolve(cx)] method=[method] - accept-charset=[self.charset().get()] + accept-charset=[self.charset().as_deref()] { (self.children().render(cx).await) } diff --git a/src/base/component/form/hidden.rs b/src/base/component/form/hidden.rs index 5a63f734..f5cb4384 100644 --- a/src/base/component/form/hidden.rs +++ b/src/base/component/form/hidden.rs @@ -48,8 +48,8 @@ impl Component for Hidden { Ok(html! { input type="hidden" - name=[self.name().get()] - value=[self.value().get()]; + name=[self.name().as_deref()] + value=[self.value().as_deref()]; }) } } diff --git a/src/base/component/form/input.rs b/src/base/component/form/input.rs index d17dcb01..da15942b 100644 --- a/src/base/component/form/input.rs +++ b/src/base/component/form/input.rs @@ -165,13 +165,15 @@ pub struct Field { /// Devuelve el texto de ayuda del campo. help_text: Lc, /// Devuelve la longitud mínima permitida en caracteres. - minlength: Attr, + #[getters(copy)] + minlength: Option, /// Devuelve la longitud máxima permitida en caracteres. - maxlength: Attr, + #[getters(copy)] + maxlength: Option, /// Devuelve el texto indicativo del campo. placeholder: Lc, /// Devuelve la configuración de autocompletado del campo. - autocomplete: Attr, + autocomplete: Option, /// Devuelve si el campo recibe el foco automáticamente al cargar la página. autofocus: bool, /// Devuelve si el campo es de sólo lectura. @@ -183,7 +185,8 @@ pub struct Field { /// Devuelve si el campo se muestra como texto plano sin bordes ni fondo. plaintext: bool, /// Devuelve la sugerencia de teclado virtual para el campo. - inputmode: Attr, + #[getters(copy)] + inputmode: Option, } #[async_trait] @@ -199,7 +202,7 @@ impl Component for Field { fn setup(&mut self, _cx: &Context) { if let Some(container_id) = self .id() - .or_else(|| self.name().get().map(|n| util::join!("edit-", n))) + .or_else(|| self.name().as_deref().map(|n| util::join!("edit-", n))) { self.alter_prop(PropsOp::ensure_id(container_id)); } @@ -225,9 +228,9 @@ impl Component for Field { let strict = self.kind().is_strict(); let masked = *self.kind() == Kind::StrictPassword; let autocomplete = if strict { - Some(form::Autocomplete::Off) + Some(&form::Autocomplete::Off) } else { - self.autocomplete().get() + self.autocomplete() }; Ok(html! { @@ -249,12 +252,12 @@ impl Component for Field { type=(self.kind()) id=[input_id.as_deref()] class=(input_class) - name=[self.name().get()] - value=[self.value().get()] - minlength=[self.minlength().get()] - maxlength=[self.maxlength().get()] + name=[self.name().as_deref()] + value=[self.value().as_deref()] + minlength=[self.minlength()] + maxlength=[self.maxlength()] placeholder=[self.placeholder().lookup(cx)] - inputmode=[self.inputmode().get()] + inputmode=[self.inputmode()] autocomplete=[autocomplete] spellcheck=[strict.then_some("false")] autocorrect=[strict.then_some("off")] @@ -434,15 +437,15 @@ impl Field { /// Establece la longitud mínima permitida en caracteres (`None` para no imponer mínimo). #[builder_fn] - pub fn with_minlength(mut self, minlength: Option) -> Self { - self.minlength.alter_opt(minlength); + pub fn with_minlength(mut self, minlength: impl Into>) -> Self { + self.minlength = minlength.into(); self } /// Establece la longitud máxima permitida en caracteres (`None` para no imponer límite). #[builder_fn] - pub fn with_maxlength(mut self, maxlength: Option) -> Self { - self.maxlength.alter_opt(maxlength); + pub fn with_maxlength(mut self, maxlength: impl Into>) -> Self { + self.maxlength = maxlength.into(); self } @@ -462,8 +465,11 @@ impl Field { /// [`Autocomplete::email()`](form::Autocomplete::email) o /// [`Autocomplete::current_password()`](form::Autocomplete::current_password)). #[builder_fn] - pub fn with_autocomplete(mut self, autocomplete: Option) -> Self { - self.autocomplete.alter_opt(autocomplete); + pub fn with_autocomplete( + mut self, + autocomplete: impl Into>, + ) -> Self { + self.autocomplete = autocomplete.into(); self } @@ -510,8 +516,8 @@ impl Field { /// A diferencia del atributo `type` ([`form::input::Kind`]), no restringe los valores aceptados /// ni activa la validación del navegador; es sólo una sugerencia de presentación. #[builder_fn] - pub fn with_inputmode(mut self, inputmode: Option) -> Self { - self.inputmode.alter_opt(inputmode); + pub fn with_inputmode(mut self, inputmode: impl Into>) -> Self { + self.inputmode = inputmode.into(); self } } diff --git a/src/base/component/form/number.rs b/src/base/component/form/number.rs index 242d18a0..8e66261f 100644 --- a/src/base/component/form/number.rs +++ b/src/base/component/form/number.rs @@ -35,17 +35,21 @@ pub struct Number { /// Devuelve el nombre del campo. name: AttrName, /// Devuelve el valor inicial del campo. - value: Attr, + #[getters(copy)] + value: Option, /// Devuelve la etiqueta del campo. label: Lc, /// Devuelve el texto de ayuda del campo. help_text: Lc, /// Devuelve el valor mínimo permitido. - min: Attr, + #[getters(copy)] + min: Option, /// Devuelve el valor máximo permitido. - max: Attr, + #[getters(copy)] + max: Option, /// Devuelve el incremento entre valores del campo. - step: Attr, + #[getters(copy)] + step: Option, /// Devuelve si el campo recibe el foco automáticamente al cargar la página. autofocus: bool, /// Devuelve si el campo es de sólo lectura. @@ -69,7 +73,7 @@ impl Component for Number { fn setup(&mut self, _cx: &Context) { if let Some(container_id) = self .id() - .or_else(|| self.name().get().map(|n| util::join!("edit-", n))) + .or_else(|| self.name().as_deref().map(|n| util::join!("edit-", n))) { self.alter_prop(PropsOp::ensure_id(container_id)); } @@ -100,11 +104,11 @@ impl Component for Number { type="number" id=[input_id.as_deref()] class="form-control" - name=[self.name().get()] - min=[self.min().get()] - max=[self.max().get()] - step=[self.step().get()] - value=[self.value().get()] + name=[self.name().as_deref()] + min=[self.min()] + max=[self.max()] + step=[self.step()] + value=[self.value()] autofocus[*self.autofocus()] readonly[*self.readonly()] required[*self.required()] @@ -146,8 +150,8 @@ impl Number { /// Establece el valor inicial del campo. #[builder_fn] - pub fn with_value(mut self, value: Option) -> Self { - self.value.alter_opt(value); + pub fn with_value(mut self, value: impl Into>) -> Self { + self.value = value.into(); self } @@ -167,15 +171,15 @@ impl Number { /// Establece el valor mínimo permitido (`None` para no imponer mínimo). #[builder_fn] - pub fn with_min(mut self, min: Option) -> Self { - self.min.alter_opt(min); + pub fn with_min(mut self, min: impl Into>) -> Self { + self.min = min.into(); self } /// Establece el valor máximo permitido (`None` para no imponer máximo). #[builder_fn] - pub fn with_max(mut self, max: Option) -> Self { - self.max.alter_opt(max); + pub fn with_max(mut self, max: impl Into>) -> Self { + self.max = max.into(); self } @@ -184,8 +188,8 @@ impl Number { /// Pasar `None` omite el atributo `step` y deja que el navegador aplique su valor por defecto /// (normalmente `1`). #[builder_fn] - pub fn with_step(mut self, step: Option) -> Self { - self.step.alter_opt(step); + pub fn with_step(mut self, step: impl Into>) -> Self { + self.step = step.into(); self } diff --git a/src/base/component/form/radio.rs b/src/base/component/form/radio.rs index 3a441ae0..77d4ec24 100644 --- a/src/base/component/form/radio.rs +++ b/src/base/component/form/radio.rs @@ -141,7 +141,7 @@ impl Component for Field { async fn prepare(&self, cx: &mut Context) -> Result { // En `setup()` se garantiza que `name` e `id` están definidos antes del renderizado. - let name = self.name().get().unwrap(); + let name = self.name().as_deref().unwrap(); let container_id = self.id().unwrap(); Ok(html! { @@ -178,8 +178,8 @@ impl Component for Field { type="radio" id=(&item_id) class="form-check-input" - name=(&name) - value=[item.value().get()] + name=(name) + value=[item.value().as_deref()] checked[checked] required[*self.required()] disabled[*item.disabled() || *self.disabled()]; diff --git a/src/base/component/form/range.rs b/src/base/component/form/range.rs index d656fce2..444a2de6 100644 --- a/src/base/component/form/range.rs +++ b/src/base/component/form/range.rs @@ -36,17 +36,21 @@ pub struct Range { /// Devuelve el nombre del campo. name: AttrName, /// Devuelve el valor inicial del campo. - value: Attr, + #[getters(copy)] + value: Option, /// Devuelve la etiqueta del campo. label: Lc, /// Devuelve el texto de ayuda del campo. help_text: Lc, /// Devuelve el valor mínimo permitido. - min: Attr, + #[getters(copy)] + min: Option, /// Devuelve el valor máximo permitido. - max: Attr, + #[getters(copy)] + max: Option, /// Devuelve el incremento entre valores del campo. - step: Attr, + #[getters(copy)] + step: Option, /// Devuelve si el control recibe el foco automáticamente al cargar la página. autofocus: bool, /// Devuelve si el control está deshabilitado. @@ -66,7 +70,7 @@ impl Component for Range { fn setup(&mut self, _cx: &Context) { if let Some(container_id) = self .id() - .or_else(|| self.name().get().map(|n| util::join!("edit-", n))) + .or_else(|| self.name().as_deref().map(|n| util::join!("edit-", n))) { self.alter_prop(PropsOp::ensure_id(container_id)); }; @@ -87,11 +91,11 @@ impl Component for Range { type="range" id=[range_id.as_deref()] class="form-range" - name=[self.name().get()] - min=[self.min().get()] - max=[self.max().get()] - step=[self.step().get()] - value=[self.value().get()] + name=[self.name().as_deref()] + min=[self.min()] + max=[self.max()] + step=[self.step()] + value=[self.value()] autofocus[*self.autofocus()] disabled[*self.disabled()]; @if let Some(description) = self.help_text().lookup(cx) { @@ -134,8 +138,8 @@ impl Range { /// Pasar `None` omite el atributo `value` y deja que el navegador aplique su valor por defecto /// (normalmente el punto medio del rango). #[builder_fn] - pub fn with_value(mut self, value: Option) -> Self { - self.value.alter_opt(value); + pub fn with_value(mut self, value: impl Into>) -> Self { + self.value = value.into(); self } @@ -157,8 +161,8 @@ impl Range { /// /// Pasar `None` omite el atributo `min` y deja que el navegador aplique su valor por defecto. #[builder_fn] - pub fn with_min(mut self, min: Option) -> Self { - self.min.alter_opt(min); + pub fn with_min(mut self, min: impl Into>) -> Self { + self.min = min.into(); self } @@ -166,8 +170,8 @@ impl Range { /// /// Pasar `None` omite el atributo `max` y deja que el navegador aplique su valor por defecto. #[builder_fn] - pub fn with_max(mut self, max: Option) -> Self { - self.max.alter_opt(max); + pub fn with_max(mut self, max: impl Into>) -> Self { + self.max = max.into(); self } @@ -176,8 +180,8 @@ impl Range { /// Pasar `None` omite el atributo `step` y deja que el navegador aplique su valor por defecto /// (normalmente `1`). #[builder_fn] - pub fn with_step(mut self, step: Option) -> Self { - self.step.alter_opt(step); + pub fn with_step(mut self, step: impl Into>) -> Self { + self.step = step.into(); self } diff --git a/src/base/component/form/select.rs b/src/base/component/form/select.rs index 29448bbd..d562916d 100644 --- a/src/base/component/form/select.rs +++ b/src/base/component/form/select.rs @@ -205,9 +205,10 @@ pub struct Field { /// Devuelve si la lista permite selección múltiple. multiple: bool, /// Devuelve el número de filas visibles de la lista de selección. - rows: Attr, + #[getters(copy)] + rows: Option, /// Devuelve la configuración de autocompletado del campo. - autocomplete: Attr, + autocomplete: Option, /// Devuelve si la lista recibe el foco automáticamente al cargar la página. autofocus: bool, /// Devuelve si la selección de un elemento es obligatoria. @@ -229,7 +230,7 @@ impl Component for Field { fn setup(&mut self, _cx: &Context) { if let Some(container_id) = self .id() - .or_else(|| self.name().get().map(|n| util::join!("edit-", n))) + .or_else(|| self.name().as_deref().map(|n| util::join!("edit-", n))) { self.alter_prop(PropsOp::ensure_id(container_id)); } @@ -260,10 +261,10 @@ impl Component for Field { select id=[select_id.as_deref()] class="form-select" - name=[self.name().get()] + name=[self.name().as_deref()] multiple[*self.multiple()] - size=[self.rows().get()] - autocomplete=[self.autocomplete().get()] + size=[self.rows()] + autocomplete=[self.autocomplete()] autofocus[*self.autofocus()] required[*self.required()] disabled[*self.disabled()] @@ -272,7 +273,7 @@ impl Component for Field { @match entry { Entry::Item(opt) => { option - value=(opt.value().as_str().unwrap_or("")) + value=(opt.value().as_deref().unwrap_or("")) selected[*opt.selected()] disabled[*opt.disabled()] { @@ -286,7 +287,7 @@ impl Component for Field { { @for opt in group.items() { option - value=(opt.value().as_str().unwrap_or("")) + value=(opt.value().as_deref().unwrap_or("")) selected[*opt.selected()] disabled[*opt.disabled()] { @@ -389,8 +390,8 @@ impl Field { /// Es especialmente útil con selección múltiple para controlar el número de filas visibles sin /// necesidad de recurrir al desplazamiento. #[builder_fn] - pub fn with_rows(mut self, rows: Option) -> Self { - self.rows.alter_opt(rows); + pub fn with_rows(mut self, rows: impl Into>) -> Self { + self.rows = rows.into(); self } @@ -404,8 +405,11 @@ impl Field { /// Usa los métodos de [`form::Autocomplete`] para los valores más habituales. Pasa `None` para /// omitir el atributo. #[builder_fn] - pub fn with_autocomplete(mut self, autocomplete: Option) -> Self { - self.autocomplete.alter_opt(autocomplete); + pub fn with_autocomplete( + mut self, + autocomplete: impl Into>, + ) -> Self { + self.autocomplete = autocomplete.into(); self } diff --git a/src/base/component/form/textarea.rs b/src/base/component/form/textarea.rs index cc043bb9..97e047ce 100644 --- a/src/base/component/form/textarea.rs +++ b/src/base/component/form/textarea.rs @@ -44,15 +44,18 @@ pub struct Textarea { /// Devuelve el texto de ayuda del campo. help_text: Lc, /// Devuelve el número de filas visibles del área de texto. - rows: Attr, + #[getters(copy)] + rows: Option, /// Devuelve la longitud mínima permitida en caracteres. - minlength: Attr, + #[getters(copy)] + minlength: Option, /// Devuelve la longitud máxima permitida en caracteres. - maxlength: Attr, + #[getters(copy)] + maxlength: Option, /// Devuelve el texto indicativo del área de texto. placeholder: Lc, /// Devuelve la configuración de autocompletado del campo. - autocomplete: Attr, + autocomplete: Option, /// Devuelve si el campo recibe el foco automáticamente al cargar la página. autofocus: bool, /// Devuelve si el campo es de sólo lectura. @@ -76,7 +79,7 @@ impl Component for Textarea { fn setup(&mut self, _cx: &Context) { if let Some(container_id) = self .id() - .or_else(|| self.name().get().map(|n| util::join!("edit-", n))) + .or_else(|| self.name().as_deref().map(|n| util::join!("edit-", n))) { self.alter_prop(PropsOp::ensure_id(container_id)); } @@ -109,18 +112,18 @@ impl Component for Textarea { textarea id=[textarea_id.as_deref()] class="form-control" - name=[self.name().get()] - rows=[self.rows().get()] - minlength=[self.minlength().get()] - maxlength=[self.maxlength().get()] + name=[self.name().as_deref()] + rows=[self.rows()] + minlength=[self.minlength()] + maxlength=[self.maxlength()] placeholder=[self.placeholder().lookup(cx)] - autocomplete=[self.autocomplete().get()] + autocomplete=[self.autocomplete()] autofocus[*self.autofocus()] readonly[*self.readonly()] required[*self.required()] disabled[*self.disabled()] { - @if let Some(value) = self.value().get() { + @if let Some(value) = self.value().as_deref() { (value) } } @@ -185,22 +188,22 @@ impl Textarea { /// Sin valor o pasando `None`, el área muestra su altura predeterminada, dos filas según el /// estándar. #[builder_fn] - pub fn with_rows(mut self, rows: Option) -> Self { - self.rows.alter_opt(rows); + pub fn with_rows(mut self, rows: impl Into>) -> Self { + self.rows = rows.into(); self } /// Establece la longitud mínima permitida en caracteres. #[builder_fn] - pub fn with_minlength(mut self, minlength: Option) -> Self { - self.minlength.alter_opt(minlength); + pub fn with_minlength(mut self, minlength: impl Into>) -> Self { + self.minlength = minlength.into(); self } /// Establece la longitud máxima permitida en caracteres. #[builder_fn] - pub fn with_maxlength(mut self, maxlength: Option) -> Self { - self.maxlength.alter_opt(maxlength); + pub fn with_maxlength(mut self, maxlength: impl Into>) -> Self { + self.maxlength = maxlength.into(); self } @@ -222,8 +225,11 @@ impl Textarea { /// Usa los métodos de [`form::Autocomplete`] para los valores más habituales. Pasa `None` para /// omitir el atributo. #[builder_fn] - pub fn with_autocomplete(mut self, autocomplete: Option) -> Self { - self.autocomplete.alter_opt(autocomplete); + pub fn with_autocomplete( + mut self, + autocomplete: impl Into>, + ) -> Self { + self.autocomplete = autocomplete.into(); self } diff --git a/src/base/component/intro.rs b/src/base/component/intro.rs index 4a8efb54..f2c306c1 100644 --- a/src/base/component/intro.rs +++ b/src/base/component/intro.rs @@ -263,8 +263,8 @@ impl Intro { /// let intro_no_button = Intro::default().with_button(None); /// ``` #[builder_fn] - pub fn with_button(mut self, button: Option<(Lc, Route)>) -> Self { - self.button = button; + pub fn with_button(mut self, button: impl Into>) -> Self { + self.button = button.into(); self } diff --git a/src/base/component/pager.rs b/src/base/component/pager.rs index 1cf282ce..da24013e 100644 --- a/src/base/component/pager.rs +++ b/src/base/component/pager.rs @@ -203,11 +203,11 @@ impl Component for Pager { return Ok(html! {}); } let page = self.current_page().clamp(1, total_pages); - let base_path = self.base_path().as_str().unwrap_or_default(); + let base_path = self.base_path().as_deref().unwrap_or(""); // Ruta común a los enlaces del paginador, con los parámetros de `extra_query` añadidos a // `base_path`. Pasa por `cx.route()` para preservar el parámetro `lang` si corresponde. - let mut route = cx.route(base_path.to_owned()); + let mut route = cx.route(base_path); for (key, value) in self.extra_query() { route.alter_param(key, value); } diff --git a/src/base/component/table/column.rs b/src/base/component/table/column.rs index cc55ca71..ea7604ad 100644 --- a/src/base/component/table/column.rs +++ b/src/base/component/table/column.rs @@ -99,7 +99,7 @@ impl Column { html! { th (self.props()) scope="col" aria-sort=(aria_sort) { - a href=[sort.href().as_str()] (link_props) { (label) } + a href=[sort.href().as_deref()] (link_props) { (label) } } } } diff --git a/src/base/component/table/props.rs b/src/base/component/table/props.rs index c6446e92..c7b3acdc 100644 --- a/src/base/component/table/props.rs +++ b/src/base/component/table/props.rs @@ -21,7 +21,7 @@ use crate::prelude::*; /// .with_prop(PropsOp::set("data-sort", "email")); /// /// assert_eq!(link.props().get_id(), Some("sort-email".to_string())); -/// assert_eq!(link.href().as_str(), Some("/admin/users?sort=email")); +/// assert_eq!(link.href().as_deref(), Some("/admin/users?sort=email")); /// assert_eq!(link.dir(), Some(&SortDir::Desc)); /// ``` /// diff --git a/src/core/component/children.rs b/src/core/component/children.rs index 2a89f2c2..154b4945 100644 --- a/src/core/component/children.rs +++ b/src/core/component/children.rs @@ -45,8 +45,8 @@ impl Child { /// /// Si se proporciona `Some(component)`, se encapsula como [`Child`]; y si es `None`, se limpia. #[builder_fn] - pub fn with_component(mut self, component: Option) -> Self { - self.0 = component.map(|c| Arc::new(c) as Arc); + pub fn with_component(mut self, component: impl Into>) -> Self { + self.0 = component.into().map(|c| Arc::new(c) as Arc); self } @@ -166,8 +166,8 @@ impl Embed { /// /// Si se proporciona `Some(component)`, se encapsula como [`Embed`]; y si es `None`, se limpia. #[builder_fn] - pub fn with_component(mut self, component: Option) -> Self { - self.0 = component.map(Arc::new); + pub fn with_component(mut self, component: impl Into>) -> Self { + self.0 = component.into().map(Arc::new); self } diff --git a/src/html.rs b/src/html.rs index a2d4d2d0..d2f88c52 100644 --- a/src/html.rs +++ b/src/html.rs @@ -26,7 +26,7 @@ pub use logo::PageTopSvg; // **< HTML ATTRIBUTES >**************************************************************************** mod attr; -pub use attr::{Attr, AttrName, AttrValue}; +pub use attr::{AttrName, AttrValue}; mod props; pub use props::{Props, PropsError, PropsExtra, PropsOp}; diff --git a/src/html/attr.rs b/src/html/attr.rs index 6067907e..5ad5b3a3 100644 --- a/src/html/attr.rs +++ b/src/html/attr.rs @@ -1,87 +1,5 @@ use crate::{AutoDefault, builder_fn, util}; -/// Valor opcional para atributos HTML. -/// -/// `Attr` encapsula un `Option` y sirve como tipo base para representar atributos HTML -/// opcionales, uniformes y tipados. -/// -/// Este tipo **no impone ninguna normalización ni semántica concreta**; dichas reglas se definen en -/// implementaciones concretas como `Attr`, o en tipos específicos como [`AttrName`]. Para -/// texto localizado usa directamente [`Lc`](crate::locale::Lc) que ya representa su propia ausencia -/// con [`Lc::none()`](crate::locale::Lc::none()), sin necesidad de envolverlo en `Attr`. -#[derive(AutoDefault, Clone, Debug)] -pub struct Attr(Option); - -impl Attr { - /// Crea un atributo vacío. - pub fn empty() -> Self { - Self(None) - } - - /// Crea un atributo con valor. - pub fn some(value: T) -> Self { - Self(Some(value)) - } - - // **< Attr BUILDER >************************************************************************ - - /// Establece un valor opcional para el atributo. - #[builder_fn] - pub fn with_opt(mut self, opt: Option) -> Self { - self.0 = opt; - self - } - - /// Establece un valor para el atributo. - #[builder_fn] - pub fn with_value(mut self, value: T) -> Self { - self.0 = Some(value); - self - } - - /// Elimina el valor del atributo. - #[builder_fn] - pub fn with_none(mut self) -> Self { - self.0 = None; - self - } - - // **< Attr GETTERS >************************************************************************ - - /// Devuelve el valor (clonado), si existe. - pub fn get(&self) -> Option - where - T: Clone, - { - self.0.clone() - } - - /// Devuelve una referencia al valor, si existe. - pub fn as_ref(&self) -> Option<&T> { - self.0.as_ref() - } - - /// Devuelve el valor (propiedad), si existe. - pub fn into_inner(self) -> Option { - self.0 - } - - /// `true` si no hay valor. - pub fn is_empty(&self) -> bool { - self.0.is_none() - } -} - -// **< Attr >******************************************************************************* - -/// Extiende [`Attr`] para cadenas de texto. -impl Attr { - /// Devuelve el texto como `&str` si existe. - pub fn as_str(&self) -> Option<&str> { - self.0.as_deref() - } -} - // **< AttrName >*********************************************************************************** /// Nombre normalizado para el atributo `name` o similar de HTML. @@ -98,13 +16,13 @@ impl Attr { /// ```rust /// # use pagetop::prelude::*; /// let name = AttrName::new(" DISplay name "); -/// assert_eq!(name.as_str(), Some("display_name")); +/// assert_eq!(name.as_deref(), Some("display_name")); /// /// let empty = AttrName::default(); /// assert_eq!(empty.get(), None); /// ``` #[derive(AutoDefault, Clone, Debug)] -pub struct AttrName(Attr); +pub struct AttrName(Option); impl AttrName { /// Crea un nuevo `AttrName` normalizando el valor. @@ -117,33 +35,25 @@ impl AttrName { /// Establece un nombre nuevo normalizando el valor. #[builder_fn] pub fn with_name(mut self, name: impl AsRef) -> Self { - self.0 = match util::normalize_token(name) { - Some(name) => Attr::some(name), - None => Attr::default(), - }; + self.0 = util::normalize_token(name); self } // **< AttrName GETTERS >*********************************************************************** - /// Devuelve el nombre normalizado, si existe. - pub fn get(&self) -> Option { - self.0.get() - } - /// Devuelve el nombre normalizado (sin clonar), si existe. - pub fn as_str(&self) -> Option<&str> { - self.0.as_str() + pub fn as_deref(&self) -> Option<&str> { + self.0.as_deref() } - /// Devuelve el nombre normalizado (propiedad), si existe. - pub fn into_inner(self) -> Option { - self.0.into_inner() + /// Devuelve el nombre normalizado (clonado), si existe. + pub fn get(&self) -> Option { + self.0.clone() } /// `true` si no hay valor. pub fn is_empty(&self) -> bool { - self.0.is_empty() + self.0.is_none() } } @@ -161,13 +71,13 @@ impl AttrName { /// ```rust /// # use pagetop::prelude::*; /// let s = AttrValue::new(" a new string "); -/// assert_eq!(s.as_str(), Some("a new string")); +/// assert_eq!(s.as_deref(), Some("a new string")); /// /// let empty = AttrValue::default(); /// assert_eq!(empty.get(), None); /// ``` #[derive(AutoDefault, Clone, Debug)] -pub struct AttrValue(Attr); +pub struct AttrValue(Option); impl AttrValue { /// Crea un nuevo `AttrValue` normalizando el valor. @@ -180,32 +90,24 @@ impl AttrValue { /// Establece una cadena nueva normalizando el valor. #[builder_fn] pub fn with_str(mut self, value: impl AsRef) -> Self { - self.0 = match util::non_blank(value.as_ref()) { - Some(value) => Attr::some(value.to_string()), - None => Attr::default(), - }; + self.0 = util::non_blank(value.as_ref()).map(str::to_string); self } // **< AttrValue GETTERS >********************************************************************** - /// Devuelve la cadena normalizada, si existe. - pub fn get(&self) -> Option { - self.0.get() - } - /// Devuelve la cadena normalizada (sin clonar), si existe. - pub fn as_str(&self) -> Option<&str> { - self.0.as_str() + pub fn as_deref(&self) -> Option<&str> { + self.0.as_deref() } - /// Devuelve la cadena normalizada (propiedad), si existe. - pub fn into_inner(self) -> Option { - self.0.into_inner() + /// Devuelve la cadena normalizada (clonada), si existe. + pub fn get(&self) -> Option { + self.0.clone() } /// `true` si no hay valor. pub fn is_empty(&self) -> bool { - self.0.is_empty() + self.0.is_none() } }