Skip to main content

topcoat_view/attribute/
value.rs

1use topcoat_core::context::Cx;
2
3use crate::{PartsWriter, Unescaped, ViewPart};
4
5/// Converts a value used as an attribute value into view parts.
6///
7/// When this trait is implemented on a type, it can be used in the attribute value position of an
8/// element in the [`view!`](https://docs.rs/topcoat/latest/topcoat/view/macro.view.html) macro:
9///
10/// ```rust
11/// # use topcoat::view::{component, view};
12/// # #[component]
13/// # async fn example() -> topcoat::Result {
14/// # let my_value = "primary";
15/// view! {
16///     <div class=(my_value)></div>
17/// }
18/// # }
19/// ```
20///
21/// For [boolean HTML attributes], a false value must be omitted from the markup entirely.
22/// [`attribute_present`](Self::attribute_present) is the hook that makes that decision.
23/// The built-in `bool` and `Option<T>` implementations use this so `false` and `None` omit the
24/// whole attribute.
25///
26/// [boolean HTML attributes]: https://developer.mozilla.org/en-US/docs/Glossary/Boolean/HTML
27pub trait AttributeValueViewParts {
28    /// Returns whether the containing attribute should be rendered.
29    ///
30    /// For [boolean HTML attributes], a false value must be omitted from the markup entirely.
31    ///
32    /// [boolean HTML attributes]: https://developer.mozilla.org/en-US/docs/Glossary/Boolean/HTML
33    fn attribute_present(&self) -> bool;
34
35    /// Appends this attribute value to the view being built.
36    fn into_view_parts(self, cx: &Cx, parts: &mut PartsWriter<'_>);
37}
38
39macro_rules! impl_primitive {
40    ($ty:ty, $method:ident) => {
41        impl AttributeValueViewParts for $ty {
42            #[inline]
43            fn attribute_present(&self) -> bool {
44                true
45            }
46
47            #[inline]
48            fn into_view_parts(self, _cx: &Cx, parts: &mut PartsWriter<'_>) {
49                parts.$method(self);
50            }
51        }
52    };
53    ($ty:ty, $method:ident, ref) => {
54        impl_primitive!($ty, $method);
55
56        impl AttributeValueViewParts for &$ty {
57            #[inline]
58            fn attribute_present(&self) -> bool {
59                (*self).attribute_present()
60            }
61
62            #[inline]
63            fn into_view_parts(self, cx: &Cx, parts: &mut PartsWriter<'_>) {
64                (*self).into_view_parts(cx, parts);
65            }
66        }
67    };
68}
69
70impl_primitive!(char, push_char, ref);
71impl_primitive!(i8, push_i8, ref);
72impl_primitive!(i16, push_i16, ref);
73impl_primitive!(i32, push_i32, ref);
74impl_primitive!(i64, push_i64, ref);
75impl_primitive!(i128, push_i128, ref);
76impl_primitive!(isize, push_isize, ref);
77impl_primitive!(u8, push_u8, ref);
78impl_primitive!(u16, push_u16, ref);
79impl_primitive!(u32, push_u32, ref);
80impl_primitive!(u64, push_u64, ref);
81impl_primitive!(u128, push_u128, ref);
82impl_primitive!(usize, push_usize, ref);
83impl_primitive!(f32, push_f32, ref);
84impl_primitive!(f64, push_f64, ref);
85impl_primitive!(String, push_str);
86
87impl AttributeValueViewParts for &str {
88    #[inline]
89    fn attribute_present(&self) -> bool {
90        true
91    }
92
93    #[inline]
94    fn into_view_parts(self, _cx: &Cx, parts: &mut PartsWriter<'_>) {
95        parts.push_str(self.to_owned());
96    }
97}
98
99impl AttributeValueViewParts for Unescaped<String> {
100    #[inline]
101    fn attribute_present(&self) -> bool {
102        true
103    }
104
105    #[inline]
106    fn into_view_parts(self, _cx: &Cx, parts: &mut PartsWriter<'_>) {
107        parts.push_str_unescaped(self.0);
108    }
109}
110
111impl AttributeValueViewParts for Unescaped<&'static str> {
112    #[inline]
113    fn attribute_present(&self) -> bool {
114        true
115    }
116
117    #[inline]
118    fn into_view_parts(self, _cx: &Cx, parts: &mut PartsWriter<'_>) {
119        parts.push_str_unescaped(self.0);
120    }
121}
122
123impl AttributeValueViewParts for &String {
124    #[inline]
125    fn attribute_present(&self) -> bool {
126        self.as_str().attribute_present()
127    }
128
129    #[inline]
130    fn into_view_parts(self, cx: &Cx, parts: &mut PartsWriter<'_>) {
131        self.as_str().into_view_parts(cx, parts);
132    }
133}
134
135impl AttributeValueViewParts for bool {
136    #[inline]
137    fn attribute_present(&self) -> bool {
138        *self
139    }
140
141    #[inline]
142    fn into_view_parts(self, _cx: &Cx, parts: &mut PartsWriter<'_>) {
143        parts.push_bool(self);
144    }
145}
146
147impl AttributeValueViewParts for &bool {
148    #[inline]
149    fn attribute_present(&self) -> bool {
150        (*self).attribute_present()
151    }
152
153    #[inline]
154    fn into_view_parts(self, cx: &Cx, parts: &mut PartsWriter<'_>) {
155        (*self).into_view_parts(cx, parts);
156    }
157}
158
159impl<'b, T: ?Sized> AttributeValueViewParts for &&'b T
160where
161    &'b T: AttributeValueViewParts,
162{
163    #[inline]
164    fn attribute_present(&self) -> bool {
165        (**self).attribute_present()
166    }
167
168    #[inline]
169    fn into_view_parts(self, cx: &Cx, parts: &mut PartsWriter<'_>) {
170        (*self).into_view_parts(cx, parts);
171    }
172}
173
174impl<T> AttributeValueViewParts for Option<T>
175where
176    T: AttributeValueViewParts,
177{
178    #[inline]
179    fn attribute_present(&self) -> bool {
180        self.as_ref()
181            .is_some_and(AttributeValueViewParts::attribute_present)
182    }
183
184    #[inline]
185    fn into_view_parts(self, cx: &Cx, parts: &mut PartsWriter<'_>) {
186        if let Some(value) = self {
187            value.into_view_parts(cx, parts);
188        }
189    }
190}
191
192impl AttributeValueViewParts for ViewPart {
193    fn attribute_present(&self) -> bool {
194        match self {
195            Self::Empty | Self::Bool(false) => false,
196            Self::BoxSlice { inner, .. } if inner.is_empty() => false,
197            _ => true,
198        }
199    }
200
201    #[inline]
202    fn into_view_parts(self, _cx: &Cx, parts: &mut PartsWriter<'_>) {
203        parts.push_part(self);
204    }
205}
206
207macro_rules! impl_tuple {
208    ($($ty:ident),+) => {
209        impl<$($ty),+> AttributeValueViewParts for ($($ty,)+)
210        where
211            $($ty: AttributeValueViewParts,)+
212        {
213            #[inline]
214            #[allow(non_snake_case)]
215            fn attribute_present(&self) -> bool {
216                let ($($ty,)+) = self;
217                $($ty.attribute_present())||+
218            }
219
220            #[inline]
221            #[allow(non_snake_case)]
222            fn into_view_parts(self, cx: &Cx, parts: &mut PartsWriter<'_>) {
223                let ($($ty,)+) = self;
224                $($ty.into_view_parts(cx, parts);)+
225            }
226        }
227    };
228}
229
230impl_tuple!(T1);
231impl_tuple!(T1, T2);
232impl_tuple!(T1, T2, T3);
233impl_tuple!(T1, T2, T3, T4);
234impl_tuple!(T1, T2, T3, T4, T5);
235impl_tuple!(T1, T2, T3, T4, T5, T6);
236impl_tuple!(T1, T2, T3, T4, T5, T6, T7);
237impl_tuple!(T1, T2, T3, T4, T5, T6, T7, T8);
238impl_tuple!(T1, T2, T3, T4, T5, T6, T7, T8, T9);
239impl_tuple!(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10);
240impl_tuple!(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11);
241impl_tuple!(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12);