♻️ Refactoriza AttrClasses como Classes

This commit is contained in:
Manuel Cillero 2025-12-28 14:57:21 +01:00
parent fa32833ffa
commit 4bf2c18b24
13 changed files with 57 additions and 49 deletions

View file

@ -11,7 +11,7 @@ pub struct Container {
#[getters(skip)] #[getters(skip)]
id: AttrId, id: AttrId,
/// Devuelve las clases CSS asociadas al contenedor. /// Devuelve las clases CSS asociadas al contenedor.
classes: AttrClasses, classes: Classes,
/// Devuelve el tipo semántico del contenedor. /// Devuelve el tipo semántico del contenedor.
container_kind: container::Kind, container_kind: container::Kind,
/// Devuelve el comportamiento para el ancho del contenedor. /// Devuelve el comportamiento para el ancho del contenedor.
@ -139,7 +139,7 @@ impl Container {
/// - Redondear las esquinas ([`classes::Rounded`]). /// - Redondear las esquinas ([`classes::Rounded`]).
#[builder_fn] #[builder_fn]
pub fn with_classes(mut self, op: ClassesOp, classes: impl AsRef<str>) -> Self { pub fn with_classes(mut self, op: ClassesOp, classes: impl AsRef<str>) -> Self {
self.classes.alter_value(op, classes); self.classes.alter_classes(op, classes);
self self
} }

View file

@ -24,7 +24,7 @@ pub struct Dropdown {
#[getters(skip)] #[getters(skip)]
id: AttrId, id: AttrId,
/// Devuelve las clases CSS asociadas al menú desplegable. /// Devuelve las clases CSS asociadas al menú desplegable.
classes: AttrClasses, classes: Classes,
/// Devuelve el título del menú desplegable. /// Devuelve el título del menú desplegable.
title: L10n, title: L10n,
/// Devuelve el tamaño configurado del botón. /// Devuelve el tamaño configurado del botón.
@ -76,7 +76,7 @@ impl Component for Dropdown {
PrepareMarkup::With(html! { PrepareMarkup::With(html! {
div id=[self.id()] class=[self.classes().get()] { div id=[self.id()] class=[self.classes().get()] {
@if !title.is_empty() { @if !title.is_empty() {
@let mut btn_classes = AttrClasses::new({ @let mut btn_classes = Classes::new({
let mut classes = "btn".to_string(); let mut classes = "btn".to_string();
self.button_size().push_class(&mut classes); self.button_size().push_class(&mut classes);
self.button_color().push_class(&mut classes); self.button_color().push_class(&mut classes);
@ -86,7 +86,7 @@ impl Component for Dropdown {
@let offset = pos.data_offset(); @let offset = pos.data_offset();
@let reference = pos.data_reference(); @let reference = pos.data_reference();
@let auto_close = self.auto_close.as_str(); @let auto_close = self.auto_close.as_str();
@let menu_classes = AttrClasses::new({ @let menu_classes = Classes::new({
let mut classes = "dropdown-menu".to_string(); let mut classes = "dropdown-menu".to_string();
self.menu_align().push_class(&mut classes); self.menu_align().push_class(&mut classes);
classes classes
@ -107,7 +107,7 @@ impl Component for Dropdown {
@let btn_toggle = html! { @let btn_toggle = html! {
button button
type="button" type="button"
class=[btn_classes.alter_value( class=[btn_classes.alter_classes(
ClassesOp::Add, "dropdown-toggle dropdown-toggle-split" ClassesOp::Add, "dropdown-toggle dropdown-toggle-split"
).get()] ).get()]
data-bs-toggle="dropdown" data-bs-toggle="dropdown"
@ -138,7 +138,7 @@ impl Component for Dropdown {
// Botón único con funcionalidad de *toggle*. // Botón único con funcionalidad de *toggle*.
button button
type="button" type="button"
class=[btn_classes.alter_value( class=[btn_classes.alter_classes(
ClassesOp::Add, "dropdown-toggle" ClassesOp::Add, "dropdown-toggle"
).get()] ).get()]
data-bs-toggle="dropdown" data-bs-toggle="dropdown"
@ -173,7 +173,7 @@ impl Dropdown {
/// Modifica la lista de clases CSS aplicadas al menú desplegable. /// Modifica la lista de clases CSS aplicadas al menú desplegable.
#[builder_fn] #[builder_fn]
pub fn with_classes(mut self, op: ClassesOp, classes: impl AsRef<str>) -> Self { pub fn with_classes(mut self, op: ClassesOp, classes: impl AsRef<str>) -> Self {
self.classes.alter_value(op, classes); self.classes.alter_classes(op, classes);
self self
} }

View file

@ -48,7 +48,7 @@ pub struct Item {
#[getters(skip)] #[getters(skip)]
id: AttrId, id: AttrId,
/// Devuelve las clases CSS asociadas al elemento. /// Devuelve las clases CSS asociadas al elemento.
classes: AttrClasses, classes: Classes,
/// Devuelve el tipo de elemento representado. /// Devuelve el tipo de elemento representado.
item_kind: ItemKind, item_kind: ItemKind,
} }
@ -270,7 +270,7 @@ impl Item {
/// Modifica la lista de clases CSS aplicadas al elemento. /// Modifica la lista de clases CSS aplicadas al elemento.
#[builder_fn] #[builder_fn]
pub fn with_classes(mut self, op: ClassesOp, classes: impl AsRef<str>) -> Self { pub fn with_classes(mut self, op: ClassesOp, classes: impl AsRef<str>) -> Self {
self.classes.alter_value(op, classes); self.classes.alter_classes(op, classes);
self self
} }
} }

View file

@ -16,7 +16,7 @@ pub enum IconKind {
#[derive(AutoDefault, Getters)] #[derive(AutoDefault, Getters)]
pub struct Icon { pub struct Icon {
/// Devuelve las clases CSS asociadas al icono. /// Devuelve las clases CSS asociadas al icono.
classes: AttrClasses, classes: Classes,
icon_kind: IconKind, icon_kind: IconKind,
aria_label: AttrL10n, aria_label: AttrL10n,
} }

View file

@ -14,7 +14,7 @@ pub struct Image {
#[getters(skip)] #[getters(skip)]
id: AttrId, id: AttrId,
/// Devuelve las clases CSS asociadas a la imagen. /// Devuelve las clases CSS asociadas a la imagen.
classes: AttrClasses, classes: Classes,
/// Devuelve las dimensiones de la imagen. /// Devuelve las dimensiones de la imagen.
size: image::Size, size: image::Size,
/// Devuelve el origen de la imagen. /// Devuelve el origen de la imagen.
@ -93,7 +93,7 @@ impl Image {
/// - Redondear las esquinas ([`classes::Rounded`]). /// - Redondear las esquinas ([`classes::Rounded`]).
#[builder_fn] #[builder_fn]
pub fn with_classes(mut self, op: ClassesOp, classes: impl AsRef<str>) -> Self { pub fn with_classes(mut self, op: ClassesOp, classes: impl AsRef<str>) -> Self {
self.classes.alter_value(op, classes); self.classes.alter_classes(op, classes);
self self
} }

View file

@ -15,7 +15,7 @@ pub struct Nav {
#[getters(skip)] #[getters(skip)]
id: AttrId, id: AttrId,
/// Devuelve las clases CSS asociadas al menú. /// Devuelve las clases CSS asociadas al menú.
classes: AttrClasses, classes: Classes,
/// Devuelve el estilo visual seleccionado. /// Devuelve el estilo visual seleccionado.
nav_kind: nav::Kind, nav_kind: nav::Kind,
/// Devuelve la distribución y orientación seleccionada. /// Devuelve la distribución y orientación seleccionada.
@ -84,7 +84,7 @@ impl Nav {
/// Modifica la lista de clases CSS aplicadas al menú. /// Modifica la lista de clases CSS aplicadas al menú.
#[builder_fn] #[builder_fn]
pub fn with_classes(mut self, op: ClassesOp, classes: impl AsRef<str>) -> Self { pub fn with_classes(mut self, op: ClassesOp, classes: impl AsRef<str>) -> Self {
self.classes.alter_value(op, classes); self.classes.alter_classes(op, classes);
self self
} }

View file

@ -81,7 +81,7 @@ pub struct Item {
#[getters(skip)] #[getters(skip)]
id: AttrId, id: AttrId,
/// Devuelve las clases CSS asociadas al elemento. /// Devuelve las clases CSS asociadas al elemento.
classes: AttrClasses, classes: Classes,
/// Devuelve el tipo de elemento representado. /// Devuelve el tipo de elemento representado.
item_kind: ItemKind, item_kind: ItemKind,
} }
@ -293,7 +293,7 @@ impl Item {
/// Modifica la lista de clases CSS aplicadas al elemento. /// Modifica la lista de clases CSS aplicadas al elemento.
#[builder_fn] #[builder_fn]
pub fn with_classes(mut self, op: ClassesOp, classes: impl AsRef<str>) -> Self { pub fn with_classes(mut self, op: ClassesOp, classes: impl AsRef<str>) -> Self {
self.classes.alter_value(op, classes); self.classes.alter_classes(op, classes);
self self
} }
} }

View file

@ -19,7 +19,7 @@ pub struct Navbar {
#[getters(skip)] #[getters(skip)]
id: AttrId, id: AttrId,
/// Devuelve las clases CSS asociadas a la barra de navegación. /// Devuelve las clases CSS asociadas a la barra de navegación.
classes: AttrClasses, classes: Classes,
/// Devuelve el punto de ruptura configurado. /// Devuelve el punto de ruptura configurado.
expand: BreakPoint, expand: BreakPoint,
/// Devuelve la disposición configurada para la barra de navegación. /// Devuelve la disposición configurada para la barra de navegación.
@ -230,7 +230,7 @@ impl Navbar {
/// - Definir la apariencia del texto ([`classes::Text`]). /// - Definir la apariencia del texto ([`classes::Text`]).
#[builder_fn] #[builder_fn]
pub fn with_classes(mut self, op: ClassesOp, classes: impl AsRef<str>) -> Self { pub fn with_classes(mut self, op: ClassesOp, classes: impl AsRef<str>) -> Self {
self.classes.alter_value(op, classes); self.classes.alter_classes(op, classes);
self self
} }

View file

@ -26,7 +26,7 @@ pub struct Offcanvas {
#[getters(skip)] #[getters(skip)]
id: AttrId, id: AttrId,
/// Devuelve las clases CSS asociadas al panel. /// Devuelve las clases CSS asociadas al panel.
classes: AttrClasses, classes: Classes,
/// Devuelve el título del panel. /// Devuelve el título del panel.
title: L10n, title: L10n,
/// Devuelve el punto de ruptura configurado para cambiar el comportamiento del panel. /// Devuelve el punto de ruptura configurado para cambiar el comportamiento del panel.
@ -80,7 +80,7 @@ impl Offcanvas {
/// Modifica la lista de clases CSS aplicadas al panel. /// Modifica la lista de clases CSS aplicadas al panel.
#[builder_fn] #[builder_fn]
pub fn with_classes(mut self, op: ClassesOp, classes: impl AsRef<str>) -> Self { pub fn with_classes(mut self, op: ClassesOp, classes: impl AsRef<str>) -> Self {
self.classes.alter_value(op, classes); self.classes.alter_classes(op, classes);
self self
} }

View file

@ -9,7 +9,7 @@ pub struct Block {
#[getters(skip)] #[getters(skip)]
id: AttrId, id: AttrId,
/// Devuelve las clases CSS asociadas al bloque. /// Devuelve las clases CSS asociadas al bloque.
classes: AttrClasses, classes: Classes,
/// Devuelve el título del bloque. /// Devuelve el título del bloque.
title: L10n, title: L10n,
/// Devuelve la lista de componentes hijo del bloque. /// Devuelve la lista de componentes hijo del bloque.
@ -62,7 +62,7 @@ impl Block {
/// Modifica la lista de clases CSS aplicadas al bloque. /// Modifica la lista de clases CSS aplicadas al bloque.
#[builder_fn] #[builder_fn]
pub fn with_classes(mut self, op: ClassesOp, classes: impl AsRef<str>) -> Self { pub fn with_classes(mut self, op: ClassesOp, classes: impl AsRef<str>) -> Self {
self.classes.alter_value(op, classes); self.classes.alter_classes(op, classes);
self self
} }

View file

@ -24,8 +24,8 @@ pub use logo::PageTopSvg;
mod attr; mod attr;
pub use attr::{Attr, AttrId, AttrName, AttrValue}; pub use attr::{Attr, AttrId, AttrName, AttrValue};
mod attr_classes; mod classes;
pub use attr_classes::{AttrClasses, ClassesOp}; pub use classes::{Classes, ClassesOp};
mod unit; mod unit;
pub use unit::UnitValue; pub use unit::UnitValue;

View file

@ -1,16 +1,18 @@
use crate::{builder_fn, AutoDefault}; use crate::{builder_fn, AutoDefault};
/// Operaciones disponibles sobre la lista de clases en [`AttrClasses`]. use std::borrow::Cow;
/// Operaciones disponibles sobre la lista de clases en [`Classes`].
pub enum ClassesOp { pub enum ClassesOp {
/// Añade al final (si no existe). /// Añade al final (si no existe).
Add, Add,
/// Añade al principio. /// Añade al principio.
Prepend, Prepend,
/// Elimina coincidencias. /// Elimina la(s) clase(s) indicada(s).
Remove, Remove,
/// Sustituye una o varias por las nuevas (`Replace("old other")`). /// Sustituye una o varias clases por otras nuevas (`Replace("old other".into())`).
Replace(String), Replace(Cow<'static, str>),
/// Alterna presencia/ausencia. /// Alterna presencia/ausencia de una o más clases.
Toggle, Toggle,
/// Sustituye toda la lista. /// Sustituye toda la lista.
Set, Set,
@ -23,34 +25,40 @@ pub enum ClassesOp {
/// ///
/// # Normalización /// # Normalización
/// ///
/// - El [orden de las clases no es relevante](https://stackoverflow.com/a/1321712) en CSS. /// - El [orden de las clases no es relevante](https://stackoverflow.com/a/1321712) en CSS, pero
/// - No se permiten clases duplicadas. /// [`ClassesOp`] ofrece operaciones para controlar su orden de aparición.
/// - Las clases se convierten a minúsculas. /// - Las clases se convierten a minúsculas.
/// - No se permiten clases duplicadas.
/// - Las clases vacías se ignoran. /// - Las clases vacías se ignoran.
/// ///
/// # Ejemplo /// # Ejemplo
/// ///
/// ```rust /// ```rust
/// # use pagetop::prelude::*; /// # use pagetop::prelude::*;
/// let classes = AttrClasses::new("Btn btn-primary") /// let classes = Classes::new("Btn btn-primary")
/// .with_value(ClassesOp::Add, "Active") /// .with_classes(ClassesOp::Add, "Active")
/// .with_value(ClassesOp::Remove, "btn-primary"); /// .with_classes(ClassesOp::Remove, "btn-primary");
/// ///
/// assert_eq!(classes.get(), Some("btn active".to_string())); /// assert_eq!(classes.get(), Some("btn active".to_string()));
/// assert!(classes.contains("active")); /// assert!(classes.contains("active"));
/// ``` /// ```
#[derive(AutoDefault, Clone, Debug)] #[derive(AutoDefault, Clone, Debug)]
pub struct AttrClasses(Vec<String>); pub struct Classes(Vec<String>);
impl AttrClasses { impl Classes {
/// Crea una nueva lista de clases a partir de la clase o clases proporcionadas en `classes`.
pub fn new(classes: impl AsRef<str>) -> Self { pub fn new(classes: impl AsRef<str>) -> Self {
Self::default().with_value(ClassesOp::Prepend, classes) Self::default().with_classes(ClassesOp::Prepend, classes)
} }
// **< AttrClasses BUILDER >******************************************************************** // **< Classes BUILDER >************************************************************************
/// Modifica la lista de clases según la operación indicada.
///
/// Realiza la operación indicada en `op` para las clases proporcionadas en `classes` sobre la
/// lista de clases actual.
#[builder_fn] #[builder_fn]
pub fn with_value(mut self, op: ClassesOp, classes: impl AsRef<str>) -> Self { pub fn with_classes(mut self, op: ClassesOp, classes: impl AsRef<str>) -> Self {
let classes = classes.as_ref().to_ascii_lowercase(); let classes = classes.as_ref().to_ascii_lowercase();
let classes: Vec<&str> = classes.split_ascii_whitespace().collect(); let classes: Vec<&str> = classes.split_ascii_whitespace().collect();
@ -114,7 +122,7 @@ impl AttrClasses {
} }
} }
// **< AttrClasses GETTERS >******************************************************************** // **< Classes GETTERS >************************************************************************
/// Devuelve la cadena de clases, si existe. /// Devuelve la cadena de clases, si existe.
pub fn get(&self) -> Option<String> { pub fn get(&self) -> Option<String> {
@ -127,7 +135,7 @@ impl AttrClasses {
/// Devuelve `true` si la clase está presente. /// Devuelve `true` si la clase está presente.
pub fn contains(&self, class: impl AsRef<str>) -> bool { pub fn contains(&self, class: impl AsRef<str>) -> bool {
let class = class.as_ref(); let class = class.as_ref().to_ascii_lowercase();
self.0.iter().any(|c| c == class) self.0.iter().any(|c| c == &class)
} }
} }

View file

@ -25,7 +25,7 @@ use crate::core::theme::{DefaultRegion, Region, RegionRef, TemplateRef, ThemeRef
use crate::html::{html, Markup, DOCTYPE}; use crate::html::{html, Markup, DOCTYPE};
use crate::html::{Assets, Favicon, JavaScript, StyleSheet}; use crate::html::{Assets, Favicon, JavaScript, StyleSheet};
use crate::html::{Attr, AttrId}; use crate::html::{Attr, AttrId};
use crate::html::{AttrClasses, ClassesOp}; use crate::html::{Classes, ClassesOp};
use crate::locale::{CharacterDirection, L10n, LangId, LanguageIdentifier}; use crate::locale::{CharacterDirection, L10n, LangId, LanguageIdentifier};
use crate::service::HttpRequest; use crate::service::HttpRequest;
use crate::{builder_fn, AutoDefault}; use crate::{builder_fn, AutoDefault};
@ -94,7 +94,7 @@ pub struct Page {
metadata : Vec<(&'static str, &'static str)>, metadata : Vec<(&'static str, &'static str)>,
properties : Vec<(&'static str, &'static str)>, properties : Vec<(&'static str, &'static str)>,
body_id : AttrId, body_id : AttrId,
body_classes: AttrClasses, body_classes: Classes,
context : Context, context : Context,
} }
@ -111,7 +111,7 @@ impl Page {
metadata : Vec::default(), metadata : Vec::default(),
properties : Vec::default(), properties : Vec::default(),
body_id : AttrId::default(), body_id : AttrId::default(),
body_classes: AttrClasses::default(), body_classes: Classes::default(),
context : Context::new(Some(request)), context : Context::new(Some(request)),
} }
} }
@ -153,10 +153,10 @@ impl Page {
self self
} }
/// Modifica las clases CSS del elemento `<body>` con una operación sobre [`AttrClasses`]. /// Modifica las clases CSS del elemento `<body>` con una operación sobre [`Classes`].
#[builder_fn] #[builder_fn]
pub fn with_body_classes(mut self, op: ClassesOp, classes: impl AsRef<str>) -> Self { pub fn with_body_classes(mut self, op: ClassesOp, classes: impl AsRef<str>) -> Self {
self.body_classes.alter_value(op, classes); self.body_classes.alter_classes(op, classes);
self self
} }
@ -204,7 +204,7 @@ impl Page {
} }
/// Devuelve las clases CSS del elemento `<body>`. /// Devuelve las clases CSS del elemento `<body>`.
pub fn body_classes(&self) -> &AttrClasses { pub fn body_classes(&self) -> &Classes {
&self.body_classes &self.body_classes
} }