⚡️ Mejora operaciones con clases y documentación
This commit is contained in:
parent
e9565bf70b
commit
00d4de840b
1 changed files with 70 additions and 49 deletions
|
|
@ -3,33 +3,44 @@ use crate::{builder_fn, AutoDefault};
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
|
|
||||||
/// Operaciones disponibles sobre la lista de clases en [`Classes`].
|
/// Operaciones disponibles sobre la lista de clases en [`Classes`].
|
||||||
|
///
|
||||||
|
/// Cada variante opera sobre **una o más clases** proporcionadas como una cadena separada por
|
||||||
|
/// espacios (p. ej. `"btn active"`), que se normalizan internamente a minúsculas en
|
||||||
|
/// [`Classes::with_classes()`].
|
||||||
pub enum ClassesOp {
|
pub enum ClassesOp {
|
||||||
/// Añade al final (si no existe).
|
/// Añade las clases que no existan al final.
|
||||||
Add,
|
Add,
|
||||||
/// Añade al principio.
|
/// Añade las clases que no existan al principio.
|
||||||
Prepend,
|
Prepend,
|
||||||
/// Elimina la(s) clase(s) indicada(s).
|
/// Elimina las clases indicadas que existan.
|
||||||
Remove,
|
Remove,
|
||||||
/// Sustituye una o varias clases por otras nuevas (`Replace("old other".into())`).
|
/// Sustituye una o varias clases existentes (indicadas en la variante) por las clases
|
||||||
|
/// proporcionadas.
|
||||||
Replace(Cow<'static, str>),
|
Replace(Cow<'static, str>),
|
||||||
/// Alterna presencia/ausencia de una o más clases.
|
/// Alterna presencia/ausencia de una o más clases.
|
||||||
|
///
|
||||||
|
/// Si en una misma llamada se repite una clase (p. ej. `"a a"`) que ya existe, el resultado
|
||||||
|
/// mantiene la pertenencia pero puede cambiar el orden (primero se elimina y luego se añade al
|
||||||
|
/// final).
|
||||||
Toggle,
|
Toggle,
|
||||||
/// Sustituye toda la lista.
|
/// Sustituye la lista completa por las clases indicadas.
|
||||||
Set,
|
Set,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Cadena de clases CSS normalizadas para el atributo `class` de HTML.
|
/// Lista de clases CSS normalizadas para el atributo `class` de HTML.
|
||||||
///
|
///
|
||||||
/// Permite construir y modificar dinámicamente con [`ClassesOp`] una lista de clases CSS
|
/// Permite construir y modificar dinámicamente con [`ClassesOp`] una lista de clases CSS
|
||||||
/// normalizadas.
|
/// normalizadas.
|
||||||
///
|
///
|
||||||
/// # Normalización
|
/// # Normalización
|
||||||
///
|
///
|
||||||
/// - El [orden de las clases no es relevante](https://stackoverflow.com/a/1321712) en CSS, pero
|
/// - Aunque el orden de las clases en el atributo `class` no afecta al resultado en CSS,
|
||||||
/// [`ClassesOp`] ofrece operaciones para controlar su orden de aparición.
|
/// [`ClassesOp`] ofrece operaciones para controlar su orden de aparición por legibilidad.
|
||||||
/// - Las clases se convierten a minúsculas.
|
/// - Las clases se almacenan en minúsculas.
|
||||||
/// - No se permiten clases duplicadas.
|
/// - No se permiten clases duplicadas tras la normalización (por ejemplo, `Btn` y `btn` se
|
||||||
|
/// consideran la misma clase).
|
||||||
/// - Las clases vacías se ignoran.
|
/// - Las clases vacías se ignoran.
|
||||||
|
/// - Sin clases, [`get()`](Self::get) devuelve `None` (no `Some("")`).
|
||||||
///
|
///
|
||||||
/// # Ejemplo
|
/// # Ejemplo
|
||||||
///
|
///
|
||||||
|
|
@ -59,53 +70,62 @@ impl Classes {
|
||||||
/// lista de clases actual.
|
/// lista de clases actual.
|
||||||
#[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 {
|
||||||
let classes = classes.as_ref().to_ascii_lowercase();
|
let classes = classes.as_ref();
|
||||||
let classes: Vec<&str> = classes.split_ascii_whitespace().collect();
|
|
||||||
|
|
||||||
if classes.is_empty() {
|
|
||||||
return self;
|
|
||||||
}
|
|
||||||
|
|
||||||
match op {
|
match op {
|
||||||
ClassesOp::Add => {
|
ClassesOp::Add => {
|
||||||
self.add(&classes, self.0.len());
|
self.add(classes, self.0.len());
|
||||||
}
|
}
|
||||||
ClassesOp::Prepend => {
|
ClassesOp::Prepend => {
|
||||||
self.add(&classes, 0);
|
self.add(classes, 0);
|
||||||
}
|
}
|
||||||
ClassesOp::Remove => {
|
ClassesOp::Remove => {
|
||||||
for class in classes {
|
let mut classes_to_remove = classes.split_ascii_whitespace();
|
||||||
self.0.retain(|c| c != class);
|
|
||||||
|
// 0 clases: no se hace nada.
|
||||||
|
let Some(first) = classes_to_remove.next() else {
|
||||||
|
return self;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 1 clase: un único *retain*, cero reservas extra.
|
||||||
|
let first = first.to_ascii_lowercase();
|
||||||
|
let Some(second) = classes_to_remove.next() else {
|
||||||
|
self.0.retain(|c| c != &first);
|
||||||
|
return self;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 2+ clases: se construye lista para borrar y un único *retain*.
|
||||||
|
let mut to_remove = Vec::new();
|
||||||
|
to_remove.push(first);
|
||||||
|
to_remove.push(second.to_ascii_lowercase());
|
||||||
|
for class in classes_to_remove {
|
||||||
|
to_remove.push(class.to_ascii_lowercase());
|
||||||
}
|
}
|
||||||
|
self.0.retain(|c| !to_remove.iter().any(|r| r == c));
|
||||||
}
|
}
|
||||||
ClassesOp::Replace(classes_to_replace) => {
|
ClassesOp::Replace(classes_to_replace) => {
|
||||||
let mut pos = self.0.len();
|
let mut pos = self.0.len();
|
||||||
let replace = classes_to_replace.to_ascii_lowercase();
|
for class in classes_to_replace.split_ascii_whitespace() {
|
||||||
let replace: Vec<&str> = replace.split_ascii_whitespace().collect();
|
let class = class.to_ascii_lowercase();
|
||||||
for class in replace {
|
if let Some(replace_pos) = self.0.iter().position(|c| c == &class) {
|
||||||
if let Some(replace_pos) = self.0.iter().position(|c| c == class) {
|
|
||||||
self.0.remove(replace_pos);
|
self.0.remove(replace_pos);
|
||||||
if pos > replace_pos {
|
pos = pos.min(replace_pos);
|
||||||
pos = replace_pos;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.add(&classes, pos);
|
self.add(classes, pos);
|
||||||
}
|
}
|
||||||
ClassesOp::Toggle => {
|
ClassesOp::Toggle => {
|
||||||
for class in classes {
|
for class in classes.split_ascii_whitespace() {
|
||||||
if !class.is_empty() {
|
let class = class.to_ascii_lowercase();
|
||||||
if let Some(pos) = self.0.iter().position(|c| c.eq(class)) {
|
if let Some(pos) = self.0.iter().position(|c| c == &class) {
|
||||||
self.0.remove(pos);
|
self.0.remove(pos);
|
||||||
} else {
|
} else {
|
||||||
self.0.push(class.to_string());
|
self.0.push(class);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ClassesOp::Set => {
|
ClassesOp::Set => {
|
||||||
self.0.clear();
|
self.0.clear();
|
||||||
self.add(&classes, 0);
|
self.add(classes, 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -113,10 +133,16 @@ impl Classes {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn add(&mut self, classes: &[&str], mut pos: usize) {
|
fn add(&mut self, classes: &str, mut pos: usize) {
|
||||||
for &class in classes {
|
for class in classes.split_ascii_whitespace() {
|
||||||
if !class.is_empty() && !self.0.iter().any(|c| c == class) {
|
let class = class.to_ascii_lowercase();
|
||||||
self.0.insert(pos, class.to_string());
|
// Inserción segura descartando duplicados.
|
||||||
|
if !self.0.iter().any(|c| c == &class) {
|
||||||
|
if pos >= self.0.len() {
|
||||||
|
self.0.push(class);
|
||||||
|
} else {
|
||||||
|
self.0.insert(pos, class);
|
||||||
|
}
|
||||||
pos += 1;
|
pos += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -135,8 +161,8 @@ impl Classes {
|
||||||
|
|
||||||
/// Devuelve `true` si **una única clase** está presente.
|
/// Devuelve `true` si **una única clase** está presente.
|
||||||
///
|
///
|
||||||
/// Si necesitas comprobar varias clases separadas por espacios, usa [`contains_all`] o
|
/// Si necesitas comprobar varias clases, usa [`contains_all()`](Self::contains_all) o
|
||||||
/// [`contains_any`].
|
/// [`contains_any()`](Self::contains_any).
|
||||||
pub fn contains(&self, class: impl AsRef<str>) -> bool {
|
pub fn contains(&self, class: impl AsRef<str>) -> bool {
|
||||||
self.contains_class(class.as_ref())
|
self.contains_class(class.as_ref())
|
||||||
}
|
}
|
||||||
|
|
@ -159,11 +185,6 @@ impl Classes {
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn contains_class(&self, class: &str) -> bool {
|
fn contains_class(&self, class: &str) -> bool {
|
||||||
if class.bytes().any(|b| b.is_ascii_uppercase()) {
|
self.0.iter().any(|c| c.eq_ignore_ascii_case(class))
|
||||||
let class = class.to_ascii_lowercase();
|
|
||||||
self.0.iter().any(|c| c == &class)
|
|
||||||
} else {
|
|
||||||
self.0.iter().any(|c| c == class)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue