diff --git a/packages/yew/src/html/classes.rs b/packages/yew/src/html/classes.rs
index 5311ca6e163..a2805be532b 100644
--- a/packages/yew/src/html/classes.rs
+++ b/packages/yew/src/html/classes.rs
@@ -1,5 +1,4 @@
use std::borrow::{Borrow, Cow};
-use std::hint::unreachable_unchecked;
use std::iter::FromIterator;
use std::rc::Rc;
@@ -16,8 +15,28 @@ pub struct Classes {
set: IndexSet>,
}
+/// helper method to efficiently turn a set of classes into a space-separated
+/// string. Abstracts differences between ToString and IntoPropValue. The
+/// `rest` iterator is cloned to pre-compute the length of the String; it
+/// should be cheap to clone.
+fn build_string<'a>(first: &'a str, rest: impl Iterator + Clone) -> String {
+ // The length of the string is known to be the length of all the
+ // components, plus one space for each element in `rest`.
+ let mut s = String::with_capacity(
+ rest.clone()
+ .map(|class| class.len())
+ .chain([first.len(), rest.size_hint().0])
+ .sum(),
+ );
+
+ s.push_str(first);
+ s.extend(rest.flat_map(|class| [" ", class]));
+ s
+}
+
impl Classes {
/// Creates an empty set of classes. (Does not allocate.)
+ #[inline]
pub fn new() -> Self {
Self {
set: IndexSet::new(),
@@ -26,6 +45,7 @@ impl Classes {
/// Creates an empty set of classes with capacity for n elements. (Does not allocate if n is
/// zero.)
+ #[inline]
pub fn with_capacity(n: usize) -> Self {
Self {
set: IndexSet::with_capacity(n),
@@ -37,7 +57,11 @@ impl Classes {
/// If the provided class has already been added, this method will ignore it.
pub fn push>(&mut self, class: T) {
let classes_to_add: Self = class.into();
- self.set.extend(classes_to_add.set);
+ if self.is_empty() {
+ *self = classes_to_add
+ } else {
+ self.set.extend(classes_to_add.set)
+ }
}
/// Adds a class to a set.
@@ -49,18 +73,20 @@ impl Classes {
/// # Safety
///
/// This function will not split the string into multiple classes. Please do not use it unless
- /// you are absolutely certain that the string does not contain any whitespace. Using `push()`
- /// is preferred.
+ /// you are absolutely certain that the string does not contain any whitespace and it is not
+ /// empty. Using `push()` is preferred.
pub unsafe fn unchecked_push>>(&mut self, class: T) {
self.set.insert(class.into());
}
/// Check the set contains a class.
+ #[inline]
pub fn contains>(&self, class: T) -> bool {
self.set.contains(class.as_ref())
}
/// Check the set is empty.
+ #[inline]
pub fn is_empty(&self) -> bool {
self.set.is_empty()
}
@@ -68,15 +94,16 @@ impl Classes {
impl IntoPropValue for Classes {
#[inline]
- fn into_prop_value(mut self) -> AttrValue {
- if self.set.len() == 1 {
- match self.set.pop() {
- Some(attr) => AttrValue::Rc(Rc::from(attr)),
- // SAFETY: the collection is checked to be non-empty above
- None => unsafe { unreachable_unchecked() },
- }
- } else {
- AttrValue::Rc(Rc::from(self.to_string()))
+ fn into_prop_value(self) -> AttrValue {
+ let mut classes = self.set.iter();
+
+ match classes.next() {
+ None => AttrValue::Static(""),
+ Some(class) if classes.len() == 0 => match *class {
+ Cow::Borrowed(class) => AttrValue::Static(class),
+ Cow::Owned(ref class) => AttrValue::Rc(Rc::from(class.as_str())),
+ },
+ Some(first) => AttrValue::Rc(Rc::from(build_string(first, classes.map(Cow::borrow)))),
}
}
}
@@ -93,6 +120,7 @@ impl IntoPropValue