diff --git a/typed-html/Cargo.toml b/typed-html/Cargo.toml
index b5b870c..95275ea 100644
--- a/typed-html/Cargo.toml
+++ b/typed-html/Cargo.toml
@@ -25,8 +25,10 @@ htmlescape = "0.3.1"
proc-macro-hack = "0.5.4"
proc-macro-nested = "0.1.3"
stdweb = { version = "0.4.14", optional = true }
+yew = { version = "0.6.0", optional = true }
dodrio = { version = "0.1.0", optional = true }
web-sys = { version = "0.3.16", optional = true, features = ["Event", "Element"] }
[features]
dodrio_macro = ["web-sys", "dodrio", "typed-html-macros/dodrio"]
+yew_html = ["yew", "stdweb"]
diff --git a/typed-html/src/output/mod.rs b/typed-html/src/output/mod.rs
index 6243b47..7df0a56 100644
--- a/typed-html/src/output/mod.rs
+++ b/typed-html/src/output/mod.rs
@@ -2,3 +2,5 @@
pub mod stdweb;
#[cfg(feature = "dodrio_macro")]
pub mod dodrio;
+#[cfg(feature = "yew_html")]
+pub mod yew;
diff --git a/typed-html/src/output/yew.rs b/typed-html/src/output/yew.rs
new file mode 100644
index 0000000..2e2f56d
--- /dev/null
+++ b/typed-html/src/output/yew.rs
@@ -0,0 +1,223 @@
+use std::fmt::{Display, Error, Formatter};
+use std::marker::PhantomData;
+
+use yew::html;
+use yew::html::{Component, Html, Renderable};
+use yew::virtual_dom::vtag::VTag;
+use yew::virtual_dom::vtext::VText;
+use yew::virtual_dom::Listener;
+
+use crate::dom::VNode as DomVNode;
+use crate::events::EventHandler;
+use crate::OutputType;
+
+/// DOM output using the stdweb crate
+pub struct Yew> {
+ component_type: PhantomData,
+}
+
+impl> OutputType for Yew {
+ type Events = Events;
+ type EventTarget = VTag;
+ type EventListenerHandle = ();
+}
+
+macro_rules! declare_events_yew {
+ ($($name:ident : $action:ident ,)*) => {
+ /// Container type for DOM events.
+ pub struct Events> {
+ $(
+ pub $name: Option, html::$action::Event>>>,
+ )*
+ }
+
+ $(
+ impl private::Sealed for html::$action::Event {}
+ impl ConcreteEvent for html::$action::Event {}
+
+ impl From for BoxedListener
+ where
+ F: Fn(html::$action::Event) -> C::Message + 'static,
+ C: Component + Renderable,
+ {
+ fn from(f: F) -> Self {
+ BoxedListener(Some(Box::new(html::$action::Wrapper::from(f))), PhantomData)
+ }
+ }
+
+ impl From for Box, html::$action::Event>>
+ where
+ F: Fn(html::$action::Event) -> C::Message + 'static,
+ C: Component + Renderable,
+ {
+ fn from(f: F) -> Self {
+ Box::new(BoxedListener::from(f))
+ }
+ }
+ )*
+
+ impl> Default for Events {
+ fn default() -> Self {
+ Events {
+ $(
+ $name: None,
+ )*
+ }
+ }
+ }
+
+ /// Iterate over the defined events on a DOM object.
+ #[macro_export]
+ macro_rules! for_events_yew {
+ ($event:ident in $events:expr => $body:block) => {
+ $(
+ if let Some(ref mut $event) = $events.$name $body
+ )*
+ }
+ }
+ }
+}
+
+// TODO? these are all the "on*" attributes used in typed-html, with
+// the ones I've been unable to match to yew event types commented out.
+// Yew also includes pointer events, which have been left disabled for now.
+//
+// This needs review.
+
+declare_events_yew! {
+ // abort: ?,
+ // autocomplete: ?,
+ // autocompleteerror: ?,
+ blur: onblur,
+ // cancel: ?,
+ // canplay: ?,
+ // canplaythrough: ?,
+ change: onchange,
+ click: onclick,
+ // close: ?,
+ contextmenu: oncontextmenu,
+ // cuechange: ?,
+ dblclick: ondoubleclick,
+ drag: ondrag,
+ dragend: ondragend,
+ dragenter: ondragenter,
+ dragexit: ondragexit,
+ dragleave: ondragleave,
+ dragover: ondragover,
+ dragstart: ondragstart,
+ drop: ondrop,
+ // durationchange: ?,
+ // emptied: ?,
+ // ended: ?,
+ // error: ?,
+ focus: onfocus,
+ // gotpointercapture: ongotpointercapture,
+ input: oninput,
+ // invalid: ?,
+ keydown: onkeydown,
+ keypress: onkeypress,
+ keyup: onkeyup,
+ // load: ?,
+ // loadeddata: ?,
+ // loadedmetadata: ?,
+ // loadstart: ?,
+ // lostpointercapture: onlostpointercapture,
+ mousedown: onmousedown,
+ mouseenter: onmouseenter,
+ mouseleave: onmouseleave,
+ mousemove: onmousemove,
+ mouseout: onmouseout,
+ mouseover: onmouseover,
+ mouseup: onmouseup,
+ mousewheel: onmousewheel,
+ // pause: ?,
+ // play: ?,
+ // playing: ?,
+ // pointercancel: onpointercancel,
+ // pointerdown: onpointerdown,
+ // pointerenter: onpointerenter,
+ // pointerleave: onpointerleave,
+ // pointermove: onpointermove,
+ // pointerout: onpointerout,
+ // pointerover: onpointerover,
+ // pointerup: onpointerup,
+ // progress: ?,
+ // ratechange: ?,
+ // reset: ?,
+ // resize: ?,
+ scroll: onscroll,
+ // seeked: ?,
+ // seeking: ?,
+ // select: ?,
+ // show: ?,
+ // sort: ?,
+ // stalled: ?,
+ submit: onsubmit,
+ // suspend: ?,
+ // timeupdate: ?,
+ // toggle: ?,
+ // volumechange: ?,
+ // waiting: ?,
+}
+
+impl> Display for Events {
+ fn fmt(&self, _f: &mut Formatter) -> Result<(), Error> {
+ Ok(())
+ }
+}
+
+/// A trait representing any concrete event type, as Yew doesn't have one.
+/// Cannot be implemented externally, as it's intended as a marker.
+pub trait ConcreteEvent: private::Sealed {}
+
+mod private {
+ pub trait Sealed {}
+}
+
+pub struct BoxedListener, E: ConcreteEvent>(
+ Option>>,
+ PhantomData,
+);
+
+impl EventHandler, E> for BoxedListener
+where
+ E: ConcreteEvent,
+ C: Component + Renderable,
+{
+ fn attach(&mut self, target: &mut as OutputType>::EventTarget) -> () {
+ let handler = self.0.take().unwrap();
+ target.add_listener(handler)
+ }
+
+ fn render(&self) -> Option {
+ None
+ }
+}
+
+impl> Yew {
+ pub fn install_handlers(target: &mut VTag, handlers: &mut Events) {
+ for_events_yew!(handler in handlers => {
+ handler.attach(target);
+ });
+ }
+
+ pub fn build(vnode: DomVNode<'_, Yew>) -> Html {
+ match vnode {
+ DomVNode::Text(text) => VText::new(text.to_owned()).into(),
+ DomVNode::UnsafeText(text) => VText::new(text.to_owned()).into(),
+ DomVNode::Element(element) => {
+ let mut tag = VTag::new(element.name);
+ tag.attributes = element
+ .attributes
+ .into_iter()
+ .map(|(k, v)| (k.to_owned(), v))
+ .collect();
+ Yew::::install_handlers(&mut tag, element.events);
+ for child in element.children {
+ tag.add_child(Yew::::build(child))
+ }
+ tag.into()
+ }
+ }
+ }
+}