@@ -0,0 +1,884 @@
1+//! Helpers for building virtual DOM nodes.
2+
3+use crate::{Attribute, Listener, Node, RootRender, VdomWeak};
4+use bumpalo::Bump;
5+
6+/// A virtual DOM element builder.
7+///
8+/// Typically constructed with element-specific constructors, eg the `div`
9+/// function for building `<div>` elements or the `button` function for building
10+/// `<button>` elements.
11+#[derive(Clone, Debug)]
12+pub struct ElementBuilder<'a, Listeners, Attributes, Children>
13+where
14+ Listeners: 'a + AsRef<[Listener<'a>]>,
15+ Attributes: 'a + AsRef<[Attribute<'a>]>,
16+ Children: 'a + AsRef<[Node<'a>]>,
17+{
18+ bump: &'a Bump,
19+ tag_name: &'a str,
20+ listeners: Listeners,
21+ attributes: Attributes,
22+ children: Children,
23+}
24+
25+impl<'a, Listeners, Attributes, Children> ElementBuilder<'a, Listeners, Attributes, Children>
26+where
27+ Listeners: 'a + AsRef<[Listener<'a>]>,
28+ Attributes: 'a + AsRef<[Attribute<'a>]>,
29+ Children: 'a + AsRef<[Node<'a>]>,
30+{
31+ /// Set the listeners for this element.
32+ ///
33+ /// You can use this method to customize the backing storage for listeners,
34+ /// for example to use a fixed-size array instead of the default
35+ /// dynamically-sized `bumpalo::collections::Vec`.
36+ ///
37+ /// Any listeners already added to the builder will be overridden.
38+ ///
39+ /// # Example
40+ ///
41+ /// ```no_run
42+ /// use dodrio::{builder::*, bumpalo::Bump};
43+ ///
44+ /// let b = Bump::new();
45+ ///
46+ /// // Create a `<div>` with a fixed-size array of two listeners.
47+ /// let my_div = div(&b)
48+ /// .listeners([
49+ /// on(&b, "click", |root, vdom, event| {
50+ /// // ...
51+ /// }),
52+ /// on(&b, "dblclick", |root, vdom, event| {
53+ /// // ...
54+ /// }),
55+ /// ])
56+ /// .finish();
57+ /// ```
58+ #[inline]
59+ pub fn listeners<L>(self, listeners: L) -> ElementBuilder<'a, L, Attributes, Children>
60+ where
61+ L: 'a + AsRef<[Listener<'a>]>,
62+ {
63+ ElementBuilder {
64+ bump: self.bump,
65+ tag_name: self.tag_name,
66+ listeners,
67+ attributes: self.attributes,
68+ children: self.children,
69+ }
70+ }
71+
72+ /// Set the attributes for this element.
73+ ///
74+ /// You can use this method to customize the backing storage for attributes,
75+ /// for example to use a fixed-size array instead of the default
76+ /// dynamically-sized `bumpalo::collections::Vec`.
77+ ///
78+ /// Any attributes already added to the builder will be overridden.
79+ ///
80+ /// # Example
81+ ///
82+ /// ```no_run
83+ /// use dodrio::{builder::*, bumpalo::Bump, Attribute};
84+ ///
85+ /// let b = Bump::new();
86+ ///
87+ /// // Create a `<div>` with a fixed-size array of two attributes.
88+ /// let my_div = div(&b)
89+ /// .attributes([
90+ /// attr("id", "my-div"),
91+ /// attr("class", "notification"),
92+ /// ])
93+ /// .finish();
94+ /// ```
95+ #[inline]
96+ pub fn attributes<A>(self, attributes: A) -> ElementBuilder<'a, Listeners, A, Children>
97+ where
98+ A: 'a + AsRef<[Attribute<'a>]>,
99+ {
100+ ElementBuilder {
101+ bump: self.bump,
102+ tag_name: self.tag_name,
103+ listeners: self.listeners,
104+ attributes,
105+ children: self.children,
106+ }
107+ }
108+
109+ /// Set the children for this element.
110+ ///
111+ /// You can use this method to customize the backing storage for children,
112+ /// for example to use a fixed-size array instead of the default
113+ /// dynamically-sized `bumpalo::collections::Vec`.
114+ ///
115+ /// Any children already added to the builder will be overridden.
116+ ///
117+ /// # Example
118+ ///
119+ /// ```no_run
120+ /// use dodrio::{builder::*, bumpalo::Bump};
121+ ///
122+ /// let b = Bump::new();
123+ ///
124+ /// // Create a `<div>` with a fixed-size array of two `<span>` children.
125+ /// let my_div = div(&b)
126+ /// .children([
127+ /// span(&b).finish(),
128+ /// span(&b).finish(),
129+ /// ])
130+ /// .finish();
131+ /// ```
132+ #[inline]
133+ pub fn children<C>(self, children: C) -> ElementBuilder<'a, Listeners, Attributes, C>
134+ where
135+ C: 'a + AsRef<[Node<'a>]>,
136+ {
137+ ElementBuilder {
138+ bump: self.bump,
139+ tag_name: self.tag_name,
140+ listeners: self.listeners,
141+ attributes: self.attributes,
142+ children,
143+ }
144+ }
145+
146+ /// Create the virtual DOM node described by this builder.
147+ ///
148+ /// # Example
149+ ///
150+ /// ```no_run
151+ /// use dodrio::{builder::*, bumpalo::Bump, Node};
152+ ///
153+ /// let b = Bump::new();
154+ ///
155+ /// // Start with a builder...
156+ /// let builder: ElementBuilder<_, _, _> = div(&b);
157+ ///
158+ /// // ...and finish it to create a virtual DOM node!
159+ /// let my_div: Node = builder.finish();
160+ /// ```
161+ #[inline]
162+ pub fn finish(self) -> Node<'a> {
163+ Node::element(
164+ self.bump,
165+ self.tag_name,
166+ self.listeners,
167+ self.attributes,
168+ self.children,
169+ )
170+ }
171+}
172+
173+impl<'a, Attributes, Children>
174+ ElementBuilder<'a, bumpalo::collections::Vec<'a, Listener<'a>>, Attributes, Children>
175+where
176+ Attributes: 'a + AsRef<[Attribute<'a>]>,
177+ Children: 'a + AsRef<[Node<'a>]>,
178+{
179+ /// Add a new event listener to this element.
180+ ///
181+ /// The `event` string specifies which event will be listened for. The
182+ /// `callback` function is the function that will be invoked if the
183+ /// specified event occurs.
184+ ///
185+ /// # Example
186+ ///
187+ /// ```no_run
188+ /// use dodrio::{builder::*, bumpalo::Bump};
189+ ///
190+ /// let b = Bump::new();
191+ ///
192+ /// // A button that does something when clicked!
193+ /// let my_button = button(&b)
194+ /// .on("click", |root, vdom, event| {
195+ /// // ...
196+ /// })
197+ /// .finish();
198+ /// ```
199+ #[inline]
200+ pub fn on<F>(mut self, event: &'a str, callback: F) -> Self
201+ where
202+ F: 'static + Fn(&mut dyn RootRender, VdomWeak, web_sys::Event),
203+ {
204+ self.listeners.push(Listener {
205+ event,
206+ callback: self.bump.alloc(callback),
207+ });
208+ self
209+ }
210+}
211+
212+impl<'a, Listeners, Children>
213+ ElementBuilder<'a, Listeners, bumpalo::collections::Vec<'a, Attribute<'a>>, Children>
214+where
215+ Listeners: 'a + AsRef<[Listener<'a>]>,
216+ Children: 'a + AsRef<[Node<'a>]>,
217+{
218+ /// Add a new attribute to this element.
219+ ///
220+ /// # Example
221+ ///
222+ /// ```no_run
223+ /// use dodrio::{builder::*, bumpalo::Bump};
224+ ///
225+ /// let b = Bump::new();
226+ ///
227+ /// // Create the `<div id="my-div"/>` element.
228+ /// let my_div = div(&b).attr("id", "my-div").finish();
229+ /// ```
230+ #[inline]
231+ pub fn attr(mut self, name: &'a str, value: &'a str) -> Self {
232+ self.attributes.push(Attribute { name, value });
233+ self
234+ }
235+
236+ /// Conditionally add a "boolean-style" attribute to this element.
237+ ///
238+ /// If the `should_add` parameter is true, then adds an attribute with the
239+ /// given `name` and an empty string value. If the `should_add` parameter is
240+ /// false, then the attribute is not added.
241+ ///
242+ /// This method is useful for attributes whose semantics are defined by
243+ /// whether or not the attribute is present or not, and whose value is
244+ /// ignored. Example attributes like this include:
245+ ///
246+ /// * `checked`
247+ /// * `hidden`
248+ /// * `selected`
249+ ///
250+ /// # Example
251+ ///
252+ /// ```no_run
253+ /// use dodrio::{builder::*, bumpalo::Bump};
254+ /// use js_sys::Math;
255+ ///
256+ /// let b = Bump::new();
257+ ///
258+ /// // Create the `<div>` that is randomly hidden 50% of the time.
259+ /// let my_div = div(&b)
260+ /// .bool_attr("hidden", Math::random() >= 0.5)
261+ /// .finish();
262+ /// ```
263+ pub fn bool_attr(mut self, name: &'a str, should_add: bool) -> Self {
264+ if should_add {
265+ self.attributes.push(Attribute { name, value: "" });
266+ }
267+ self
268+ }
269+}
270+
271+impl<'a, Listeners, Attributes>
272+ ElementBuilder<'a, Listeners, Attributes, bumpalo::collections::Vec<'a, Node<'a>>>
273+where
274+ Listeners: 'a + AsRef<[Listener<'a>]>,
275+ Attributes: 'a + AsRef<[Attribute<'a>]>,
276+{
277+ /// Add a new child to this element.
278+ ///
279+ /// # Example
280+ ///
281+ /// ```no_run
282+ /// use dodrio::{builder::*, bumpalo::Bump};
283+ /// use js_sys::Math;
284+ ///
285+ /// let b = Bump::new();
286+ ///
287+ /// // Create `<p><span></span></p>`.
288+ /// let my_div = p(&b)
289+ /// .child(span(&b).finish())
290+ /// .finish();
291+ /// ```
292+ #[inline]
293+ pub fn child(mut self, child: Node<'a>) -> Self {
294+ self.children.push(child);
295+ self
296+ }
297+}
298+
299+macro_rules! builder_constructors {
300+ ( $(
301+ $(#[$attr:meta])*
302+ $name:ident;
303+ )* ) => {
304+ $(
305+ $(#[$attr])*
306+ #[inline]
307+ pub fn $name<'a>(
308+ bump: &'a Bump,
309+ ) -> ElementBuilder<
310+ 'a,
311+ bumpalo::collections::Vec<'a, Listener<'a>>,
312+ bumpalo::collections::Vec<'a, Attribute<'a>>,
313+ bumpalo::collections::Vec<'a, Node<'a>>,
314+ > {
315+ ElementBuilder {
316+ bump,
317+ tag_name: stringify!($name),
318+ listeners: bumpalo::collections::Vec::new_in(bump),
319+ attributes: bumpalo::collections::Vec::new_in(bump),
320+ children: bumpalo::collections::Vec::new_in(bump),
321+ }
322+ }
323+ )*
324+ }
325+}
326+
327+// Organized in the same order as
328+// https://developer.mozilla.org/en-US/docs/Web/HTML/Element
329+//
330+// Does not include obsolete elements.
331+builder_constructors! {
332+ // Document metadata
333+
334+ /// Build a
335+ /// [`<base>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base)
336+ /// element.
337+ base;
338+ /// Build a
339+ /// [`<head>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/head)
340+ /// element.
341+ head;
342+ /// Build a
343+ /// [`<link>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/link)
344+ /// element.
345+ link;
346+ /// Build a
347+ /// [`<meta>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta)
348+ /// element.
349+ meta;
350+ /// Build a
351+ /// [`<style>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/style)
352+ /// element.
353+ style;
354+ /// Build a
355+ /// [`<title>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/title)
356+ /// element.
357+ title;
358+
359+ // Sectioning root
360+
361+ /// Build a
362+ /// [`<body>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/body)
363+ /// element.
364+ body;
365+
366+ // Content sectioning
367+
368+ /// Build a
369+ /// [`<address>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/address)
370+ /// element.
371+ address;
372+ /// Build a
373+ /// [`<article>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/article)
374+ /// element.
375+ article;
376+ /// Build a
377+ /// [`<aside>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/aside)
378+ /// element.
379+ aside;
380+ /// Build a
381+ /// [`<footer>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/footer)
382+ /// element.
383+ footer;
384+ /// Build a
385+ /// [`<header>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/header)
386+ /// element.
387+ header;
388+ /// Build a
389+ /// [`<h1>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/h1)
390+ /// element.
391+ h1;
392+ /// Build a
393+ /// [`<h2>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/h2)
394+ /// element.
395+ h2;
396+ /// Build a
397+ /// [`<h3>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/h3)
398+ /// element.
399+ h3;
400+ /// Build a
401+ /// [`<h4>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/h4)
402+ /// element.
403+ h4;
404+ /// Build a
405+ /// [`<h5>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/h5)
406+ /// element.
407+ h5;
408+ /// Build a
409+ /// [`<h6>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/h6)
410+ /// element.
411+ h6;
412+ /// Build a
413+ /// [`<hgroup>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/hgroup)
414+ /// element.
415+ hgroup;
416+ /// Build a
417+ /// [`<main>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/main)
418+ /// element.
419+ main;
420+ /// Build a
421+ /// [`<nav>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/nav)
422+ /// element.
423+ nav;
424+ /// Build a
425+ /// [`<section>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/section)
426+ /// element.
427+ section;
428+
429+ // Text content
430+
431+ /// Build a
432+ /// [`<blockquote>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/blockquote)
433+ /// element.
434+ blockquote;
435+ /// Build a
436+ /// [`<dd>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/dd)
437+ /// element.
438+ dd;
439+ /// Build a
440+ /// [`<div>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/div)
441+ /// element.
442+ div;
443+ /// Build a
444+ /// [`<dl>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/dl)
445+ /// element.
446+ dl;
447+ /// Build a
448+ /// [`<dt>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/dt)
449+ /// element.
450+ dt;
451+ /// Build a
452+ /// [`<figcaption>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/figcaption)
453+ /// element.
454+ figcaption;
455+ /// Build a
456+ /// [`<figure>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/figure)
457+ /// element.
458+ figure;
459+ /// Build a
460+ /// [`<hr>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/hr)
461+ /// element.
462+ hr;
463+ /// Build a
464+ /// [`<li>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/li)
465+ /// element.
466+ li;
467+ /// Build a
468+ /// [`<ol>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/ol)
469+ /// element.
470+ ol;
471+ /// Build a
472+ /// [`<p>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/p)
473+ /// element.
474+ p;
475+ /// Build a
476+ /// [`<pre>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/pre)
477+ /// element.
478+ pre;
479+ /// Build a
480+ /// [`<ul>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/ul)
481+ /// element.
482+ ul;
483+
484+ // Inline text semantics
485+
486+ /// Build a
487+ /// [`<a>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a)
488+ /// element.
489+ a;
490+ /// Build a
491+ /// [`<abbr>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/abbr)
492+ /// element.
493+ abbr;
494+ /// Build a
495+ /// [`<b>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/b)
496+ /// element.
497+ b;
498+ /// Build a
499+ /// [`<bdi>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/bdi)
500+ /// element.
501+ bdi;
502+ /// Build a
503+ /// [`<bdo>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/bdo)
504+ /// element.
505+ bdo;
506+ /// Build a
507+ /// [`<br>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/br)
508+ /// element.
509+ br;
510+ /// Build a
511+ /// [`<cite>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/cite)
512+ /// element.
513+ cite;
514+ /// Build a
515+ /// [`<code>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/code)
516+ /// element.
517+ code;
518+ /// Build a
519+ /// [`<data>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/data)
520+ /// element.
521+ data;
522+ /// Build a
523+ /// [`<dfn>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/dfn)
524+ /// element.
525+ dfn;
526+ /// Build a
527+ /// [`<em>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/em)
528+ /// element.
529+ em;
530+ /// Build a
531+ /// [`<i>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/i)
532+ /// element.
533+ i;
534+ /// Build a
535+ /// [`<kbd>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/kbd)
536+ /// element.
537+ kbd;
538+ /// Build a
539+ /// [`<mark>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/mark)
540+ /// element.
541+ mark;
542+ /// Build a
543+ /// [`<q>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/q)
544+ /// element.
545+ q;
546+ /// Build a
547+ /// [`<rb>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/rb)
548+ /// element.
549+ rb;
550+ /// Build a
551+ /// [`<rp>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/rp)
552+ /// element.
553+ rp;
554+ /// Build a
555+ /// [`<rt>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/rt)
556+ /// element.
557+ rt;
558+ /// Build a
559+ /// [`<rtc>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/rtc)
560+ /// element.
561+ rtc;
562+ /// Build a
563+ /// [`<ruby>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/ruby)
564+ /// element.
565+ ruby;
566+ /// Build a
567+ /// [`<s>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/s)
568+ /// element.
569+ s;
570+ /// Build a
571+ /// [`<samp>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/samp)
572+ /// element.
573+ samp;
574+ /// Build a
575+ /// [`<small>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/small)
576+ /// element.
577+ small;
578+ /// Build a
579+ /// [`<span>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/span)
580+ /// element.
581+ span;
582+ /// Build a
583+ /// [`<strong>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/strong)
584+ /// element.
585+ strong;
586+ /// Build a
587+ /// [`<sub>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/sub)
588+ /// element.
589+ sub;
590+ /// Build a
591+ /// [`<sup>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/sup)
592+ /// element.
593+ sup;
594+ /// Build a
595+ /// [`<time>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/time)
596+ /// element.
597+ time;
598+ /// Build a
599+ /// [`<u>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/u)
600+ /// element.
601+ u;
602+ /// Build a
603+ /// [`<var>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/var)
604+ /// element.
605+ var;
606+ /// Build a
607+ /// [`<wbr>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/wbr)
608+ /// element.
609+ wbr;
610+
611+ // Image and multimedia
612+
613+ /// Build a
614+ /// [`<area>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/area)
615+ /// element.
616+ area;
617+ /// Build a
618+ /// [`<audio>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/audio)
619+ /// element.
620+ audio;
621+ /// Build a
622+ /// [`<img>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/img)
623+ /// element.
624+ img;
625+ /// Build a
626+ /// [`<map>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/map)
627+ /// element.
628+ map;
629+ /// Build a
630+ /// [`<track>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/track)
631+ /// element.
632+ track;
633+ /// Build a
634+ /// [`<video>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/video)
635+ /// element.
636+ video;
637+
638+ // Embedded content
639+
640+ /// Build a
641+ /// [`<embed>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/embed)
642+ /// element.
643+ embed;
644+ /// Build a
645+ /// [`<iframe>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe)
646+ /// element.
647+ iframe;
648+ /// Build a
649+ /// [`<object>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/object)
650+ /// element.
651+ object;
652+ /// Build a
653+ /// [`<param>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/param)
654+ /// element.
655+ param;
656+ /// Build a
657+ /// [`<picture>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/picture)
658+ /// element.
659+ picture;
660+ /// Build a
661+ /// [`<source>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/source)
662+ /// element.
663+ source;
664+
665+ // Scripting
666+
667+ /// Build a
668+ /// [`<canvas>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/canvas)
669+ /// element.
670+ canvas;
671+ /// Build a
672+ /// [`<noscript>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/noscript)
673+ /// element.
674+ noscript;
675+ /// Build a
676+ /// [`<script>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script)
677+ /// element.
678+ script;
679+
680+ // Demarcating edits
681+
682+ /// Build a
683+ /// [`<del>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/del)
684+ /// element.
685+ del;
686+ /// Build a
687+ /// [`<ins>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/ins)
688+ /// element.
689+ ins;
690+
691+ // Table content
692+
693+ /// Build a
694+ /// [`<caption>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/caption)
695+ /// element.
696+ caption;
697+ /// Build a
698+ /// [`<col>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/col)
699+ /// element.
700+ col;
701+ /// Build a
702+ /// [`<colgroup>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/colgroup)
703+ /// element.
704+ colgroup;
705+ /// Build a
706+ /// [`<table>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/table)
707+ /// element.
708+ table;
709+ /// Build a
710+ /// [`<tbody>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/tbody)
711+ /// element.
712+ tbody;
713+ /// Build a
714+ /// [`<td>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/td)
715+ /// element.
716+ td;
717+ /// Build a
718+ /// [`<tfoot>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/tfoot)
719+ /// element.
720+ tfoot;
721+ /// Build a
722+ /// [`<th>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/th)
723+ /// element.
724+ th;
725+ /// Build a
726+ /// [`<thead>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/thead)
727+ /// element.
728+ thead;
729+ /// Build a
730+ /// [`<tr>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/tr)
731+ /// element.
732+ tr;
733+
734+ // Forms
735+
736+ /// Build a
737+ /// [`<button>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button)
738+ /// element.
739+ button;
740+ /// Build a
741+ /// [`<datalist>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/datalist)
742+ /// element.
743+ datalist;
744+ /// Build a
745+ /// [`<fieldset>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/fieldset)
746+ /// element.
747+ fieldset;
748+ /// Build a
749+ /// [`<form>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/form)
750+ /// element.
751+ form;
752+ /// Build a
753+ /// [`<input>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input)
754+ /// element.
755+ input;
756+ /// Build a
757+ /// [`<label>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/label)
758+ /// element.
759+ label;
760+ /// Build a
761+ /// [`<legend>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/legend)
762+ /// element.
763+ legend;
764+ /// Build a
765+ /// [`<meter>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meter)
766+ /// element.
767+ meter;
768+ /// Build a
769+ /// [`<optgroup>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/optgroup)
770+ /// element.
771+ optgroup;
772+ /// Build a
773+ /// [`<option>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/option)
774+ /// element.
775+ option;
776+ /// Build a
777+ /// [`<output>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/output)
778+ /// element.
779+ output;
780+ /// Build a
781+ /// [`<progress>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/progress)
782+ /// element.
783+ progress;
784+ /// Build a
785+ /// [`<select>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/select)
786+ /// element.
787+ select;
788+ /// Build a
789+ /// [`<textarea>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/textarea)
790+ /// element.
791+ textarea;
792+
793+ // Interactive elements
794+
795+ /// Build a
796+ /// [`<details>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/details)
797+ /// element.
798+ details;
799+ /// Build a
800+ /// [`<dialog>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/dialog)
801+ /// element.
802+ dialog;
803+ /// Build a
804+ /// [`<menu>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/menu)
805+ /// element.
806+ menu;
807+ /// Build a
808+ /// [`<menuitem>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/menuitem)
809+ /// element.
810+ menuitem;
811+ /// Build a
812+ /// [`<summary>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/summary)
813+ /// element.
814+ summary;
815+
816+ // Web components
817+
818+ /// Build a
819+ /// [`<slot>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/slot)
820+ /// element.
821+ slot;
822+ /// Build a
823+ /// [`<template>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/template)
824+ /// element.
825+ template;
826+}
827+
828+/// Construct a text node.
829+///
830+/// This is `dodrio`'s virtual DOM equivalent of `document.createTextNode`.
831+///
832+/// # Example
833+///
834+/// ```no_run
835+/// use dodrio::builder::*;
836+///
837+/// let my_text = text("hello, dodrio!");
838+/// ```
839+#[inline]
840+pub fn text<'a>(contents: &'a str) -> Node<'a> {
841+ Node::text(contents)
842+}
843+
844+/// Construct an attribute for an element.
845+///
846+/// # Example
847+///
848+/// This example creates the `id="my-id"` for some element like `<div
849+/// id="my-id"/>`.
850+///
851+/// ```no_run
852+/// use dodrio::builder::*;
853+///
854+/// let my_id_attr = attr("id", "my-id");
855+/// ```
856+pub fn attr<'a>(name: &'a str, value: &'a str) -> Attribute<'a> {
857+ Attribute { name, value }
858+}
859+
860+/// Create an event listener.
861+///
862+/// `event` is the type of event to listen for, e.g. `"click"`. The `callback`
863+/// is the function that will be invoked when the event occurs.
864+///
865+/// # Example
866+///
867+/// ```no_run
868+/// use dodrio::{builder::*, bumpalo::Bump};
869+///
870+/// let b = Bump::new();
871+///
872+/// let listener = on(&b, "click", |root, vdom, event| {
873+/// // do something when a click happens...
874+/// });
875+/// ```
876+pub fn on<'a, F>(bump: &'a Bump, event: &'a str, callback: F) -> Listener<'a>
877+where
878+ F: Fn(&mut dyn RootRender, VdomWeak, web_sys::Event) + 'static,
879+{
880+ Listener {
881+ event,
882+ callback: bump.alloc(callback),
883+ }
884+}