diff --git a/src/base/component/button.rs b/src/base/component/button.rs index 4316766e..bb429fb7 100644 --- a/src/base/component/button.rs +++ b/src/base/component/button.rs @@ -74,9 +74,9 @@ pub struct Button { /// Devuelve el valor del botón. value: AttrValue, /// Devuelve la etiqueta del botón. - label: Attr, + label: Lc, /// Devuelve el texto emergente del botón (atributo `title`). - title: Attr, + title: Lc, /// Devuelve si el botón recibe el foco automáticamente al cargar la página. autofocus: bool, /// Devuelve si el botón está deshabilitado. @@ -124,7 +124,7 @@ impl Button { pub fn submit(label: Lc) -> Self { Self { kind: ButtonAction::Submit, - label: Attr::some(label), + label, ..Default::default() } } @@ -135,7 +135,7 @@ impl Button { pub fn reset(label: Lc) -> Self { Self { kind: ButtonAction::Reset, - label: Attr::some(label), + label, ..Default::default() } } @@ -147,7 +147,7 @@ impl Button { pub fn plain(label: Lc) -> Self { Self { kind: ButtonAction::Plain, - label: Attr::some(label), + label, ..Default::default() } } @@ -188,17 +188,17 @@ impl Button { self } - /// Establece o elimina la etiqueta visible del botón (basta pasar `None` para quitarla). + /// Establece la etiqueta visible del botón (usa [`Lc::none()`] para quitarla). #[builder_fn] - pub fn with_label(mut self, label: impl Into>) -> Self { - self.label.alter_opt(label.into()); + pub fn with_label(mut self, label: Lc) -> Self { + self.label = label; self } - /// Establece o elimina el texto emergente del botón (basta pasar `None` para quitarlo). + /// Establece el texto emergente del botón (usa [`Lc::none()`] para quitarlo). #[builder_fn] - pub fn with_title(mut self, title: impl Into>) -> Self { - self.title.alter_opt(title.into()); + pub fn with_title(mut self, title: Lc) -> Self { + self.title = title; self } diff --git a/src/base/component/form/check.rs b/src/base/component/form/check.rs index e0021e6c..8f0d9999 100644 --- a/src/base/component/form/check.rs +++ b/src/base/component/form/check.rs @@ -103,9 +103,9 @@ pub struct Field { /// Devuelve el nombre compartido por todas las casillas del grupo. name: AttrName, /// Devuelve la etiqueta del grupo. - label: Attr, + label: Lc, /// Devuelve el texto de ayuda del grupo. - help_text: Attr, + help_text: Lc, /// Devuelve las casillas del grupo. items: Vec, /// Devuelve si todo el grupo está deshabilitado. @@ -207,17 +207,17 @@ impl Field { self } - /// Establece o elimina la etiqueta visible del grupo (basta pasar `None` para quitarla). + /// Establece la etiqueta visible del grupo (usa [`Lc::none()`] para quitarla). #[builder_fn] - pub fn with_label(mut self, label: impl Into>) -> Self { - self.label.alter_opt(label.into()); + pub fn with_label(mut self, label: Lc) -> Self { + self.label = label; self } - /// Establece o elimina el texto de ayuda del grupo (basta pasar `None` para quitarlo). + /// Establece el texto de ayuda del grupo (usa [`Lc::none()`] para quitarlo). #[builder_fn] - pub fn with_help_text(mut self, help_text: impl Into>) -> Self { - self.help_text.alter_opt(help_text.into()); + pub fn with_help_text(mut self, help_text: Lc) -> Self { + self.help_text = help_text; self } diff --git a/src/base/component/form/checkbox.rs b/src/base/component/form/checkbox.rs index af721490..4d1378b3 100644 --- a/src/base/component/form/checkbox.rs +++ b/src/base/component/form/checkbox.rs @@ -46,7 +46,7 @@ pub struct Checkbox { /// Devuelve el nombre del campo. name: AttrName, /// Devuelve la etiqueta del control. - label: Attr, + label: Lc, /// Devuelve si el control debe estar marcado/activo por defecto. checked: bool, /// Devuelve si el control recibe el foco automáticamente al cargar la página. @@ -182,10 +182,10 @@ impl Checkbox { self } - /// Establece o elimina la etiqueta visible del control (basta pasar `None` para quitarla). + /// Establece la etiqueta visible del control (usa [`Lc::none()`] para quitarla). #[builder_fn] - pub fn with_label(mut self, label: impl Into>) -> Self { - self.label.alter_opt(label.into()); + pub fn with_label(mut self, label: Lc) -> Self { + self.label = label; self } diff --git a/src/base/component/form/fieldset.rs b/src/base/component/form/fieldset.rs index 1deac27b..1cca3bee 100644 --- a/src/base/component/form/fieldset.rs +++ b/src/base/component/form/fieldset.rs @@ -27,9 +27,9 @@ pub struct Fieldset { /// Devuelve identificador, clases CSS, atributos HTML y valores extra del componente. props: Props, /// Devuelve la leyenda del `fieldset`. - legend: Attr, + legend: Lc, /// Devuelve la descripción del `fieldset`. - description: Attr, + description: Lc, /// Devuelve si el `fieldset` está deshabilitado. disabled: bool, /// Devuelve la lista de componentes del `fieldset`. @@ -84,17 +84,17 @@ impl Fieldset { self } - /// Establece o elimina la leyenda del `fieldset` (basta pasar `None` para quitarla). + /// Establece la leyenda del `fieldset` (usa [`Lc::none()`] para quitarla). #[builder_fn] - pub fn with_legend(mut self, legend: impl Into>) -> Self { - self.legend.alter_opt(legend.into()); + pub fn with_legend(mut self, legend: Lc) -> Self { + self.legend = legend; self } - /// Establece o elimina la descripción del `fieldset` (basta pasar `None` para quitarla). + /// Establece la descripción del `fieldset` (usa [`Lc::none()`] para quitarla). #[builder_fn] - pub fn with_description(mut self, description: impl Into>) -> Self { - self.description.alter_opt(description.into()); + pub fn with_description(mut self, description: Lc) -> Self { + self.description = description; self } diff --git a/src/base/component/form/input.rs b/src/base/component/form/input.rs index 62279724..d17dcb01 100644 --- a/src/base/component/form/input.rs +++ b/src/base/component/form/input.rs @@ -161,15 +161,15 @@ pub struct Field { /// Devuelve el valor inicial del campo. value: AttrValue, /// Devuelve la etiqueta del campo. - label: Attr, + label: Lc, /// Devuelve el texto de ayuda del campo. - help_text: Attr, + help_text: Lc, /// Devuelve la longitud mínima permitida en caracteres. minlength: Attr, /// Devuelve la longitud máxima permitida en caracteres. maxlength: Attr, /// Devuelve el texto indicativo del campo. - placeholder: Attr, + placeholder: Lc, /// Devuelve la configuración de autocompletado del campo. autocomplete: Attr, /// Devuelve si el campo recibe el foco automáticamente al cargar la página. @@ -418,17 +418,17 @@ impl Field { self } - /// Establece o elimina la etiqueta visible del campo (basta pasar `None` para quitarla). + /// Establece la etiqueta visible del campo (usa [`Lc::none()`] para quitarla). #[builder_fn] - pub fn with_label(mut self, label: impl Into>) -> Self { - self.label.alter_opt(label.into()); + pub fn with_label(mut self, label: Lc) -> Self { + self.label = label; self } - /// Establece o elimina el texto de ayuda del campo (basta pasar `None` para quitarlo). + /// Establece el texto de ayuda del campo (usa [`Lc::none()`] para quitarlo). #[builder_fn] - pub fn with_help_text(mut self, help_text: impl Into>) -> Self { - self.help_text.alter_opt(help_text.into()); + pub fn with_help_text(mut self, help_text: Lc) -> Self { + self.help_text = help_text; self } @@ -446,13 +446,13 @@ impl Field { self } - /// Establece o elimina el texto indicativo del campo (`None` para quitarlo). + /// Establece el texto indicativo del campo (usa [`Lc::none()`] para quitarlo). /// /// Este texto aparece en el mismo campo y desaparece en cuanto el usuario empieza a escribir. /// Al ser texto visible para el usuario se acepta [`Lc`] para poder localizarlo. #[builder_fn] - pub fn with_placeholder(mut self, placeholder: impl Into>) -> Self { - self.placeholder.alter_opt(placeholder.into()); + pub fn with_placeholder(mut self, placeholder: Lc) -> Self { + self.placeholder = placeholder; self } diff --git a/src/base/component/form/number.rs b/src/base/component/form/number.rs index 48a8f617..242d18a0 100644 --- a/src/base/component/form/number.rs +++ b/src/base/component/form/number.rs @@ -37,9 +37,9 @@ pub struct Number { /// Devuelve el valor inicial del campo. value: Attr, /// Devuelve la etiqueta del campo. - label: Attr, + label: Lc, /// Devuelve el texto de ayuda del campo. - help_text: Attr, + help_text: Lc, /// Devuelve el valor mínimo permitido. min: Attr, /// Devuelve el valor máximo permitido. @@ -151,17 +151,17 @@ impl Number { self } - /// Establece o elimina la etiqueta visible del campo (basta pasar `None` para quitarla). + /// Establece la etiqueta visible del campo (usa [`Lc::none()`] para quitarla). #[builder_fn] - pub fn with_label(mut self, label: impl Into>) -> Self { - self.label.alter_opt(label.into()); + pub fn with_label(mut self, label: Lc) -> Self { + self.label = label; self } - /// Establece o elimina el texto de ayuda del campo (basta pasar `None` para quitarlo). + /// Establece el texto de ayuda del campo (usa [`Lc::none()`] para quitarlo). #[builder_fn] - pub fn with_help_text(mut self, help_text: impl Into>) -> Self { - self.help_text.alter_opt(help_text.into()); + pub fn with_help_text(mut self, help_text: Lc) -> Self { + self.help_text = help_text; self } diff --git a/src/base/component/form/radio.rs b/src/base/component/form/radio.rs index 2cd39b54..3a441ae0 100644 --- a/src/base/component/form/radio.rs +++ b/src/base/component/form/radio.rs @@ -101,9 +101,9 @@ pub struct Field { /// Devuelve el nombre compartido por todos los botones de opción del grupo. name: AttrName, /// Devuelve la etiqueta del grupo. - label: Attr, + label: Lc, /// Devuelve el texto de ayuda del grupo. - help_text: Attr, + help_text: Lc, /// Devuelve las opciones del grupo. items: Vec, /// Devuelve si la selección de alguna opción del grupo es obligatoria. @@ -227,17 +227,17 @@ impl Field { self } - /// Establece o elimina la etiqueta visible del grupo (basta pasar `None` para quitarla). + /// Establece la etiqueta visible del grupo (usa [`Lc::none()`] para quitarla). #[builder_fn] - pub fn with_label(mut self, label: impl Into>) -> Self { - self.label.alter_opt(label.into()); + pub fn with_label(mut self, label: Lc) -> Self { + self.label = label; self } - /// Establece o elimina el texto de ayuda del grupo (basta pasar `None` para quitarlo). + /// Establece el texto de ayuda del grupo (usa [`Lc::none()`] para quitarlo). #[builder_fn] - pub fn with_help_text(mut self, help_text: impl Into>) -> Self { - self.help_text.alter_opt(help_text.into()); + pub fn with_help_text(mut self, help_text: Lc) -> Self { + self.help_text = help_text; self } diff --git a/src/base/component/form/range.rs b/src/base/component/form/range.rs index bebb95bc..d656fce2 100644 --- a/src/base/component/form/range.rs +++ b/src/base/component/form/range.rs @@ -38,9 +38,9 @@ pub struct Range { /// Devuelve el valor inicial del campo. value: Attr, /// Devuelve la etiqueta del campo. - label: Attr, + label: Lc, /// Devuelve el texto de ayuda del campo. - help_text: Attr, + help_text: Lc, /// Devuelve el valor mínimo permitido. min: Attr, /// Devuelve el valor máximo permitido. @@ -139,17 +139,17 @@ impl Range { self } - /// Establece o elimina la etiqueta visible del campo (basta pasar `None` para quitarla). + /// Establece la etiqueta visible del campo (usa [`Lc::none()`] para quitarla). #[builder_fn] - pub fn with_label(mut self, label: impl Into>) -> Self { - self.label.alter_opt(label.into()); + pub fn with_label(mut self, label: Lc) -> Self { + self.label = label; self } - /// Establece o elimina el texto de ayuda del campo (basta pasar `None` para quitarlo). + /// Establece el texto de ayuda del campo (usa [`Lc::none()`] para quitarlo). #[builder_fn] - pub fn with_help_text(mut self, help_text: impl Into>) -> Self { - self.help_text.alter_opt(help_text.into()); + pub fn with_help_text(mut self, help_text: Lc) -> Self { + self.help_text = help_text; self } diff --git a/src/base/component/form/select.rs b/src/base/component/form/select.rs index 8de719df..29448bbd 100644 --- a/src/base/component/form/select.rs +++ b/src/base/component/form/select.rs @@ -197,9 +197,9 @@ pub struct Field { /// Devuelve el nombre del campo. name: AttrName, /// Devuelve la etiqueta del campo. - label: Attr, + label: Lc, /// Devuelve el texto de ayuda del campo. - help_text: Attr, + help_text: Lc, /// Devuelve las entradas de la lista (elementos individuales y grupos de elementos). entries: Vec, /// Devuelve si la lista permite selección múltiple. @@ -333,17 +333,17 @@ impl Field { self } - /// Establece o elimina la etiqueta visible del campo (basta pasar `None` para quitarla). + /// Establece la etiqueta visible del campo (usa [`Lc::none()`] para quitarla). #[builder_fn] - pub fn with_label(mut self, label: impl Into>) -> Self { - self.label.alter_opt(label.into()); + pub fn with_label(mut self, label: Lc) -> Self { + self.label = label; self } - /// Establece o elimina el texto de ayuda del campo (basta pasar `None` para quitarlo). + /// Establece el texto de ayuda del campo (usa [`Lc::none()`] para quitarlo). #[builder_fn] - pub fn with_help_text(mut self, help_text: impl Into>) -> Self { - self.help_text.alter_opt(help_text.into()); + pub fn with_help_text(mut self, help_text: Lc) -> Self { + self.help_text = help_text; self } diff --git a/src/base/component/form/textarea.rs b/src/base/component/form/textarea.rs index 5760d779..cc043bb9 100644 --- a/src/base/component/form/textarea.rs +++ b/src/base/component/form/textarea.rs @@ -40,9 +40,9 @@ pub struct Textarea { /// Devuelve el valor inicial del área de texto. value: AttrValue, /// Devuelve la etiqueta del campo. - label: Attr, + label: Lc, /// Devuelve el texto de ayuda del campo. - help_text: Attr, + help_text: Lc, /// Devuelve el número de filas visibles del área de texto. rows: Attr, /// Devuelve la longitud mínima permitida en caracteres. @@ -50,7 +50,7 @@ pub struct Textarea { /// Devuelve la longitud máxima permitida en caracteres. maxlength: Attr, /// Devuelve el texto indicativo del área de texto. - placeholder: Attr, + placeholder: Lc, /// Devuelve la configuración de autocompletado del campo. autocomplete: Attr, /// Devuelve si el campo recibe el foco automáticamente al cargar la página. @@ -166,17 +166,17 @@ impl Textarea { self } - /// Establece o elimina la etiqueta visible del campo (basta pasar `None` para quitarla). + /// Establece la etiqueta visible del campo (usa [`Lc::none()`] para quitarla). #[builder_fn] - pub fn with_label(mut self, label: impl Into>) -> Self { - self.label.alter_opt(label.into()); + pub fn with_label(mut self, label: Lc) -> Self { + self.label = label; self } - /// Establece o elimina el texto de ayuda del campo (basta pasar `None` para quitarlo). + /// Establece el texto de ayuda del campo (usa [`Lc::none()`] para quitarlo). #[builder_fn] - pub fn with_help_text(mut self, help_text: impl Into>) -> Self { - self.help_text.alter_opt(help_text.into()); + pub fn with_help_text(mut self, help_text: Lc) -> Self { + self.help_text = help_text; self } @@ -204,13 +204,13 @@ impl Textarea { self } - /// Establece o elimina el texto indicativo del área de texto (`None` para quitarlo). + /// Establece el texto indicativo del área de texto (usa [`Lc::none()`] para quitarlo). /// /// Este texto aparece en el área de texto y desaparece en cuanto el usuario empieza a escribir. /// Al ser texto visible para el usuario se acepta [`Lc`] para poder localizarlo. #[builder_fn] - pub fn with_placeholder(mut self, placeholder: impl Into>) -> Self { - self.placeholder.alter_opt(placeholder.into()); + pub fn with_placeholder(mut self, placeholder: Lc) -> Self { + self.placeholder = placeholder; self } diff --git a/src/base/component/image/component.rs b/src/base/component/image/component.rs index ed799886..220a079e 100644 --- a/src/base/component/image/component.rs +++ b/src/base/component/image/component.rs @@ -29,7 +29,7 @@ pub struct Image { /// Devuelve el origen de la imagen. source: image::Source, /// Devuelve el texto alternativo localizado. - alternative: Attr, + alternative: Lc, } #[async_trait] @@ -140,7 +140,7 @@ impl Image { /// decorativa. #[builder_fn] pub fn with_alternative(mut self, alt: Lc) -> Self { - self.alternative.alter_value(alt); + self.alternative = alt; self } } diff --git a/src/html/attr.rs b/src/html/attr.rs index 92593da3..6067907e 100644 --- a/src/html/attr.rs +++ b/src/html/attr.rs @@ -1,4 +1,3 @@ -use crate::locale::{LangId, Lc}; use crate::{AutoDefault, builder_fn, util}; /// Valor opcional para atributos HTML. @@ -7,8 +6,9 @@ use crate::{AutoDefault, builder_fn, util}; /// opcionales, uniformes y tipados. /// /// Este tipo **no impone ninguna normalización ni semántica concreta**; dichas reglas se definen en -/// implementaciones concretas como `Attr` y `Attr`, o en tipos específicos como -/// [`AttrName`]. +/// 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); @@ -72,52 +72,6 @@ impl Attr { } } -// **< Attr >*********************************************************************************** - -/// Extiende [`Attr`] para [texto localizado](crate::locale) en atributos HTML. -/// -/// Encapsula un [`Lc`] para manejar traducciones de forma segura en atributos. -/// -/// # Ejemplo -/// -/// ```rust -/// # use pagetop::prelude::*; -/// // Traducción por clave en las locales por defecto de PageTop. -/// let hello = Attr::::new(Lc::l("test_hello_world")); -/// -/// // Español disponible. -/// assert_eq!( -/// hello.lookup(&Locale::resolve("es-ES")), -/// Some("¡Hola mundo!".to_string()) -/// ); -/// -/// // Japonés no disponible, traduce al idioma de respaldo (`"en-US"`). -/// assert_eq!( -/// hello.lookup(&Locale::resolve("ja-JP")), -/// Some("Hello world!".to_string()) -/// ); -/// -/// // Uso típico en un atributo: -/// let title = hello.value(&Locale::resolve("es-ES")); -/// // Ejemplo: html! { a title=(title) { "Link" } } -/// ``` -impl Attr { - /// Crea una nueva instancia `Attr`. - pub fn new(value: Lc) -> Self { - Self::some(value) - } - - /// Devuelve la traducción para `language` si puede resolverse. - pub fn lookup(&self, language: &impl LangId) -> Option { - self.0.as_ref()?.lookup(language) - } - - /// Devuelve la traducción para `language` o una cadena vacía si no existe. - pub fn value(&self, language: &impl LangId) -> String { - self.lookup(language).unwrap_or_default() - } -} - // **< Attr >******************************************************************************* /// Extiende [`Attr`] para cadenas de texto. diff --git a/src/locale/lc.rs b/src/locale/lc.rs index 93e4a84e..0299d2da 100644 --- a/src/locale/lc.rs +++ b/src/locale/lc.rs @@ -32,6 +32,7 @@ enum LcKind { /// - Un texto puro (`n()`) que no requiere traducción. /// - Una clave para traducir un texto del conjunto de traducciones predefinidas de PageTop (`l()`). /// - Una clave para traducir de un conjunto concreto de traducciones (`t()`). +/// - Ningún contenido (`none()`), para representar la ausencia en un campo `Lc` opcional. /// /// # ¿Cuál usar, `get()`, `lookup()` o `using()`? /// @@ -122,6 +123,23 @@ impl Lc { } } + /// Crea una instancia **sin contenido**: no traduce nada y no representa ningún texto. + /// + /// Equivale a [`Lc::default()`](Default::default), con un nombre más explícito. Útil para + /// representar la ausencia en un campo `Lc` opcional sin requerir `Option`: + /// [`get()`](Self::get) y [`lookup()`](Self::lookup) devuelven `None`, y + /// [`using()`](Self::using) devuelve un marcado vacío. + /// + /// ```rust + /// # use pagetop::prelude::*; + /// assert_eq!(Lc::none().get(), None); + /// ``` + pub fn none() -> Self { + Self::default() + } + + // **< Lc BUILDER >***************************************************************************** + /// Añade un argumento `{$arg}` => `value` a la traducción. pub fn with_arg(mut self, arg: impl Into, value: impl Into) -> Self { self.args.push((arg.into(), value.into())); @@ -142,6 +160,8 @@ impl Lc { self } + // **< Lc GETTERS >***************************************************************************** + /// Resuelve la traducción usando el idioma por defecto o, si no procede, el de respaldo de la /// aplicación. /// diff --git a/src/response/page.rs b/src/response/page.rs index f9b2cc67..27ca749c 100644 --- a/src/response/page.rs +++ b/src/response/page.rs @@ -25,8 +25,8 @@ use crate::core::component::{AssetsOp, ChildOp, ComponentRender}; use crate::core::component::{Context, ContextError, Contextual}; use crate::core::theme::{CoreRegions, RegionName, RegionRef, TemplateRef, ThemeRef}; use crate::html::{Assets, Favicon, JavaScript, StyleSheet}; -use crate::html::{Attr, Props, PropsOp}; use crate::html::{DOCTYPE, Markup, html}; +use crate::html::{Props, PropsOp}; use crate::locale::{CharacterDirection, LangId, LanguageIdentifier, Lc}; use crate::web::HttpRequest; use crate::{AutoDefault, builder_fn}; @@ -88,11 +88,11 @@ impl RegionName for ReservedRegions { #[rustfmt::skip] #[derive(AutoDefault)] pub struct Page { - title : Attr, - description : Attr, - metadata : Vec<(&'static str, &'static str)>, - properties : Vec<(&'static str, &'static str)>, - context : Context, + title : Lc, + description: Lc, + metadata : Vec<(&'static str, &'static str)>, + properties : Vec<(&'static str, &'static str)>, + context : Context, } impl Page { @@ -128,14 +128,14 @@ impl Page { /// Establece el título de la página como un valor traducible. #[builder_fn] pub fn with_title(mut self, title: Lc) -> Self { - self.title.alter_value(title); + self.title = title; self } /// Establece la descripción de la página como un valor traducible. #[builder_fn] pub fn with_description(mut self, description: Lc) -> Self { - self.description.alter_value(description); + self.description = description; self } diff --git a/tests/component_button.rs b/tests/component_button.rs new file mode 100644 index 00000000..5918d7c1 --- /dev/null +++ b/tests/component_button.rs @@ -0,0 +1,45 @@ +use pagetop::prelude::*; + +#[pagetop::test] +async fn label_is_rendered_when_set() { + let mut button = Button::submit(Lc::n("Save")); + let html = button.render(&mut Context::default()).await.into_string(); + + assert!(html.contains("Save")); +} + +#[pagetop::test] +async fn label_can_be_cleared_with_lc_none() { + let mut button = Button::submit(Lc::n("Save")).with_label(Lc::none()); + let html = button.render(&mut Context::default()).await.into_string(); + + assert!(!html.contains("Save")); + // The button itself must still render. + assert!(html.contains("