vertigo/dom_macro/
dom.rs

1use std::{collections::BTreeMap, rc::Rc};
2
3use crate::{
4    dom::{attr_value::AttrValue, dom_node::DomNode, events::ClickEvent},
5    Callback, Callback1, Computed, Css, DomComment, DomElement, DomText, DropFileEvent,
6    KeyDownEvent, Value,
7};
8
9/// Type interpreted as component's dynamic attributes groups
10///
11/// Be careful when using dynamic attributes, key-value type compatibility is checked
12/// in runtime (errors logged into JS console) or ignored for `AttrValue` variant.
13///
14/// ```rust
15/// use vertigo::{bind, component, dom, AttrGroup, Value};
16///
17/// #[component]
18/// pub fn Input(value: Value<String>, input: AttrGroup) {
19///     let on_input = bind!(value, |new_value: String| {
20///         value.set(new_value);
21///     });
22///
23///     dom! {
24///         <input {value} {on_input} {..input} />
25///     }
26/// }
27///
28/// let value = Value::new("world".to_string());
29///
30/// dom! {
31///     <div>
32///        <Input {value} input:name="hello_value" input:id="my_input_1" />
33///     </div>
34/// };
35/// ```
36pub type AttrGroup = BTreeMap<String, AttrGroupValue>;
37
38#[derive(Clone)]
39pub enum AttrGroupValue {
40    AttrValue(AttrValue),
41    Css {
42        css: Css,
43        class_name: Option<String>,
44    },
45    HookKeyDown(Rc<Callback1<KeyDownEvent, bool>>),
46    OnBlur(Rc<Callback<()>>),
47    OnChange(Rc<Callback1<String, ()>>),
48    OnClick(Rc<Callback1<ClickEvent, ()>>),
49    OnDropfile(Rc<Callback1<DropFileEvent, ()>>),
50    OnInput(Rc<Callback1<String, ()>>),
51    OnKeyDown(Rc<Callback1<KeyDownEvent, bool>>),
52    OnLoad(Rc<Callback<()>>),
53    OnMouseDown(Rc<Callback<bool>>),
54    OnMouseEnter(Rc<Callback<()>>),
55    OnMouseLeave(Rc<Callback<()>>),
56    OnMouseUp(Rc<Callback<bool>>),
57    OnSubmit(Rc<Callback<()>>),
58}
59
60impl From<&Self> for AttrGroupValue {
61    fn from(value: &Self) -> Self {
62        value.to_owned()
63    }
64}
65
66macro_rules! group_value_constructor {
67    ($function:ident, $cb_type:ty, $variant:ident) => {
68        pub fn $function(callback: impl Into<$cb_type>) -> Self {
69            Self::$variant(Rc::new(callback.into()))
70        }
71    };
72}
73
74impl AttrGroupValue {
75    pub fn css(css: impl Into<Css>, class_name: Option<String>) -> Self {
76        Self::Css {
77            css: css.into(),
78            class_name,
79        }
80    }
81
82    group_value_constructor!(hook_key_down, Callback1<KeyDownEvent, bool>, HookKeyDown);
83    group_value_constructor!(on_blur, Callback<()>, OnBlur);
84    group_value_constructor!(on_change, Callback1<String, ()>, OnChange);
85    group_value_constructor!(on_click, Callback1<ClickEvent, ()>, OnClick);
86    group_value_constructor!(on_dropfile, Callback1<DropFileEvent, ()>, OnDropfile);
87    group_value_constructor!(on_input, Callback1<String, ()>, OnInput);
88    group_value_constructor!(on_key_down, Callback1<KeyDownEvent, bool>, OnKeyDown);
89    group_value_constructor!(on_load, Callback<()>, OnLoad);
90    group_value_constructor!(on_mouse_down, Callback<bool>, OnMouseDown);
91    group_value_constructor!(on_mouse_enter, Callback<()>, OnMouseEnter);
92    group_value_constructor!(on_mouse_leave, Callback<()>, OnMouseLeave);
93    group_value_constructor!(on_mouse_up, Callback<bool>, OnMouseUp);
94    group_value_constructor!(on_submit, Callback<()>, OnSubmit);
95
96    /// Extract [`Computed<String>`] from this [AttrGroupValue] if possible.
97    ///
98    /// Otherwise (for css and event handlers variants) this gives constant empty string.
99    /// For displaying in HTML it's better to use `.embed()` method (which uses this one internally).
100    pub fn to_string_or_empty(&self) -> Computed<String> {
101        match self {
102            Self::AttrValue(AttrValue::String(val)) => {
103                let val = val.clone();
104                Computed::from(move |_| val.to_string())
105            }
106            Self::AttrValue(AttrValue::Computed(val)) => val.clone(),
107            Self::AttrValue(AttrValue::ComputedOpt(val)) => val.map(|val| val.unwrap_or_default()),
108            Self::AttrValue(AttrValue::Value(val)) => val.to_computed(),
109            Self::AttrValue(AttrValue::ValueOpt(val)) => {
110                val.to_computed().map(|val| val.unwrap_or_default())
111            }
112            _ => Computed::from(|_| "".to_string()),
113        }
114    }
115}
116
117impl<T: Into<AttrValue>> From<T> for AttrGroupValue {
118    fn from(value: T) -> Self {
119        Self::AttrValue(value.into())
120    }
121}
122
123impl EmbedDom for AttrGroupValue {
124    fn embed(self) -> DomNode {
125        self.to_string_or_empty().embed()
126    }
127}
128
129impl EmbedDom for &AttrGroupValue {
130    fn embed(self) -> DomNode {
131        self.to_string_or_empty().embed()
132    }
133}
134
135/// Can be embedded into [dom!](crate::dom!) macro
136pub trait EmbedDom {
137    fn embed(self) -> DomNode;
138}
139
140impl EmbedDom for DomElement {
141    fn embed(self) -> DomNode {
142        self.into()
143    }
144}
145
146impl EmbedDom for DomComment {
147    fn embed(self) -> DomNode {
148        self.into()
149    }
150}
151
152impl EmbedDom for DomText {
153    fn embed(self) -> DomNode {
154        self.into()
155    }
156}
157
158impl EmbedDom for DomNode {
159    fn embed(self) -> DomNode {
160        self
161    }
162}
163
164impl<T: ToString> EmbedDom for T {
165    fn embed(self) -> DomNode {
166        DomNode::Text {
167            node: DomText::new(self.to_string()),
168        }
169    }
170}
171
172impl<T: ToString + Clone + PartialEq + 'static> EmbedDom for &Computed<T> {
173    fn embed(self) -> DomNode {
174        self.render_value(|val| DomNode::Text {
175            node: DomText::new(val.to_string()),
176        })
177    }
178}
179
180impl<T: ToString + Clone + PartialEq + 'static> EmbedDom for Computed<T> {
181    fn embed(self) -> DomNode {
182        (&self).embed()
183    }
184}
185
186impl<T: ToString + Clone + PartialEq + 'static> EmbedDom for Value<T> {
187    fn embed(self) -> DomNode {
188        self.to_computed().embed()
189    }
190}
191
192impl<T: ToString + Clone + PartialEq + 'static> EmbedDom for &Value<T> {
193    fn embed(self) -> DomNode {
194        self.to_computed().embed()
195    }
196}