1use super::attribute::{
2 maybe_next_attr_erasure_macros::next_attr_output_type, Attribute,
3 NamedAttributeKey, NextAttribute,
4};
5use crate::{
6 html::attribute::maybe_next_attr_erasure_macros::next_attr_combine,
7 renderer::Rndr,
8 view::{Position, ToTemplate},
9};
10use std::{borrow::Cow, future::Future, sync::Arc};
11
12#[inline(always)]
14pub fn class<C>(class: C) -> Class<C>
15where
16 C: IntoClass,
17{
18 Class { class }
19}
20
21#[derive(Debug)]
23pub struct Class<C> {
24 class: C,
25}
26
27impl<C> Clone for Class<C>
28where
29 C: Clone,
30{
31 fn clone(&self) -> Self {
32 Self {
33 class: self.class.clone(),
34 }
35 }
36}
37
38impl<C> Attribute for Class<C>
39where
40 C: IntoClass,
41{
42 const MIN_LENGTH: usize = C::MIN_LENGTH;
43
44 type AsyncOutput = Class<C::AsyncOutput>;
45 type State = C::State;
46 type Cloneable = Class<C::Cloneable>;
47 type CloneableOwned = Class<C::CloneableOwned>;
48
49 fn html_len(&self) -> usize {
50 self.class.html_len() + 1
51 }
52
53 fn to_html(
54 self,
55 _buf: &mut String,
56 class: &mut String,
57 _style: &mut String,
58 _inner_html: &mut String,
59 ) {
60 if self.class.should_overwrite() {
62 class.clear();
63 }
64 class.push(' ');
65 self.class.to_html(class);
66 }
67
68 fn hydrate<const FROM_SERVER: bool>(
69 self,
70 el: &crate::renderer::types::Element,
71 ) -> Self::State {
72 self.class.hydrate::<FROM_SERVER>(el)
73 }
74
75 fn build(self, el: &crate::renderer::types::Element) -> Self::State {
76 self.class.build(el)
77 }
78
79 fn rebuild(self, state: &mut Self::State) {
80 self.class.rebuild(state)
81 }
82
83 fn into_cloneable(self) -> Self::Cloneable {
84 Class {
85 class: self.class.into_cloneable(),
86 }
87 }
88
89 fn into_cloneable_owned(self) -> Self::CloneableOwned {
90 Class {
91 class: self.class.into_cloneable_owned(),
92 }
93 }
94
95 fn dry_resolve(&mut self) {
96 self.class.dry_resolve();
97 }
98
99 async fn resolve(self) -> Self::AsyncOutput {
100 Class {
101 class: self.class.resolve().await,
102 }
103 }
104
105 fn keys(&self) -> Vec<NamedAttributeKey> {
106 vec![NamedAttributeKey::Attribute("class".into())]
107 }
108}
109
110impl<C> NextAttribute for Class<C>
111where
112 C: IntoClass,
113{
114 next_attr_output_type!(Self, NewAttr);
115
116 fn add_any_attr<NewAttr: Attribute>(
117 self,
118 new_attr: NewAttr,
119 ) -> Self::Output<NewAttr> {
120 next_attr_combine!(self, new_attr)
121 }
122}
123
124impl<C> ToTemplate for Class<C>
125where
126 C: IntoClass,
127{
128 const CLASS: &'static str = C::TEMPLATE;
129
130 fn to_template(
131 _buf: &mut String,
132 class: &mut String,
133 _style: &mut String,
134 _inner_html: &mut String,
135 _position: &mut Position,
136 ) {
137 C::to_template(class);
138 }
139}
140
141pub trait IntoClass: Send {
143 const TEMPLATE: &'static str = "";
145 const MIN_LENGTH: usize = Self::TEMPLATE.len();
147
148 type AsyncOutput: IntoClass;
150 type State;
152 type Cloneable: IntoClass + Clone;
154 type CloneableOwned: IntoClass + Clone + 'static;
156
157 fn html_len(&self) -> usize;
159
160 fn to_html(self, class: &mut String);
162
163 fn should_overwrite(&self) -> bool {
166 false
167 }
168
169 #[allow(unused)] fn to_template(class: &mut String) {}
172
173 fn hydrate<const FROM_SERVER: bool>(
176 self,
177 el: &crate::renderer::types::Element,
178 ) -> Self::State;
179
180 fn build(self, el: &crate::renderer::types::Element) -> Self::State;
182
183 fn rebuild(self, state: &mut Self::State);
185
186 fn into_cloneable(self) -> Self::Cloneable;
188
189 fn into_cloneable_owned(self) -> Self::CloneableOwned;
191
192 fn dry_resolve(&mut self);
196
197 fn resolve(self) -> impl Future<Output = Self::AsyncOutput> + Send;
199
200 fn reset(state: &mut Self::State);
202}
203
204impl<T: IntoClass> IntoClass for Option<T> {
205 type AsyncOutput = Option<T::AsyncOutput>;
206 type State = (crate::renderer::types::Element, Option<T::State>);
207 type Cloneable = Option<T::Cloneable>;
208 type CloneableOwned = Option<T::CloneableOwned>;
209
210 fn html_len(&self) -> usize {
211 self.as_ref().map_or(0, IntoClass::html_len)
212 }
213
214 fn to_html(self, class: &mut String) {
215 if let Some(t) = self {
216 t.to_html(class);
217 }
218 }
219
220 fn hydrate<const FROM_SERVER: bool>(
221 self,
222 el: &crate::renderer::types::Element,
223 ) -> Self::State {
224 if let Some(t) = self {
225 (el.clone(), Some(t.hydrate::<FROM_SERVER>(el)))
226 } else {
227 (el.clone(), None)
228 }
229 }
230
231 fn build(self, el: &crate::renderer::types::Element) -> Self::State {
232 if let Some(t) = self {
233 (el.clone(), Some(t.build(el)))
234 } else {
235 (el.clone(), None)
236 }
237 }
238
239 fn rebuild(self, state: &mut Self::State) {
240 let el = &state.0;
241 let prev_state = &mut state.1;
242 let maybe_next_t_state = match (prev_state.take(), self) {
243 (Some(mut prev_t_state), None) => {
244 T::reset(&mut prev_t_state);
245 Some(None)
246 }
247 (None, Some(t)) => Some(Some(t.build(el))),
248 (Some(mut prev_t_state), Some(t)) => {
249 t.rebuild(&mut prev_t_state);
250 Some(Some(prev_t_state))
251 }
252 (None, None) => Some(None),
253 };
254 if let Some(next_t_state) = maybe_next_t_state {
255 state.1 = next_t_state;
256 }
257 }
258
259 fn into_cloneable(self) -> Self::Cloneable {
260 self.map(|t| t.into_cloneable())
261 }
262
263 fn into_cloneable_owned(self) -> Self::CloneableOwned {
264 self.map(|t| t.into_cloneable_owned())
265 }
266
267 fn dry_resolve(&mut self) {
268 if let Some(t) = self {
269 t.dry_resolve();
270 }
271 }
272
273 async fn resolve(self) -> Self::AsyncOutput {
274 if let Some(t) = self {
275 Some(t.resolve().await)
276 } else {
277 None
278 }
279 }
280
281 fn reset(state: &mut Self::State) {
282 if let Some(prev_t_state) = &mut state.1 {
283 T::reset(prev_t_state);
284 }
285 }
286}
287
288impl IntoClass for &str {
289 type AsyncOutput = Self;
290 type State = (crate::renderer::types::Element, Self);
291 type Cloneable = Self;
292 type CloneableOwned = Arc<str>;
293
294 fn html_len(&self) -> usize {
295 self.len()
296 }
297
298 fn to_html(self, class: &mut String) {
299 class.push_str(self);
300 }
301
302 fn should_overwrite(&self) -> bool {
303 true
304 }
305
306 fn hydrate<const FROM_SERVER: bool>(
307 self,
308 el: &crate::renderer::types::Element,
309 ) -> Self::State {
310 if !FROM_SERVER {
311 Rndr::set_attribute(el, "class", self);
312 }
313 (el.clone(), self)
314 }
315
316 fn build(self, el: &crate::renderer::types::Element) -> Self::State {
317 Rndr::set_attribute(el, "class", self);
318 (el.clone(), self)
319 }
320
321 fn rebuild(self, state: &mut Self::State) {
322 let (el, prev) = state;
323 if self != *prev {
324 Rndr::set_attribute(el, "class", self);
325 }
326 *prev = self;
327 }
328
329 fn into_cloneable(self) -> Self::Cloneable {
330 self
331 }
332
333 fn into_cloneable_owned(self) -> Self::CloneableOwned {
334 self.into()
335 }
336
337 fn dry_resolve(&mut self) {}
338
339 async fn resolve(self) -> Self::AsyncOutput {
340 self
341 }
342
343 fn reset(state: &mut Self::State) {
344 let (el, _prev) = state;
345 Rndr::remove_attribute(el, "class");
346 }
347}
348
349impl IntoClass for Cow<'_, str> {
350 type AsyncOutput = Self;
351 type State = (crate::renderer::types::Element, Self);
352 type Cloneable = Arc<str>;
353 type CloneableOwned = Arc<str>;
354
355 fn html_len(&self) -> usize {
356 self.len()
357 }
358
359 fn to_html(self, class: &mut String) {
360 IntoClass::to_html(&*self, class);
361 }
362
363 fn should_overwrite(&self) -> bool {
364 true
365 }
366
367 fn hydrate<const FROM_SERVER: bool>(
368 self,
369 el: &crate::renderer::types::Element,
370 ) -> Self::State {
371 if !FROM_SERVER {
372 Rndr::set_attribute(el, "class", &self);
373 }
374 (el.clone(), self)
375 }
376
377 fn build(self, el: &crate::renderer::types::Element) -> Self::State {
378 Rndr::set_attribute(el, "class", &self);
379 (el.clone(), self)
380 }
381
382 fn rebuild(self, state: &mut Self::State) {
383 let (el, prev) = state;
384 if self != *prev {
385 Rndr::set_attribute(el, "class", &self);
386 }
387 *prev = self;
388 }
389
390 fn into_cloneable(self) -> Self::Cloneable {
391 self.into()
392 }
393
394 fn into_cloneable_owned(self) -> Self::CloneableOwned {
395 self.into()
396 }
397
398 fn dry_resolve(&mut self) {}
399
400 async fn resolve(self) -> Self::AsyncOutput {
401 self
402 }
403
404 fn reset(state: &mut Self::State) {
405 let (el, _prev) = state;
406 Rndr::remove_attribute(el, "class");
407 }
408}
409
410impl IntoClass for String {
411 type AsyncOutput = Self;
412 type State = (crate::renderer::types::Element, Self);
413 type Cloneable = Arc<str>;
414 type CloneableOwned = Arc<str>;
415
416 fn html_len(&self) -> usize {
417 self.len()
418 }
419
420 fn to_html(self, class: &mut String) {
421 IntoClass::to_html(self.as_str(), class);
422 }
423
424 fn should_overwrite(&self) -> bool {
425 true
426 }
427
428 fn hydrate<const FROM_SERVER: bool>(
429 self,
430 el: &crate::renderer::types::Element,
431 ) -> Self::State {
432 if !FROM_SERVER {
433 Rndr::set_attribute(el, "class", &self);
434 }
435 (el.clone(), self)
436 }
437
438 fn build(self, el: &crate::renderer::types::Element) -> Self::State {
439 Rndr::set_attribute(el, "class", &self);
440 (el.clone(), self)
441 }
442
443 fn rebuild(self, state: &mut Self::State) {
444 let (el, prev) = state;
445 if self != *prev {
446 Rndr::set_attribute(el, "class", &self);
447 }
448 *prev = self;
449 }
450
451 fn into_cloneable(self) -> Self::Cloneable {
452 self.into()
453 }
454
455 fn into_cloneable_owned(self) -> Self::CloneableOwned {
456 self.into()
457 }
458
459 fn dry_resolve(&mut self) {}
460
461 async fn resolve(self) -> Self::AsyncOutput {
462 self
463 }
464
465 fn reset(state: &mut Self::State) {
466 let (el, _prev) = state;
467 Rndr::remove_attribute(el, "class");
468 }
469}
470
471impl IntoClass for Arc<str> {
472 type AsyncOutput = Self;
473 type State = (crate::renderer::types::Element, Self);
474 type Cloneable = Self;
475 type CloneableOwned = Self;
476
477 fn html_len(&self) -> usize {
478 self.len()
479 }
480
481 fn to_html(self, class: &mut String) {
482 IntoClass::to_html(self.as_ref(), class);
483 }
484
485 fn should_overwrite(&self) -> bool {
486 true
487 }
488
489 fn hydrate<const FROM_SERVER: bool>(
490 self,
491 el: &crate::renderer::types::Element,
492 ) -> Self::State {
493 if !FROM_SERVER {
494 Rndr::set_attribute(el, "class", &self);
495 }
496 (el.clone(), self)
497 }
498
499 fn build(self, el: &crate::renderer::types::Element) -> Self::State {
500 Rndr::set_attribute(el, "class", &self);
501 (el.clone(), self)
502 }
503
504 fn rebuild(self, state: &mut Self::State) {
505 let (el, prev) = state;
506 if self != *prev {
507 Rndr::set_attribute(el, "class", &self);
508 }
509 *prev = self;
510 }
511
512 fn into_cloneable(self) -> Self::Cloneable {
513 self
514 }
515
516 fn into_cloneable_owned(self) -> Self::CloneableOwned {
517 self
518 }
519
520 fn dry_resolve(&mut self) {}
521
522 async fn resolve(self) -> Self::AsyncOutput {
523 self
524 }
525
526 fn reset(state: &mut Self::State) {
527 let (el, _prev) = state;
528 Rndr::remove_attribute(el, "class");
529 }
530}
531
532impl IntoClass for (&'static str, bool) {
533 type AsyncOutput = Self;
534 type State = (crate::renderer::types::ClassList, bool, &'static str);
535 type Cloneable = Self;
536 type CloneableOwned = Self;
537
538 fn html_len(&self) -> usize {
539 self.0.len()
540 }
541
542 fn to_html(self, class: &mut String) {
543 let (name, include) = self;
544 if include {
545 class.push_str(name);
546 }
547 }
548
549 fn hydrate<const FROM_SERVER: bool>(
550 self,
551 el: &crate::renderer::types::Element,
552 ) -> Self::State {
553 let (name, include) = self;
554 let class_list = Rndr::class_list(el);
555 if !FROM_SERVER && include {
556 Rndr::add_class(&class_list, name);
557 }
558 (class_list, self.1, name)
559 }
560
561 fn build(self, el: &crate::renderer::types::Element) -> Self::State {
562 let (name, include) = self;
563 let class_list = Rndr::class_list(el);
564 if include {
565 Rndr::add_class(&class_list, name);
566 }
567 (class_list, self.1, name)
568 }
569
570 fn rebuild(self, state: &mut Self::State) {
571 let (name, include) = self;
572 let (class_list, prev_include, prev_name) = state;
573 if name == *prev_name {
574 if include != *prev_include {
575 if include {
576 Rndr::add_class(class_list, name);
577 } else {
578 Rndr::remove_class(class_list, name);
579 }
580 }
581 } else {
582 if *prev_include {
583 Rndr::remove_class(class_list, prev_name);
584 }
585 if include {
586 Rndr::add_class(class_list, name);
587 }
588 }
589 *prev_include = include;
590 *prev_name = name;
591 }
592
593 fn into_cloneable(self) -> Self::Cloneable {
594 self
595 }
596
597 fn into_cloneable_owned(self) -> Self::Cloneable {
598 self
599 }
600
601 fn dry_resolve(&mut self) {}
602
603 async fn resolve(self) -> Self::AsyncOutput {
604 self
605 }
606
607 fn reset(state: &mut Self::State) {
608 let (class_list, _, name) = state;
609 Rndr::remove_class(class_list, name);
610 }
611}
612
613#[cfg(all(feature = "nightly", rustc_nightly))]
614impl<const V: &'static str> IntoClass for crate::view::static_types::Static<V> {
615 const TEMPLATE: &'static str = V;
616
617 type AsyncOutput = Self;
618 type State = ();
619 type Cloneable = Self;
620 type CloneableOwned = Self;
621
622 fn html_len(&self) -> usize {
623 V.len()
624 }
625
626 fn to_html(self, class: &mut String) {
627 class.push_str(V);
628 }
629
630 fn to_template(class: &mut String) {
631 class.push_str(V);
632 }
633
634 fn hydrate<const FROM_SERVER: bool>(
635 self,
636 _el: &crate::renderer::types::Element,
637 ) -> Self::State {
638 }
639
640 fn build(self, el: &crate::renderer::types::Element) -> Self::State {
641 Rndr::set_attribute(el, "class", V);
642 }
643
644 fn rebuild(self, _state: &mut Self::State) {}
645
646 fn into_cloneable(self) -> Self::Cloneable {
647 self
648 }
649
650 fn into_cloneable_owned(self) -> Self::CloneableOwned {
651 self
652 }
653
654 fn dry_resolve(&mut self) {}
655
656 async fn resolve(self) -> Self::AsyncOutput {
657 self
658 }
659
660 fn reset(_state: &mut Self::State) {}
661}
662
663