✨ (html): Añade PropsOp::replace_classes()
Permite sustituir una o varias clases por otras nuevas en una sola operación, preservando su posición. Adapta el tema Bootsier para usarla en `Button` y elimina el handler duplicado sin referencias.
This commit is contained in:
parent
9e8cd77140
commit
bff7cf1b38
4 changed files with 98 additions and 9 deletions
|
|
@ -1,7 +0,0 @@
|
|||
use pagetop::prelude::*;
|
||||
|
||||
pub fn setup(button: &mut Button) {
|
||||
button
|
||||
.alter_prop(PropsOp::remove_classes("button"))
|
||||
.alter_prop(PropsOp::prepend_classes("btn"));
|
||||
}
|
||||
|
|
@ -1 +1,9 @@
|
|||
use pagetop::prelude::*;
|
||||
|
||||
pub use pagetop::base::component::{Button, ButtonAction};
|
||||
|
||||
// **< Button SETUP >*******************************************************************************
|
||||
|
||||
pub(crate) fn setup(button: &mut Button) {
|
||||
button.alter_prop(PropsOp::replace_classes("button", "btn"));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -112,6 +112,11 @@ pub enum PropsOp {
|
|||
/// Añade la clase o clases que no existan al principio de la lista. La operación se ignora si
|
||||
/// el valor contiene caracteres no ASCII.
|
||||
PrependClasses(CowStr),
|
||||
/// Sustituye una o varias clases existentes (primer valor) por las clases indicadas (segundo
|
||||
/// valor), insertando las nuevas en la posición de la primera clase sustituida encontrada. Si
|
||||
/// ninguna de las clases a sustituir existe, la operación no tiene efecto. Se ignora si alguno
|
||||
/// de los dos valores contiene caracteres no ASCII.
|
||||
ReplaceClasses(CowStr, CowStr),
|
||||
/// Elimina la clase o clases indicadas de la lista. La operación se ignora si el valor contiene
|
||||
/// caracteres no ASCII.
|
||||
RemoveClasses(CowStr),
|
||||
|
|
@ -151,6 +156,19 @@ impl PropsOp {
|
|||
Self::PrependClasses(classes.into())
|
||||
}
|
||||
|
||||
/// Crea la variante [`ReplaceClasses`](Self::ReplaceClasses) con las clases a sustituir (`old`)
|
||||
/// y las nuevas clases (`new`).
|
||||
///
|
||||
/// ```rust
|
||||
/// # use pagetop::prelude::*;
|
||||
/// let props = Props::classes("button primary")
|
||||
/// .with_prop(PropsOp::replace_classes("button", "btn"));
|
||||
/// assert_eq!(props.get_classes(), Some("btn primary".to_string()));
|
||||
/// ```
|
||||
pub fn replace_classes(old: impl Into<CowStr>, new: impl Into<CowStr>) -> Self {
|
||||
Self::ReplaceClasses(old.into(), new.into())
|
||||
}
|
||||
|
||||
/// Crea la variante [`RemoveClasses`](Self::RemoveClasses) con la clase o clases indicadas.
|
||||
pub fn remove_classes(classes: impl Into<CowStr>) -> Self {
|
||||
Self::RemoveClasses(classes.into())
|
||||
|
|
@ -251,10 +269,11 @@ impl PropsOp {
|
|||
/// # use pagetop::prelude::*;
|
||||
/// let props = Props::default()
|
||||
/// .with_prop(PropsOp::add_classes("btn btn-primary"))
|
||||
/// .with_prop(PropsOp::add_classes("active"));
|
||||
/// .with_prop(PropsOp::add_classes("active"))
|
||||
/// .with_prop(PropsOp::replace_classes("btn-primary", "btn-secondary"));
|
||||
///
|
||||
/// let markup = html! { button (props) { "OK" } };
|
||||
/// assert_eq!(markup.into_string(), r#"<button class="btn btn-primary active">OK</button>"#);
|
||||
/// assert_eq!(markup.into_string(), r#"<button class="btn btn-secondary active">OK</button>"#);
|
||||
/// ```
|
||||
///
|
||||
/// # Valores extra
|
||||
|
|
@ -354,6 +373,8 @@ impl Props {
|
|||
/// - [`PropsOp::ensure_id()`] - establece el identificador sólo si no hay ninguno definido.
|
||||
/// - [`PropsOp::add_classes()`] - añade clases al final (sin duplicados).
|
||||
/// - [`PropsOp::prepend_classes()`] - añade clases al principio (sin duplicados).
|
||||
/// - [`PropsOp::replace_classes()`] - sustituye una o varias clases existentes por otras
|
||||
/// nuevas, preservando su posición.
|
||||
/// - [`PropsOp::remove_classes()`] - elimina las clases indicadas.
|
||||
/// - [`PropsOp::set()`] - añade el atributo o reemplaza su valor. `set("id", ...)` aplica la
|
||||
/// misma normalización que `set_id()`. `set("class", ...)` reemplaza la lista de clases.
|
||||
|
|
@ -389,6 +410,28 @@ impl Props {
|
|||
};
|
||||
self.insert_classes(normalized.as_ref().split_ascii_whitespace(), 0);
|
||||
}
|
||||
PropsOp::ReplaceClasses(old, new) => {
|
||||
let Some(old) = util::normalize_ascii_or_empty(old.as_ref(), "Props::with_prop")
|
||||
else {
|
||||
return self;
|
||||
};
|
||||
let Some(new) = util::normalize_ascii_or_empty(new.as_ref(), "Props::with_prop")
|
||||
else {
|
||||
return self;
|
||||
};
|
||||
let mut pos = self.classes.len();
|
||||
let mut replaced = false;
|
||||
for class in old.as_ref().split_ascii_whitespace() {
|
||||
if let Some(replace_pos) = self.classes.iter().position(|c| c == class) {
|
||||
self.classes.remove(replace_pos);
|
||||
pos = pos.min(replace_pos);
|
||||
replaced = true;
|
||||
}
|
||||
}
|
||||
if replaced {
|
||||
self.insert_classes(new.as_ref().split_ascii_whitespace(), pos);
|
||||
}
|
||||
}
|
||||
PropsOp::RemoveClasses(classes) => {
|
||||
let Some(normalized) =
|
||||
util::normalize_ascii_or_empty(classes.as_ref(), "Props::with_prop")
|
||||
|
|
|
|||
|
|
@ -74,6 +74,51 @@ async fn classes_prepend_ignores_empty_input() {
|
|||
assert_classes(&p, Some("a b"));
|
||||
}
|
||||
|
||||
// **< PropsOp::replace_classes >********************************************************************
|
||||
|
||||
#[pagetop::test]
|
||||
async fn classes_replace_removes_targets_and_inserts_new_at_min_position() {
|
||||
let p = Props::classes("a b c d").with_prop(PropsOp::replace_classes("c a", "x y"));
|
||||
assert_classes(&p, Some("x y b d"));
|
||||
}
|
||||
|
||||
#[pagetop::test]
|
||||
async fn classes_replace_when_none_found_does_nothing() {
|
||||
let p = Props::classes("a b").with_prop(PropsOp::replace_classes("x y", "c d"));
|
||||
assert_classes(&p, Some("a b"));
|
||||
}
|
||||
|
||||
#[pagetop::test]
|
||||
async fn classes_replace_is_case_insensitive_on_targets_and_new_values_are_normalized() {
|
||||
let p = Props::classes("btn btn-primary active")
|
||||
.with_prop(PropsOp::replace_classes("BTN-PRIMARY", "Btn-Secondary"));
|
||||
assert_classes(&p, Some("btn btn-secondary active"));
|
||||
}
|
||||
|
||||
#[pagetop::test]
|
||||
async fn classes_replace_with_empty_new_removes_only() {
|
||||
let p = Props::classes("a b c").with_prop(PropsOp::replace_classes("b", " "));
|
||||
assert_classes(&p, Some("a c"));
|
||||
}
|
||||
|
||||
#[pagetop::test]
|
||||
async fn classes_replace_dedups_against_existing_items() {
|
||||
let p = Props::classes("a b c").with_prop(PropsOp::replace_classes("b", "c d"));
|
||||
assert_classes(&p, Some("a d c"));
|
||||
}
|
||||
|
||||
#[pagetop::test]
|
||||
async fn classes_replace_ignores_target_whitespace_and_repetition() {
|
||||
let p = Props::classes("a b c").with_prop(PropsOp::replace_classes(" b b ", "x y"));
|
||||
assert_classes(&p, Some("a x y c"));
|
||||
}
|
||||
|
||||
#[pagetop::test]
|
||||
async fn classes_replace_rejects_non_ascii_targets_is_noop() {
|
||||
let p = Props::classes("a b c").with_prop(PropsOp::replace_classes("b ñ", "x"));
|
||||
assert_classes(&p, Some("a b c"));
|
||||
}
|
||||
|
||||
// **< PropsOp::set / remove ("class") >************************************************************
|
||||
|
||||
#[pagetop::test]
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue