yew_stdweb/html/
classes.rs1use super::{IntoOptPropValue, IntoPropValue};
2use crate::virtual_dom::AttrValue;
3use indexmap::IndexSet;
4use std::{
5 borrow::{Borrow, Cow},
6 iter::FromIterator,
7};
8
9#[derive(Debug, Clone, Default)]
13pub struct Classes {
14 set: IndexSet<Cow<'static, str>>,
15}
16
17impl Classes {
18 pub fn new() -> Self {
20 Self {
21 set: IndexSet::new(),
22 }
23 }
24
25 pub fn with_capacity(n: usize) -> Self {
28 Self {
29 set: IndexSet::with_capacity(n),
30 }
31 }
32
33 pub fn push<T: Into<Self>>(&mut self, class: T) {
37 let classes_to_add: Self = class.into();
38 self.set.extend(classes_to_add.set);
39 }
40
41 pub unsafe fn unchecked_push<T: Into<Cow<'static, str>>>(&mut self, class: T) {
53 self.set.insert(class.into());
54 }
55
56 pub fn contains<T: AsRef<str>>(&self, class: T) -> bool {
58 self.set.contains(class.as_ref())
59 }
60
61 pub fn is_empty(&self) -> bool {
63 self.set.is_empty()
64 }
65}
66
67impl IntoPropValue<AttrValue> for Classes {
68 fn into_prop_value(mut self) -> AttrValue {
69 if self.set.len() == 1 {
70 self.set.pop().unwrap()
71 } else {
72 Cow::Owned(self.to_string())
73 }
74 }
75}
76
77impl IntoOptPropValue<AttrValue> for Classes {
78 fn into_opt_prop_value(self) -> Option<AttrValue> {
79 if self.is_empty() {
80 None
81 } else {
82 Some(self.into_prop_value())
83 }
84 }
85}
86
87impl<T: Into<Classes>> Extend<T> for Classes {
88 fn extend<I: IntoIterator<Item = T>>(&mut self, iter: I) {
89 let classes = iter
90 .into_iter()
91 .map(Into::into)
92 .flat_map(|classes| classes.set);
93 self.set.extend(classes);
94 }
95}
96
97impl<T: Into<Classes>> FromIterator<T> for Classes {
98 fn from_iter<IT: IntoIterator<Item = T>>(iter: IT) -> Self {
99 let mut classes = Self::new();
100 classes.extend(iter);
101 classes
102 }
103}
104
105impl IntoIterator for Classes {
106 type Item = Cow<'static, str>;
107 type IntoIter = indexmap::set::IntoIter<Cow<'static, str>>;
108
109 fn into_iter(self) -> Self::IntoIter {
110 self.set.into_iter()
111 }
112}
113
114impl ToString for Classes {
115 fn to_string(&self) -> String {
116 self.set
117 .iter()
118 .map(Borrow::borrow)
119 .collect::<Vec<_>>()
120 .join(" ")
121 }
122}
123
124impl From<Cow<'static, str>> for Classes {
125 fn from(t: Cow<'static, str>) -> Self {
126 match t {
127 Cow::Borrowed(x) => Self::from(x),
128 Cow::Owned(x) => Self::from(x),
129 }
130 }
131}
132
133impl From<&'static str> for Classes {
134 fn from(t: &'static str) -> Self {
135 let set = t.split_whitespace().map(Cow::Borrowed).collect();
136 Self { set }
137 }
138}
139
140impl From<String> for Classes {
141 fn from(t: String) -> Self {
142 Self::from(&t)
143 }
144}
145
146impl From<&String> for Classes {
147 fn from(t: &String) -> Self {
148 let set = t
149 .split_whitespace()
150 .map(ToOwned::to_owned)
151 .map(Cow::Owned)
152 .collect();
153 Self { set }
154 }
155}
156
157impl<T: Into<Classes>> From<Option<T>> for Classes {
158 fn from(t: Option<T>) -> Self {
159 t.map(|x| x.into()).unwrap_or_default()
160 }
161}
162
163impl<T: Into<Classes> + Clone> From<&Option<T>> for Classes {
164 fn from(t: &Option<T>) -> Self {
165 Self::from(t.clone())
166 }
167}
168
169impl<T: Into<Classes>> From<Vec<T>> for Classes {
170 fn from(t: Vec<T>) -> Self {
171 Self::from_iter(t)
172 }
173}
174
175impl<T: Into<Classes> + Clone> From<&[T]> for Classes {
176 fn from(t: &[T]) -> Self {
177 Self::from_iter(t.iter().cloned())
178 }
179}
180
181impl PartialEq for Classes {
182 fn eq(&self, other: &Self) -> bool {
183 self.set.len() == other.set.len() && self.set.iter().eq(other.set.iter())
184 }
185}
186
187#[cfg(test)]
188mod tests {
189 use super::*;
190
191 struct TestClass;
192
193 impl TestClass {
194 fn as_class(&self) -> &'static str {
195 "test-class"
196 }
197 }
198
199 impl From<TestClass> for Classes {
200 fn from(x: TestClass) -> Self {
201 Classes::from(x.as_class())
202 }
203 }
204
205 #[test]
206 fn it_is_initially_empty() {
207 let subject = Classes::new();
208 assert!(subject.is_empty());
209 }
210
211 #[test]
212 fn it_pushes_value() {
213 let mut subject = Classes::new();
214 subject.push("foo");
215 assert!(!subject.is_empty());
216 assert!(subject.contains("foo"));
217 }
218
219 #[test]
220 fn it_adds_values_via_extend() {
221 let mut other = Classes::new();
222 other.push("bar");
223 let mut subject = Classes::new();
224 subject.extend(other);
225 assert!(subject.contains("bar"));
226 }
227
228 #[test]
229 fn it_contains_both_values() {
230 let mut other = Classes::new();
231 other.push("bar");
232 let mut subject = Classes::new();
233 subject.extend(other);
234 subject.push("foo");
235 assert!(subject.contains("foo"));
236 assert!(subject.contains("bar"));
237 }
238
239 #[test]
240 fn it_splits_class_with_spaces() {
241 let mut subject = Classes::new();
242 subject.push("foo bar");
243 assert!(subject.contains("foo"));
244 assert!(subject.contains("bar"));
245 }
246
247 #[test]
248 fn push_and_contains_can_be_used_with_other_objects() {
249 let mut subject = Classes::new();
250 subject.push(TestClass);
251 let other_class: Option<TestClass> = None;
252 subject.push(other_class);
253 assert!(subject.contains(TestClass.as_class()));
254 }
255
256 #[test]
257 fn can_be_extended_with_another_class() {
258 let mut other = Classes::new();
259 other.push("foo");
260 other.push("bar");
261 let mut subject = Classes::new();
262 subject.extend(other);
263 assert!(subject.contains("foo"));
264 assert!(subject.contains("bar"));
265 }
266
267 #[test]
268 fn can_be_collected() {
269 let classes = vec!["foo", "bar"];
270 let subject = classes.into_iter().collect::<Classes>();
271 assert!(subject.contains("foo"));
272 assert!(subject.contains("bar"));
273 }
274}