diff --git a/src/element.rs b/src/element.rs index 1bafc4f..b77f566 100644 --- a/src/element.rs +++ b/src/element.rs @@ -1,4 +1,4 @@ -use std::collections::{BTreeMap, HashMap}; +use std::collections::{btree_map::Entry, BTreeMap, HashMap}; /// #[derive(Clone, Copy, PartialEq, Eq)] @@ -101,28 +101,76 @@ pub trait ElementComponent { pub struct Attr { name: String, value: String, + append_by: Option, } impl Attr { + /// Create or replace an attribute. + /// + /// When this attribute is added to an [`Element`] through + /// [`ElementComponent::add_to_element`] and an attribute of the same name + /// already exists, it replaces that attribute's value. pub fn new(name: impl ToString, value: impl ToString) -> Self { Self { name: name.to_string(), value: value.to_string(), + append_by: None, } } + /// Create or append to an attribute. + /// + /// When this attribute is added to an [`Element`] through + /// [`ElementComponent::add_to_element`] and an attribute of the same name + /// already exists, it appends the separator and then its own value to that + /// attribute's value. + pub fn append(name: impl ToString, value: impl ToString, separator: impl ToString) -> Self { + Self { + name: name.to_string(), + value: value.to_string(), + append_by: Some(separator.to_string()), + } + } + + /// Create (or replace) a new empty attribute. + /// + /// `Attr::yes(name)` is equivalent to `Attr::new(name, "").` + /// + /// When rendering an empty attribute as HTML, the value can be omitted: + /// `name=""` is equivalent to just `name`. pub fn yes(name: impl ToString) -> Self { Self::new(name, "") } + /// Create (or replace) an `id` attribute. + /// + /// `Attr::id(id)` is equivalent to `Attr::new("id", id)`. pub fn id(id: impl ToString) -> Self { Self::new("id", id) } + /// Create (or append) to a `class` attribute. + /// + /// `Attr::class(class)` is equivalent to + /// `Attr::append("class", class, " ")`. pub fn class(class: impl ToString) -> Self { - Self::new("class", class) + Self::append("class", class, " ") } + /// Create (or append) to a `style` attribute. + /// + /// `Attr::style(style)` is equivalent to + /// `Attr::append("style", style, ";")`. + pub fn style(style: impl ToString) -> Self { + Self::append("style", style, ";") + } + + /// Create (or replace) a new [`data-*` attribute][mdn]. + /// + /// `Attr::data(name, value)` is equivalent to + /// `Attr::new(format!("data-{name}"), value)`. + /// + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/data-* pub fn data(name: impl ToString, value: impl ToString) -> Self { Self::new(format!("data-{}", name.to_string()), value) } @@ -133,7 +181,21 @@ impl ElementComponent for Attr { if element.kind != ElementKind::Foreign { self.name = self.name.to_ascii_lowercase(); } - element.attributes.insert(self.name, self.value); + match element.attributes.entry(self.name) { + Entry::Vacant(entry) => { + entry.insert(self.value); + } + Entry::Occupied(mut entry) => match self.append_by { + None => { + entry.insert(self.value); + } + Some(sep) => { + let value = entry.get_mut(); + value.push_str(&sep); + value.push_str(&self.value); + } + }, + } } }