1use std::borrow::Cow;
2use std::iter::FromIterator;
3use std::rc::Rc;
4
5use implicit_clone::ImplicitClone;
6use indexmap::IndexSet;
7
8use super::IntoPropValue;
9use crate::utils::RcExt;
10use crate::virtual_dom::AttrValue;
11
12#[derive(Debug, Clone, Default)]
16pub struct Classes {
17 set: Rc<IndexSet<AttrValue>>,
18}
19
20impl ImplicitClone for Classes {}
21
22fn build_attr_value(first: AttrValue, rest: impl Iterator<Item = AttrValue> + Clone) -> AttrValue {
27 let mut s = String::with_capacity(
30 rest.clone()
31 .map(|class| class.len())
32 .chain([first.len(), rest.size_hint().0])
33 .sum(),
34 );
35
36 s.push_str(first.as_str());
37 for class in rest {
39 s.push(' ');
40 s.push_str(class.as_str());
41 }
42 s.into()
43}
44
45impl Classes {
46 #[inline]
48 pub fn new() -> Self {
49 Self {
50 set: Rc::new(IndexSet::new()),
51 }
52 }
53
54 #[inline]
57 pub fn with_capacity(n: usize) -> Self {
58 Self {
59 set: Rc::new(IndexSet::with_capacity(n)),
60 }
61 }
62
63 pub fn push<T: Into<Self>>(&mut self, class: T) {
67 let classes_to_add: Self = class.into();
68 if self.is_empty() {
69 *self = classes_to_add
70 } else {
71 Rc::make_mut(&mut self.set).extend(classes_to_add.set.iter().cloned())
72 }
73 }
74
75 pub unsafe fn unchecked_push<T: Into<AttrValue>>(&mut self, class: T) {
87 Rc::make_mut(&mut self.set).insert(class.into());
88 }
89
90 #[inline]
92 pub fn contains<T: AsRef<str>>(&self, class: T) -> bool {
93 self.set.contains(class.as_ref())
94 }
95
96 #[inline]
98 pub fn is_empty(&self) -> bool {
99 self.set.is_empty()
100 }
101}
102
103impl IntoPropValue<AttrValue> for Classes {
104 #[inline]
105 fn into_prop_value(self) -> AttrValue {
106 let mut classes = self.set.iter().cloned();
107
108 match classes.next() {
109 None => AttrValue::Static(""),
110 Some(class) if classes.len() == 0 => class,
111 Some(first) => build_attr_value(first, classes),
112 }
113 }
114}
115
116impl IntoPropValue<Option<AttrValue>> for Classes {
117 #[inline]
118 fn into_prop_value(self) -> Option<AttrValue> {
119 if self.is_empty() {
120 None
121 } else {
122 Some(self.into_prop_value())
123 }
124 }
125}
126
127impl IntoPropValue<Classes> for &'static str {
128 #[inline]
129 fn into_prop_value(self) -> Classes {
130 self.into()
131 }
132}
133
134impl<T: Into<Classes>> Extend<T> for Classes {
135 fn extend<I: IntoIterator<Item = T>>(&mut self, iter: I) {
136 iter.into_iter().for_each(|classes| self.push(classes))
137 }
138}
139
140impl<T: Into<Classes>> FromIterator<T> for Classes {
141 fn from_iter<IT: IntoIterator<Item = T>>(iter: IT) -> Self {
142 let mut classes = Self::new();
143 classes.extend(iter);
144 classes
145 }
146}
147
148impl IntoIterator for Classes {
149 type IntoIter = indexmap::set::IntoIter<AttrValue>;
150 type Item = AttrValue;
151
152 #[inline]
153 fn into_iter(self) -> Self::IntoIter {
154 RcExt::unwrap_or_clone(self.set).into_iter()
155 }
156}
157
158impl IntoIterator for &Classes {
159 type IntoIter = indexmap::set::IntoIter<AttrValue>;
160 type Item = AttrValue;
161
162 #[inline]
163 fn into_iter(self) -> Self::IntoIter {
164 (*self.set).clone().into_iter()
165 }
166}
167
168#[allow(clippy::to_string_trait_impl)]
169impl ToString for Classes {
170 fn to_string(&self) -> String {
171 let mut iter = self.set.iter().cloned();
172
173 iter.next()
174 .map(|first| build_attr_value(first, iter))
175 .unwrap_or_default()
176 .to_string()
177 }
178}
179
180impl From<Cow<'static, str>> for Classes {
181 fn from(t: Cow<'static, str>) -> Self {
182 match t {
183 Cow::Borrowed(x) => Self::from(x),
184 Cow::Owned(x) => Self::from(x),
185 }
186 }
187}
188
189impl From<&'static str> for Classes {
190 fn from(t: &'static str) -> Self {
191 let set = t.split_whitespace().map(AttrValue::Static).collect();
192 Self { set: Rc::new(set) }
193 }
194}
195
196impl From<String> for Classes {
197 fn from(t: String) -> Self {
198 match t.contains(|c: char| c.is_whitespace()) {
199 false => match t.is_empty() {
203 true => Self::new(),
204 false => Self {
205 set: Rc::new(IndexSet::from_iter([AttrValue::from(t)])),
206 },
207 },
208 true => Self::from(&t),
209 }
210 }
211}
212
213impl From<&String> for Classes {
214 fn from(t: &String) -> Self {
215 let set = t
216 .split_whitespace()
217 .map(ToOwned::to_owned)
218 .map(AttrValue::from)
219 .collect();
220 Self { set: Rc::new(set) }
221 }
222}
223
224impl From<&AttrValue> for Classes {
225 fn from(t: &AttrValue) -> Self {
226 let set = t
227 .split_whitespace()
228 .map(ToOwned::to_owned)
229 .map(AttrValue::from)
230 .collect();
231 Self { set: Rc::new(set) }
232 }
233}
234
235impl From<AttrValue> for Classes {
236 fn from(t: AttrValue) -> Self {
237 match t.contains(|c: char| c.is_whitespace()) {
238 false => match t.is_empty() {
242 true => Self::new(),
243 false => Self {
244 set: Rc::new(IndexSet::from_iter([t])),
245 },
246 },
247 true => Self::from(&t),
248 }
249 }
250}
251
252impl<T: Into<Classes>> From<Option<T>> for Classes {
253 fn from(t: Option<T>) -> Self {
254 t.map(|x| x.into()).unwrap_or_default()
255 }
256}
257
258impl<T: Into<Classes> + Clone> From<&Option<T>> for Classes {
259 fn from(t: &Option<T>) -> Self {
260 Self::from(t.clone())
261 }
262}
263
264impl<T: Into<Classes>> From<Vec<T>> for Classes {
265 fn from(t: Vec<T>) -> Self {
266 Self::from_iter(t)
267 }
268}
269
270impl<T: Into<Classes> + Clone> From<&[T]> for Classes {
271 fn from(t: &[T]) -> Self {
272 t.iter().cloned().collect()
273 }
274}
275
276impl<T: Into<Classes>, const SIZE: usize> From<[T; SIZE]> for Classes {
277 fn from(t: [T; SIZE]) -> Self {
278 t.into_iter().collect()
279 }
280}
281
282impl PartialEq for Classes {
283 fn eq(&self, other: &Self) -> bool {
284 self.set.len() == other.set.len() && self.set.iter().eq(other.set.iter())
285 }
286}
287
288impl Eq for Classes {}
289
290#[cfg(test)]
291mod tests {
292 use super::*;
293
294 struct TestClass;
295
296 impl TestClass {
297 fn as_class(&self) -> &'static str {
298 "test-class"
299 }
300 }
301
302 impl From<TestClass> for Classes {
303 fn from(x: TestClass) -> Self {
304 Classes::from(x.as_class())
305 }
306 }
307
308 #[test]
309 fn it_is_initially_empty() {
310 let subject = Classes::new();
311 assert!(subject.is_empty());
312 }
313
314 #[test]
315 fn it_pushes_value() {
316 let mut subject = Classes::new();
317 subject.push("foo");
318 assert!(!subject.is_empty());
319 assert!(subject.contains("foo"));
320 }
321
322 #[test]
323 fn it_adds_values_via_extend() {
324 let mut other = Classes::new();
325 other.push("bar");
326 let mut subject = Classes::new();
327 subject.extend(other);
328 assert!(subject.contains("bar"));
329 }
330
331 #[test]
332 fn it_contains_both_values() {
333 let mut other = Classes::new();
334 other.push("bar");
335 let mut subject = Classes::new();
336 subject.extend(other);
337 subject.push("foo");
338 assert!(subject.contains("foo"));
339 assert!(subject.contains("bar"));
340 }
341
342 #[test]
343 fn it_splits_class_with_spaces() {
344 let mut subject = Classes::new();
345 subject.push("foo bar");
346 assert!(subject.contains("foo"));
347 assert!(subject.contains("bar"));
348 }
349
350 #[test]
351 fn push_and_contains_can_be_used_with_other_objects() {
352 let mut subject = Classes::new();
353 subject.push(TestClass);
354 let other_class: Option<TestClass> = None;
355 subject.push(other_class);
356 assert!(subject.contains(TestClass.as_class()));
357 }
358
359 #[test]
360 fn can_be_extended_with_another_class() {
361 let mut other = Classes::new();
362 other.push("foo");
363 other.push("bar");
364 let mut subject = Classes::new();
365 subject.extend(&other);
366 subject.extend(other);
367 assert!(subject.contains("foo"));
368 assert!(subject.contains("bar"));
369 }
370
371 #[test]
372 fn can_be_collected() {
373 let classes = vec!["foo", "bar"];
374 let subject = classes.into_iter().collect::<Classes>();
375 assert!(subject.contains("foo"));
376 assert!(subject.contains("bar"));
377 }
378
379 #[test]
380 fn ignores_empty_string() {
381 let classes = String::from("");
382 let subject = Classes::from(classes);
383 assert!(subject.is_empty())
384 }
385}