1use std::{borrow::Cow, marker::PhantomData, rc::Rc};
2use yew::html::IntoEventCallback;
3use yew::virtual_dom as vdom;
4
5pub struct TagTypeDefault;
8pub struct TagTypeInput;
9
10pub trait TagType {}
11impl TagType for TagTypeDefault {}
12impl TagType for TagTypeInput {}
13
14pub 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
110macro_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 #[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
240impl 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}