yew_utils/vdom/
tag.rs

1use std::{borrow::Cow, marker::PhantomData, rc::Rc};
2use yew::html::IntoEventCallback;
3use yew::virtual_dom as vdom;
4
5// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
6
7pub struct TagTypeDefault;
8pub struct TagTypeInput;
9
10pub trait TagType {}
11impl TagType for TagTypeDefault {}
12impl TagType for TagTypeInput {}
13
14// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
15
16pub struct Tag<T: TagType = TagTypeDefault> {
17    tag: vdom::VTag,
18    listeners: Vec<Option<Rc<dyn vdom::Listener>>>,
19    additional_props: PhantomData<T>,
20}
21
22impl<T: TagType> From<Tag<T>> for vdom::VNode {
23    fn from(node: Tag<T>) -> Self {
24        node.to_vnode()
25    }
26}
27
28impl<T: TagType> From<Tag<T>> for yew::Children {
29    fn from(node: Tag<T>) -> Self {
30        yew::Children::new([node.into()].to_vec())
31    }
32}
33
34impl<T> Tag<T>
35where
36    T: TagType,
37{
38    pub(crate) fn new(tag: impl Into<Cow<'static, str>>) -> Self {
39        Self {
40            tag: vdom::VTag::new(tag),
41            listeners: Vec::new(),
42            additional_props: PhantomData,
43        }
44    }
45
46    fn with_props(tag: impl Into<Cow<'static, str>>) -> Self {
47        Self {
48            tag: vdom::VTag::new(tag),
49            listeners: Vec::new(),
50            additional_props: PhantomData,
51        }
52    }
53
54    #[must_use]
55    pub fn to_vnode(self) -> vdom::VNode {
56        vdom::VNode::VTag(Box::new(self.tag))
57    }
58
59    #[must_use]
60    pub fn class(self, class: impl Into<vdom::AttrValue>) -> Self {
61        self.attr("class", class)
62    }
63
64    #[must_use]
65    pub fn id(self, id: impl Into<vdom::AttrValue>) -> Self {
66        self.attr("id", id)
67    }
68
69    #[must_use]
70    pub fn style(self, style: impl Into<vdom::AttrValue>) -> Self {
71        self.attr("style", style)
72    }
73
74    #[must_use]
75    pub fn attr(mut self, key: &'static str, attr: impl Into<vdom::AttrValue>) -> Self {
76        self.tag.add_attribute(key, attr);
77        self
78    }
79
80    #[must_use]
81    pub fn key(mut self, key: impl Into<vdom::Key>) -> Self {
82        self.tag.key = Some(key.into());
83        self
84    }
85
86    #[must_use]
87    pub fn append(mut self, node: impl Into<vdom::VNode>) -> Self {
88        self.tag.add_child(node.into());
89        self
90    }
91
92    #[must_use]
93    pub fn append_all(mut self, nodes: impl IntoIterator<Item = impl Into<vdom::VNode>>) -> Self {
94        self.tag.add_children(nodes.into_iter().map(|n| n.into()));
95        self
96    }
97
98    #[must_use]
99    pub fn text(self, text: impl Into<vdom::AttrValue>) -> Self {
100        self.append(vdom::VNode::VText(vdom::VText::new(text)))
101    }
102
103    #[must_use]
104    pub fn node_ref(mut self, node_ref: yew::NodeRef) -> Self {
105        self.tag.node_ref = node_ref;
106        self
107    }
108}
109
110// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
111// events
112
113macro_rules! add_event_listener {
114    ( $arg:ident ) => {
115        #[must_use]
116        pub fn $arg(mut self, listener: impl IntoEventCallback<::yew::html::$arg::Event>) -> Self {
117            self.listeners
118                .push(::yew::html::$arg::Wrapper::__macro_new(listener));
119            self.update_listeners();
120            self
121        }
122    };
123}
124
125impl<T> Tag<T>
126where
127    T: TagType,
128{
129    fn update_listeners(&mut self) {
130        // argh, yeq implements the listeners list statically
131        #[rustfmt::skip]
132        self.tag.set_listeners(match self.listeners.as_slice() {
133            [a] => Box::new([a.clone()]),
134            [a, b] => Box::new([a.clone(), b.clone()]),
135            [a, b, c] => Box::new([a.clone(), b.clone(), c.clone()]),
136            [a, b, c, d] => Box::new([a.clone(), b.clone(), c.clone(), d.clone()]),
137            [a, b, c, d, e] => Box::new([a.clone(), b.clone(), c.clone(), d.clone(), e.clone()]),
138            _ => unimplemented!("More listeners than expected... probably need to extend this."),
139        });
140    }
141
142    add_event_listener!(onabort);
143    add_event_listener!(onauxclick);
144    add_event_listener!(onblur);
145    add_event_listener!(oncancel);
146    add_event_listener!(oncanplay);
147    add_event_listener!(oncanplaythrough);
148    add_event_listener!(onchange);
149    add_event_listener!(onclick);
150    add_event_listener!(onclose);
151    add_event_listener!(oncontextmenu);
152    add_event_listener!(oncuechange);
153    add_event_listener!(ondblclick);
154    add_event_listener!(ondrag);
155    add_event_listener!(ondragend);
156    add_event_listener!(ondragenter);
157    add_event_listener!(ondragexit);
158    add_event_listener!(ondragleave);
159    add_event_listener!(ondragover);
160    add_event_listener!(ondragstart);
161    add_event_listener!(ondrop);
162    add_event_listener!(ondurationchange);
163    add_event_listener!(onemptied);
164    add_event_listener!(onended);
165    add_event_listener!(onerror);
166    add_event_listener!(onfocus);
167    add_event_listener!(onfocusin);
168    add_event_listener!(onfocusout);
169    add_event_listener!(onformdata);
170    add_event_listener!(oninput);
171    add_event_listener!(oninvalid);
172    add_event_listener!(onkeydown);
173    add_event_listener!(onkeypress);
174    add_event_listener!(onkeyup);
175    add_event_listener!(onload);
176    add_event_listener!(onloadeddata);
177    add_event_listener!(onloadedmetadata);
178    add_event_listener!(onloadstart);
179    add_event_listener!(onmousedown);
180    add_event_listener!(onmouseenter);
181    add_event_listener!(onmouseleave);
182    add_event_listener!(onmousemove);
183    add_event_listener!(onmouseout);
184    add_event_listener!(onmouseover);
185    add_event_listener!(onmouseup);
186    add_event_listener!(onpause);
187    add_event_listener!(onplay);
188    add_event_listener!(onplaying);
189    add_event_listener!(onprogress);
190    add_event_listener!(onratechange);
191    add_event_listener!(onreset);
192    add_event_listener!(onresize);
193    add_event_listener!(onscroll);
194    add_event_listener!(onsecuritypolicyviolation);
195    add_event_listener!(onseeked);
196    add_event_listener!(onseeking);
197    add_event_listener!(onselect);
198    add_event_listener!(onslotchange);
199    add_event_listener!(onstalled);
200    add_event_listener!(onsubmit);
201    add_event_listener!(onsuspend);
202    add_event_listener!(ontimeupdate);
203    add_event_listener!(ontoggle);
204    add_event_listener!(onvolumechange);
205    add_event_listener!(onwaiting);
206    add_event_listener!(onwheel);
207    add_event_listener!(oncopy);
208    add_event_listener!(oncut);
209    add_event_listener!(onpaste);
210    add_event_listener!(onanimationcancel);
211    add_event_listener!(onanimationend);
212    add_event_listener!(onanimationiteration);
213    add_event_listener!(onanimationstart);
214    add_event_listener!(ongotpointercapture);
215    add_event_listener!(onloadend);
216    add_event_listener!(onlostpointercapture);
217    add_event_listener!(onpointercancel);
218    add_event_listener!(onpointerdown);
219    add_event_listener!(onpointerenter);
220    add_event_listener!(onpointerleave);
221    add_event_listener!(onpointerlockchange);
222    add_event_listener!(onpointerlockerror);
223    add_event_listener!(onpointermove);
224    add_event_listener!(onpointerout);
225    add_event_listener!(onpointerover);
226    add_event_listener!(onpointerup);
227    add_event_listener!(onselectionchange);
228    add_event_listener!(onselectstart);
229    add_event_listener!(onshow);
230    add_event_listener!(ontouchcancel);
231    add_event_listener!(ontouchend);
232    add_event_listener!(ontouchmove);
233    add_event_listener!(ontouchstart);
234    add_event_listener!(ontransitioncancel);
235    add_event_listener!(ontransitionend);
236    add_event_listener!(ontransitionrun);
237    add_event_listener!(ontransitionstart);
238}
239
240// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
241
242impl Tag<TagTypeInput> {
243    pub(crate) fn input() -> Self {
244        Tag::with_props("input")
245    }
246
247    #[must_use]
248    pub fn checked(mut self, checked: bool) -> Self {
249        self.tag.set_checked(checked);
250        self
251    }
252
253    #[must_use]
254    pub fn value(mut self, value: impl yew::html::IntoPropValue<Option<vdom::AttrValue>>) -> Self {
255        self.tag.set_value(value);
256        self
257    }
258
259    pub fn type_button(self) -> Self {
260        self.attr("type", "button")
261    }
262
263    pub fn type_checkbox(self) -> Self {
264        self.attr("type", "checkbox")
265    }
266
267    pub fn type_color(self) -> Self {
268        self.attr("type", "color")
269    }
270
271    pub fn type_date(self) -> Self {
272        self.attr("type", "date")
273    }
274
275    pub fn type_datetime(self) -> Self {
276        self.attr("type", "datetime")
277    }
278
279    pub fn type_datetime_local(self) -> Self {
280        self.attr("type", "datetime-local")
281    }
282
283    pub fn type_email(self) -> Self {
284        self.attr("type", "email")
285    }
286
287    pub fn type_file(self) -> Self {
288        self.attr("type", "file")
289    }
290
291    pub fn type_hidden(self) -> Self {
292        self.attr("type", "hidden")
293    }
294
295    pub fn type_image(self) -> Self {
296        self.attr("type", "image")
297    }
298
299    pub fn type_month(self) -> Self {
300        self.attr("type", "month")
301    }
302
303    pub fn type_number(self) -> Self {
304        self.attr("type", "number")
305    }
306
307    pub fn type_password(self) -> Self {
308        self.attr("type", "password")
309    }
310
311    pub fn type_radio(self) -> Self {
312        self.attr("type", "radio")
313    }
314
315    pub fn type_range(self) -> Self {
316        self.attr("type", "range")
317    }
318
319    pub fn type_reset(self) -> Self {
320        self.attr("type", "reset")
321    }
322
323    pub fn type_search(self) -> Self {
324        self.attr("type", "search")
325    }
326
327    pub fn type_submit(self) -> Self {
328        self.attr("type", "submit")
329    }
330
331    pub fn type_tel(self) -> Self {
332        self.attr("type", "tel")
333    }
334
335    pub fn type_text(self) -> Self {
336        self.attr("type", "text")
337    }
338
339    pub fn type_time(self) -> Self {
340        self.attr("type", "time")
341    }
342
343    pub fn type_url(self) -> Self {
344        self.attr("type", "url")
345    }
346
347    pub fn type_week(self) -> Self {
348        self.attr("type", "week")
349    }
350}