silkenweb_html/
macros.rs

1pub use silkenweb_dom::{
2    tag, AttributeValue, Builder, DomElement, Effect, Element, ElementBuilder, Text,
3};
4pub use wasm_bindgen::JsCast;
5
6macro_rules! html_element {
7    (
8        $(#[$elem_meta:meta])*
9        $name:ident {
10            $(
11                $(#[$attr_meta:meta])*
12                $attr:ident : $typ:ty
13            ),* $(,)?
14        }
15    ) => {
16        paste::item! {
17            $(#[$elem_meta])*
18            pub fn $name() -> [<$name:camel Builder>] {
19                [<$name: camel Builder>]($crate::macros::tag(stringify!($name)))
20            }
21
22            pub struct [<$name:camel Builder>]($crate::macros::ElementBuilder);
23
24            impl [<$name:camel Builder>] {
25                attributes![id: String, class: String, $($(#[$attr_meta])* $attr: $typ, )*];
26            }
27
28            impl $crate::macros::Builder for [<$name:camel Builder>] {
29                type Target = [<$name:camel>];
30
31                fn build(self) -> Self::Target {
32                    [<$name:camel>](self.0.build())
33                }
34
35                fn into_element(self) -> $crate::macros::Element {
36                    self.build().into()
37                }
38            }
39
40            impl From<[<$name:camel Builder>]> for $crate::macros::Element {
41                fn from(builder: [<$name:camel Builder>]) -> Self {
42                    use $crate::macros::Builder;
43                    builder.build().into()
44                }
45            }
46
47            impl From<[<$name:camel Builder>]> for $crate::macros::ElementBuilder {
48                fn from(builder: [<$name:camel Builder>]) -> Self {
49                    builder.0
50                }
51            }
52
53            #[derive(Clone)]
54            pub struct [<$name:camel>]($crate::macros::Element);
55
56            impl $crate::macros::Builder for [<$name:camel>] {
57                type Target = Self;
58
59                fn build(self) -> Self::Target {
60                    self
61                }
62
63                fn into_element(self) -> $crate::macros::Element {
64                    self.build().into()
65                }
66            }
67
68            impl From<[<$name:camel>]> for $crate::macros::Element {
69                fn from(html_elem: [<$name:camel>]) -> Self {
70                    html_elem.0
71                }
72            }
73        }
74    };
75}
76
77macro_rules! dom_type {
78    ($name:ident < $elem_type:ty >) => {
79        paste::item! {
80            impl [<$name:camel Builder>] {
81                html_events!($elem_type);
82
83                pub fn effect(self, f: impl $crate::macros::Effect<$elem_type>) -> Self {
84                    Self(self.0.effect(f))
85                }
86            }
87
88            impl $crate::macros::DomElement for [<$name:camel Builder>] {
89                type Target = $elem_type;
90
91                fn dom_element(&self) -> Self::Target {
92                    use $crate::macros::JsCast;
93                    self.0.dom_element().unchecked_into()
94                }
95            }
96
97            impl $crate::macros::DomElement for [<$name:camel>] {
98                type Target = $elem_type;
99
100                fn dom_element(&self) -> Self::Target {
101                    use $crate::macros::JsCast;
102                    self.0.dom_element().unchecked_into()
103                }
104            }
105        }
106    };
107}
108
109macro_rules! children_allowed {
110    ($name:ident) => {
111        paste::item! {
112            impl [<$name:camel Builder>] {
113                pub fn text(self, child: impl $crate::macros::Text) -> Self {
114                    Self(self.0.text(child))
115                }
116
117                pub fn child<Child>(self, c: Child) -> Self
118                where
119                    Child: Into<$crate::macros::Element>
120                {
121                    Self(self.0.child(c.into()))
122                }
123            }
124        }
125    };
126}
127
128macro_rules! html_events {
129    ($elem_type:ty) => {
130        events!($elem_type {
131            blur: web_sys::FocusEvent,
132            click: web_sys::MouseEvent,
133            change: web_sys::Event,
134            dblclick: web_sys::MouseEvent,
135            focusout: web_sys::FocusEvent,
136            input: web_sys::InputEvent,
137            keydown: web_sys::KeyboardEvent,
138            keyup: web_sys::KeyboardEvent,
139        });
140    };
141}
142
143macro_rules! events {
144    ($elem_type:ty {
145        $($name:ident: $event_type:ty),* $(,)?
146    }) => {
147        paste::item!{
148            $(
149                pub fn [<on_ $name >] (
150                    self,
151                    mut f: impl 'static + FnMut($event_type, $elem_type)
152                ) -> Self {
153                    Self(self.0.on(stringify!($name), move |js_ev| {
154                        use $crate::macros::JsCast;
155                        // I *think* it's safe to assume event and event.target aren't null
156                        let event: $event_type = js_ev.unchecked_into();
157                        let target: $elem_type = event.target().unwrap().unchecked_into();
158                        f(event, target);
159                    }))
160                }
161            )*
162        }
163    };
164}
165
166macro_rules! attributes {
167    ($(
168        $(#[$attr_meta:meta])*
169        $attr:ident : $typ:ty
170    ),* $(,)? ) => {
171        $(
172            $(#[$attr_meta])*
173            pub fn $attr(self, value: impl $crate::macros::AttributeValue<$typ>) -> Self {
174                Self(self.0.attribute(attr_name!($attr), value))
175            }
176        )*
177    };
178}
179
180macro_rules! attr_name {
181    (accept_charset) => {
182        "accept-charset"
183    };
184    (as_) => {
185        "as"
186    };
187    (async_) => {
188        "async"
189    };
190    (for_) => {
191        "for"
192    };
193    (http_equiv) => {
194        "http-equiv"
195    };
196    (current_time) => {
197        "currentTime"
198    };
199    (loop_) => {
200        "loop"
201    };
202    (type_) => {
203        "type"
204    };
205    ($name:ident) => {
206        stringify!($name)
207    };
208}