1use std::marker::PhantomData;
6
7use ravel::{Builder, State};
8use wasm_bindgen::UnwrapThrowExt;
9
10use crate::{BuildCx, RebuildCx, Web};
11
12use super::CloneString;
13
14pub trait AttrKind: 'static {
16 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
160pub trait ClassValue: 'static + PartialEq {
170 fn as_str(&self) -> Option<&'static str> {
173 None
174 }
175
176 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; 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
261pub 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#[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}