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() + } + } + } +}