Skip to main content

ravel_web/attr/
types.rs

1//! HTML attribute types.
2//!
3//! Usually you shouldn't need to import or reference these directly.
4
5use std::marker::PhantomData;
6
7use ravel::{Builder, State};
8use wasm_bindgen::UnwrapThrowExt;
9
10use crate::{BuildCx, RebuildCx, Web};
11
12use super::CloneString;
13
14/// Trait to identify attribute types.
15pub trait AttrKind: 'static {
16    /// The name of the attribute.
17    const NAME: &'static str;
18}
19
20pub trait AttrValue {
21    type Saved: 'static;
22
23    fn save(self) -> Self::Saved;
24
25    fn changed(&self, saved: &Self::Saved) -> bool;
26
27    fn with_str<F, R>(&self, f: F) -> R
28    where
29        F: FnOnce(Option<&str>) -> R;
30}
31
32impl<V: AttrValue> AttrValue for Option<V> {
33    type Saved = Option<V::Saved>;
34
35    fn save(self) -> Self::Saved {
36        self.map(AttrValue::save)
37    }
38
39    fn changed(&self, saved: &Self::Saved) -> bool {
40        match (self, saved) {
41            (None, None) => false,
42            (None, Some(_)) => true,
43            (Some(_), None) => true,
44            (Some(v), Some(saved)) => v.changed(saved),
45        }
46    }
47
48    fn with_str<F, R>(&self, f: F) -> R
49    where
50        F: FnOnce(Option<&str>) -> R,
51    {
52        match self {
53            Some(v) => v.with_str(f),
54            None => f(None),
55        }
56    }
57}
58
59impl<V: AsRef<str>> AttrValue for CloneString<V> {
60    type Saved = String;
61
62    fn save(self) -> Self::Saved {
63        self.0.as_ref().to_string()
64    }
65
66    fn changed(&self, saved: &Self::Saved) -> bool {
67        self.0.as_ref() != saved.as_str()
68    }
69
70    fn with_str<F, R>(&self, f: F) -> R
71    where
72        F: FnOnce(Option<&str>) -> R,
73    {
74        f(Some(self.0.as_ref()))
75    }
76}
77
78impl AttrValue for &'static str {
79    type Saved = Self;
80
81    fn save(self) -> Self::Saved {
82        self
83    }
84
85    fn changed(&self, saved: &Self::Saved) -> bool {
86        !std::ptr::eq(*self, *saved)
87    }
88
89    fn with_str<F, R>(&self, f: F) -> R
90    where
91        F: FnOnce(Option<&str>) -> R,
92    {
93        f(Some(self))
94    }
95}
96
97#[doc(hidden)]
98#[derive(Clone, Copy, Debug)]
99pub struct BooleanAttrValue(pub bool);
100
101impl AttrValue for BooleanAttrValue {
102    type Saved = bool;
103
104    fn save(self) -> Self::Saved {
105        self.0
106    }
107
108    fn changed(&self, saved: &Self::Saved) -> bool {
109        self.0 != *saved
110    }
111
112    fn with_str<F, R>(&self, f: F) -> R
113    where
114        F: FnOnce(Option<&str>) -> R,
115    {
116        f(if self.0 { Some("") } else { None })
117    }
118}
119
120macro_rules! make_attr_value_copy_to_string {
121    ($t:ty) => {
122        impl AttrValue for $t {
123            type Saved = Self;
124
125            fn save(self) -> Self::Saved {
126                self
127            }
128
129            fn changed(&self, saved: &Self::Saved) -> bool {
130                *self != *saved
131            }
132
133            fn with_str<F, R>(&self, f: F) -> R
134            where
135                F: FnOnce(Option<&str>) -> R,
136            {
137                let s = self.to_string();
138                f(Some(&s))
139            }
140        }
141    };
142}
143
144make_attr_value_copy_to_string!(char);
145make_attr_value_copy_to_string!(f32);
146make_attr_value_copy_to_string!(f64);
147make_attr_value_copy_to_string!(i128);
148make_attr_value_copy_to_string!(i16);
149make_attr_value_copy_to_string!(i32);
150make_attr_value_copy_to_string!(i64);
151make_attr_value_copy_to_string!(i8);
152make_attr_value_copy_to_string!(isize);
153make_attr_value_copy_to_string!(u128);
154make_attr_value_copy_to_string!(u16);
155make_attr_value_copy_to_string!(u32);
156make_attr_value_copy_to_string!(u64);
157make_attr_value_copy_to_string!(u8);
158make_attr_value_copy_to_string!(usize);
159
160/// Trait for `class` attribute values.
161///
162/// In HTML, `class` is a space separated list. Rather than requiring you to
163/// construct the string by hand, this trait allows easily constructing it from
164/// various types:
165///
166/// * A [`String`] or `&'static str` is just a class name.
167/// * A tuple of `ClassValue`s is the union of the component class names.
168/// * An [`Option<T>`] is an optional set of classes.
169pub trait ClassValue: 'static + PartialEq {
170    /// If the value is available as a static string, providing it allows a more
171    /// efficient implementation. The default implementation returns [`None`].
172    fn as_str(&self) -> Option<&'static str> {
173        None
174    }
175
176    /// Calls a callback for each class name.
177    fn for_each<F: FnMut(&str)>(&self, f: F);
178}
179
180impl ClassValue for &'static str {
181    fn as_str(&self) -> Option<&'static str> {
182        Some(self)
183    }
184
185    fn for_each<F: FnMut(&str)>(&self, mut f: F) {
186        f(self)
187    }
188}
189
190impl<C: ClassValue> ClassValue for Option<C> {
191    fn as_str(&self) -> Option<&'static str> {
192        self.as_ref().and_then(C::as_str)
193    }
194
195    fn for_each<F: FnMut(&str)>(&self, f: F) {
196        if let Some(s) = self.as_ref() {
197            s.for_each(f);
198        }
199    }
200}
201
202macro_rules! tuple_class_value {
203    ($($a:ident),*) => {
204        #[allow(non_camel_case_types)]
205        impl<$($a: ClassValue),*> ClassValue for ($($a,)*) {
206            fn for_each<F: FnMut(&str)>(&self, mut _f: F) {
207                let ($($a,)*) = self;
208                $($a.for_each(&mut _f);)*
209            }
210        }
211    };
212}
213
214tuple_class_value!();
215tuple_class_value!(a);
216tuple_class_value!(a, b);
217tuple_class_value!(a, b, c);
218tuple_class_value!(a, b, c, d);
219tuple_class_value!(a, b, c, d, e);
220tuple_class_value!(a, b, c, d, e, f);
221tuple_class_value!(a, b, c, d, e, f, g);
222tuple_class_value!(a, b, c, d, e, f, g, h);
223
224#[doc(hidden)]
225pub struct Classes<V: ClassValue>(pub V);
226
227impl<V: ClassValue> AttrValue for Classes<V> {
228    type Saved = V; // TODO: Associated saved type
229
230    fn save(self) -> Self::Saved {
231        self.0
232    }
233
234    fn changed(&self, saved: &Self::Saved) -> bool {
235        self.0 != *saved
236    }
237
238    fn with_str<F, R>(&self, f: F) -> R
239    where
240        F: FnOnce(Option<&str>) -> R,
241    {
242        match self.0.as_str() {
243            Some(s) => f(Some(s)),
244            None => {
245                let mut s = String::new();
246
247                self.0.for_each(|c| {
248                    if !s.is_empty() {
249                        s.push(' ');
250                    }
251
252                    s.push_str(c);
253                });
254
255                f(if s.is_empty() { None } else { Some(&s) })
256            }
257        }
258    }
259}
260
261/// The state of an [`Attr`].
262pub struct AttrState<Saved> {
263    value: Saved,
264}
265
266impl<Saved> AttrState<Saved> {
267    pub(crate) fn build<V: AttrValue<Saved = Saved>>(
268        parent: &web_sys::Element,
269        name: &'static str,
270        value: V,
271    ) -> Self {
272        value.with_str(|value| {
273            if let Some(value) = value {
274                parent.set_attribute(name, value).unwrap_throw()
275            }
276        });
277
278        Self {
279            value: value.save(),
280        }
281    }
282
283    pub(crate) fn rebuild<V: AttrValue<Saved = Saved>>(
284        &mut self,
285        parent: &web_sys::Element,
286        name: &'static str,
287        value: V,
288    ) {
289        if !value.changed(&self.value) {
290            return;
291        }
292
293        value.with_str(|value| {
294            if let Some(value) = value {
295                parent.set_attribute(name, value).unwrap_throw()
296            } else {
297                parent.remove_attribute(name).unwrap_throw()
298            }
299        });
300    }
301}
302
303impl<Saved: 'static, Output> State<Output> for AttrState<Saved> {
304    fn run(&mut self, _: &mut Output) {}
305}
306
307/// An arbitrary attribute.
308#[repr(transparent)]
309#[derive(Copy, Clone, Debug)]
310pub struct Attr<Kind: AttrKind, Value> {
311    pub(crate) value: Value,
312    pub(crate) kind: PhantomData<Kind>,
313}
314
315impl<Kind: AttrKind, Value: AttrValue> Builder<Web> for Attr<Kind, Value> {
316    type State = AttrState<Value::Saved>;
317
318    fn build(self, cx: BuildCx) -> Self::State {
319        AttrState::build(cx.position.parent, Kind::NAME, self.value)
320    }
321
322    fn rebuild(self, cx: RebuildCx, state: &mut Self::State) {
323        state.rebuild(cx.parent, Kind::NAME, self.value)
324    }
325}