From 9ce94a934f3d608a9db96620962d82c5ab0188ab Mon Sep 17 00:00:00 2001 From: daxpedda Date: Sun, 15 Dec 2019 18:45:06 +0100 Subject: [PATCH 01/19] `web-sys` listener initial try. --- Cargo.toml | 19 +++- build.rs | 7 ++ src/compat.rs | 23 ++++ src/html/listener.rs | 254 +++++++++++++++++++++++++++---------------- src/lib.rs | 2 + 5 files changed, 208 insertions(+), 97 deletions(-) create mode 100644 src/compat.rs diff --git a/Cargo.toml b/Cargo.toml index 848d18a24c8..a45d5f15cd5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,7 +24,9 @@ bincode = { version = "~1.2.1", optional = true } failure = "0.1" http = "0.2" indexmap = "1.0.2" +js-sys = { version = "0.3.32", optional = true } log = "0.4" +paste = "0.1" proc-macro-hack = "0.5" proc-macro-nested = "0.1" rmp-serde = { version = "0.14.0", optional = true } @@ -33,10 +35,22 @@ serde_cbor = { version = "0.10.2", optional = true } serde_json = "1.0" serde_yaml = { version = "0.8.3", optional = true } slab = "0.4" -stdweb = "0.4.20" +stdweb = { version = "0.4.20", optional = true } toml = { version = "0.5", optional = true } yew-macro = { version = "0.10.1", path = "crates/macro" } +[dependencies.web-sys] +version = "0.3.32" +optional = true +features = [ + "Element", + "Event", + "EventTarget", + "FileList", + "HtmlSelectElement", + "TouchEvent", +] + [target.'cfg(all(target_arch = "wasm32", not(target_os="wasi"), not(cargo_web)))'.dependencies] wasm-bindgen = "0.2.56" wasm-bindgen-futures = "0.4.4" @@ -53,7 +67,8 @@ trybuild = "1.0" rustversion = "1.0" [features] -default = ["services", "agent"] +default = ["services", "agent", "stdweb"] +web_sys = ["web-sys", "js-sys"] doc_test = [] web_test = [] wasm_test = [] diff --git a/build.rs b/build.rs index a0445febb0e..8c275c5bdbb 100644 --- a/build.rs +++ b/build.rs @@ -1,6 +1,13 @@ use std::env; pub fn main() { + if cfg!(all(feature = "web_sys", feature = "stdweb")) { + panic!("don't use `web_sys` and `stdweb` simultaneously") + } + else if cfg!(not(any(feature = "web_sys", feature = "stdweb"))) { + panic!("please select either `web_sys` or `stdweb`") + } + let target_arch = env::var("CARGO_CFG_TARGET_ARCH").unwrap_or_default(); let cargo_web = env::var("COMPILING_UNDER_CARGO_WEB").unwrap_or_default(); if target_arch == "wasm32" && cargo_web != "1" { diff --git a/src/compat.rs b/src/compat.rs new file mode 100644 index 00000000000..178ed855514 --- /dev/null +++ b/src/compat.rs @@ -0,0 +1,23 @@ +//! Gathering of items for compatibility between web-sys/js-sys and stdweb. + +use ::{ + std::mem::ManuallyDrop, + wasm_bindgen::{closure::Closure, JsCast}, + web_sys::EventTarget, +}; + +pub struct EventListenerHandle { + target: EventTarget, + r#type: &'static str, + callback: ManuallyDrop>, +} + +impl EventListenerHandle { + pub fn remove(self) { + self.target.remove_event_listener_with_callback( + &self.r#type, + self.callback.as_ref().unchecked_ref(), + ); + let _ = ManuallyDrop::into_inner(self.callback); + } +} diff --git a/src/html/listener.rs b/src/html/listener.rs index 99d51de5c13..7336ea218af 100644 --- a/src/html/listener.rs +++ b/src/html/listener.rs @@ -1,17 +1,43 @@ use crate::callback::Callback; +#[cfg(feature = "web_sys")] +use crate::compat::EventListenerHandle; use crate::virtual_dom::Listener; -use stdweb::web::html_element::SelectElement; +#[cfg(feature = "stdweb")] +use stdweb::html_element::SelectElement; +#[cfg(feature = "stdweb")] #[allow(unused_imports)] -use stdweb::web::{EventListenerHandle, FileList, INode}; -#[allow(unused_imports)] -use stdweb::{_js_impl, js}; +use stdweb::{ + _js_impl, js, + web::{EventListenerHandle, FileList, INode}, +}; +#[cfg(feature = "web_sys")] +use web_sys::{HtmlSelectElement as SelectElement, FileList}; macro_rules! impl_action { - ($($action:ident($event:ident : $type:ident) -> $ret:ty => $convert:expr)*) => {$( + (|onaction: $action:ident, $event:ident : $type:ident| -> $ret:ty => $convert:expr) => { + paste::expr! { + impl_action!(|action: [], name: [], $event: $type| -> $ret => $convert); + } + }; + (|onaction: $action:ident, name: $name:ident, $event:ident : $type:ident| -> $ret:ty => $convert:expr) => { + paste::expr! { + impl_action!(|action: [], name: $name, $event: $type| -> $ret => $convert); + } + }; + (|action: $action:ident, $event:ident : $type:ident| -> $ret:ty => $convert:expr) => { + impl_action!(|action: $action, name: $action, $event: $type| -> $ret => $convert); + }; + (|action: $action:ident, name: $name:ident, $event:ident : $type:ident| -> $ret:ty => $convert:expr) => { /// An abstract implementation of a listener. pub mod $action { - use stdweb::web::{IEventTarget, Element}; - use stdweb::web::event::{IEvent, $type}; + #[cfg(feature = "stdweb")] + use stdweb::web::{IEventTarget, Element, event::{IEvent, $type}}; + #[cfg(feature = "web_sys")] + use ::{ + std::mem::ManuallyDrop, + wasm_bindgen::{closure::Closure, JsCast}, + web_sys::{Element, EventTarget, $type}, + }; use super::*; /// A wrapper for a callback which attaches event listeners to elements. @@ -35,6 +61,27 @@ macro_rules! impl_action { stringify!($action) } + #[cfg(feature = "web_sys")] + fn attach(&self, element: &Element) -> EventListenerHandle<$type> { + let this = element.clone(); + let callback = self.callback.clone(); + let listener = move |event: $type| { + event.stop_propagation(); + callback.emit($convert(&this, event)); + }; + + let target = EventTarget::from(element.clone()); + let listener = Closure::wrap(Box::new(listener) as Box); + target.add_event_listener_with_callback(stringify!($name), listener.as_ref().unchecked_ref()); + + return EventListenerHandle { + target, + r#type: stringify!($name), + callback: ManuallyDrop::new(listener), + } + } + + #[cfg(feature = "stdweb")] fn attach(&self, element: &Element) -> EventListenerHandle { let this = element.clone(); let callback = self.callback.clone(); @@ -46,100 +93,117 @@ macro_rules! impl_action { } } } - )*}; + }; } +#[cfg(feature = "stdweb")] +mod internal { + use super::*; + + impl_action!(|action: touchcancel, event: TouchCancel| -> TouchCancel => |_, event| { event }); + impl_action!(|action: touchend, event: TouchEnd| -> TouchEnd => |_, event| { event }); + impl_action!(|action: touchenter, event: TouchEnter| -> TouchEnter => |_, event| { event }); + impl_action!(|action: touchmove, event: TouchMove| -> TouchMove => |_, event| { event }); + impl_action!(|action: touchstart, event: TouchStart| -> TouchStart => |_, event| { event }); +} + +#[cfg(feature = "web_sys")] +mod internal { + use super::*; + + impl_action!(|action: touchcancel, event: TouchEvent| -> TouchEvent => |_, event| { event }); + impl_action!(|action: touchend, event: TouchEvent| -> TouchEvent => |_, event| { event }); + impl_action!(|action: touchenter, event: TouchEvent| -> TouchEvent => |_, event| { event }); + impl_action!(|action: touchmove, event: TouchEvent| -> TouchEvent => |_, event| { event }); + impl_action!(|action: touchstart, event: TouchEvent| -> TouchEvent => |_, event| { event }); +} + +pub use internal::*; + // Inspired by: http://package.elm-lang.org/packages/elm-lang/html/2.0.0/Html-Events -impl_action! { - onclick(event: ClickEvent) -> ClickEvent => |_, event| { event } - ondoubleclick(event: DoubleClickEvent) -> DoubleClickEvent => |_, event| { event } - onkeypress(event: KeyPressEvent) -> KeyPressEvent => |_, event| { event } - onkeydown(event: KeyDownEvent) -> KeyDownEvent => |_, event| { event } - onkeyup(event: KeyUpEvent) -> KeyUpEvent => |_, event| { event } - onmousemove(event: MouseMoveEvent) -> MouseMoveEvent => |_, event| { event } - onmousedown(event: MouseDownEvent) -> MouseDownEvent => |_, event| { event } - onmouseup(event: MouseUpEvent) -> MouseUpEvent => |_, event| { event } - onmouseover(event: MouseOverEvent) -> MouseOverEvent => |_, event| { event } - onmouseout(event: MouseOutEvent) -> MouseOutEvent => |_, event| { event } - onmouseenter(event: MouseEnterEvent) -> MouseEnterEvent => |_, event| { event } - onmouseleave(event: MouseLeaveEvent) -> MouseLeaveEvent => |_, event| { event } - onmousewheel(event: MouseWheelEvent) -> MouseWheelEvent => |_, event| { event } - ongotpointercapture(event: GotPointerCaptureEvent) -> GotPointerCaptureEvent => |_, event| { event } - onlostpointercapture(event: LostPointerCaptureEvent) -> LostPointerCaptureEvent => |_, event| { event } - onpointercancel(event: PointerCancelEvent) -> PointerCancelEvent => |_, event| { event } - onpointerdown(event: PointerDownEvent) -> PointerDownEvent => |_, event| { event } - onpointerenter(event: PointerEnterEvent) -> PointerEnterEvent => |_, event| { event } - onpointerleave(event: PointerLeaveEvent) -> PointerLeaveEvent => |_, event| { event } - onpointermove(event: PointerMoveEvent) -> PointerMoveEvent => |_, event| { event } - onpointerout(event: PointerOutEvent) -> PointerOutEvent => |_, event| { event } - onpointerover(event: PointerOverEvent) -> PointerOverEvent => |_, event| { event } - onpointerup(event: PointerUpEvent) -> PointerUpEvent => |_, event| { event } - onscroll(event: ScrollEvent) -> ScrollEvent => |_, event| { event } - onblur(event: BlurEvent) -> BlurEvent => |_, event| { event } - onfocus(event: FocusEvent) -> FocusEvent => |_, event| { event } - onsubmit(event: SubmitEvent) -> SubmitEvent => |_, event| { event } - ondragstart(event: DragStartEvent) -> DragStartEvent => |_, event| { event } - ondrag(event: DragEvent) -> DragEvent => |_, event| { event } - ondragend(event: DragEndEvent) -> DragEndEvent => |_, event| { event } - ondragenter(event: DragEnterEvent) -> DragEnterEvent => |_, event| { event } - ondragleave(event: DragLeaveEvent) -> DragLeaveEvent => |_, event| { event } - ondragover(event: DragOverEvent) -> DragOverEvent => |_, event| { event } - ondragexit(event: DragExitEvent) -> DragExitEvent => |_, event| { event } - ondrop(event: DragDropEvent) -> DragDropEvent => |_, event| { event } - oncontextmenu(event: ContextMenuEvent) -> ContextMenuEvent => |_, event| { event } - oninput(event: InputEvent) -> InputData => |this: &Element, _| { - use stdweb::web::html_element::{InputElement, TextAreaElement}; - use stdweb::unstable::TryInto; - // Normally only InputElement or TextAreaElement can have an oninput event listener. In - // practice though any element with `contenteditable=true` may generate such events, - // therefore here we fall back to just returning the text content of the node. - // See https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/input_event. - let v1 = this.clone().try_into().map(|input: InputElement| input.raw_value()).ok(); - let v2 = this.clone().try_into().map(|input: TextAreaElement| input.value()).ok(); - let v3 = this.text_content(); - let value = v1.or(v2).or(v3) - .expect("only an InputElement or TextAreaElement or an element with contenteditable=true can have an oninput event listener"); - InputData { value } - } - onchange(event: ChangeEvent) -> ChangeData => |this: &Element, _| { - use stdweb::web::{FileList, IElement}; - use stdweb::web::html_element::{InputElement, TextAreaElement, SelectElement}; - use stdweb::unstable::TryInto; - match this.node_name().as_ref() { - "INPUT" => { - let input: InputElement = this.clone().try_into().unwrap(); - let is_file = input.get_attribute("type").map(|value| { - value.eq_ignore_ascii_case("file") - }) - .unwrap_or(false); - if is_file { - let files: FileList = js!( return @{input}.files; ) - .try_into() - .unwrap(); - ChangeData::Files(files) - } else { - ChangeData::Value(input.raw_value()) - } - } - "TEXTAREA" => { - let tae: TextAreaElement = this.clone().try_into().unwrap(); - ChangeData::Value(tae.value()) - } - "SELECT" => { - let se: SelectElement = this.clone().try_into().unwrap(); - ChangeData::Select(se) - } - _ => { - panic!("only an InputElement, TextAreaElement or SelectElement can have an onchange event listener"); +impl_action!(|onaction: click, event: ClickEvent| -> ClickEvent => |_, event| { event }); +impl_action!(|onaction: doubleclick, name: dblclick, event: DoubleClickEvent| -> DoubleClickEvent => |_, event| { event }); +impl_action!(|onaction: keypress, event: KeyPressEvent| -> KeyPressEvent => |_, event| { event }); +impl_action!(|onaction: keydown, event: KeyDownEvent| -> KeyDownEvent => |_, event| { event }); +impl_action!(|onaction: keyup, event: KeyUpEvent| -> KeyUpEvent => |_, event| { event }); +impl_action!(|onaction: mousemove, event: MouseMoveEvent| -> MouseMoveEvent => |_, event| { event }); +impl_action!(|onaction: mousedown, event: MouseDownEvent| -> MouseDownEvent => |_, event| { event }); +impl_action!(|onaction: mouseup, event: MouseUpEvent| -> MouseUpEvent => |_, event| { event }); +impl_action!(|onaction: mouseover, event: MouseOverEvent| -> MouseOverEvent => |_, event| { event }); +impl_action!(|onaction: mouseout, event: MouseOutEvent| -> MouseOutEvent => |_, event| { event }); +impl_action!(|onaction: mouseenter, event: MouseEnterEvent| -> MouseEnterEvent => |_, event| { event }); +impl_action!(|onaction: mouseleave, event: MouseLeaveEvent| -> MouseLeaveEvent => |_, event| { event }); +impl_action!(|onaction: mousewheel, event: MouseWheelEvent| -> MouseWheelEvent => |_, event| { event }); +impl_action!(|onaction: gotpointercapture, event: GotPointerCaptureEvent| -> GotPointerCaptureEvent => |_, event| { event }); +impl_action!(|onaction: lostpointercapture, event: LostPointerCaptureEvent| -> LostPointerCaptureEvent => |_, event| { event }); +impl_action!(|onaction: pointercancel, event: PointerCancelEvent| -> PointerCancelEvent => |_, event| { event }); +impl_action!(|onaction: pointerdown, event: PointerDownEvent| -> PointerDownEvent => |_, event| { event }); +impl_action!(|onaction: pointerenter, event: PointerEnterEvent| -> PointerEnterEvent => |_, event| { event }); +impl_action!(|onaction: pointerleave, event: PointerLeaveEvent| -> PointerLeaveEvent => |_, event| { event }); +impl_action!(|onaction: pointermove, event: PointerMoveEvent| -> PointerMoveEvent => |_, event| { event }); +impl_action!(|onaction: pointerout, event: PointerOutEvent| -> PointerOutEvent => |_, event| { event }); +impl_action!(|onaction: pointerover, event: PointerOverEvent| -> PointerOverEvent => |_, event| { event }); +impl_action!(|onaction: pointerup, event: PointerUpEvent| -> PointerUpEvent => |_, event| { event }); +impl_action!(|onaction: scroll, event: ScrollEvent| -> ScrollEvent => |_, event| { event }); +impl_action!(|onaction: blur, event: BlurEvent| -> BlurEvent => |_, event| { event }); +impl_action!(|onaction: focus, event: FocusEvent| -> FocusEvent => |_, event| { event }); +impl_action!(|onaction: submit, event: SubmitEvent| -> SubmitEvent => |_, event| { event }); +impl_action!(|onaction: dragstart, event: DragStartEvent| -> DragStartEvent => |_, event| { event }); +impl_action!(|onaction: drag, event: DragEvent| -> DragEvent => |_, event| { event }); +impl_action!(|onaction: dragend, event: DragEndEvent| -> DragEndEvent => |_, event| { event }); +impl_action!(|onaction: dragenter, event: DragEnterEvent| -> DragEnterEvent => |_, event| { event }); +impl_action!(|onaction: dragleave, event: DragLeaveEvent| -> DragLeaveEvent => |_, event| { event }); +impl_action!(|onaction: dragover, event: DragOverEvent| -> DragOverEvent => |_, event| { event }); +impl_action!(|onaction: dragexit, event: DragExitEvent| -> DragExitEvent => |_, event| { event }); +impl_action!(|onaction: drop, event: DragDropEvent| -> DragDropEvent => |_, event| { event }); +impl_action!(|onaction: contextmenu, event: ContextMenuEvent| -> ContextMenuEvent => |_, event| { event }); +impl_action!(|onaction: input, event: InputEvent| -> InputData => |this: &Element, _| { + use stdweb::web::html_element::{InputElement, TextAreaElement}; + use stdweb::unstable::TryInto; + // Normally only InputElement or TextAreaElement can have an oninput event listener. In + // practice though any element with `contenteditable=true` may generate such events, + // therefore here we fall back to just returning the text content of the node. + // See https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/input_event. + let v1 = this.clone().try_into().map(|input: InputElement| input.raw_value()).ok(); + let v2 = this.clone().try_into().map(|input: TextAreaElement| input.value()).ok(); + let v3 = this.text_content(); + let value = v1.or(v2).or(v3) + .expect("only an InputElement or TextAreaElement or an element with contenteditable=true can have an oninput event listener"); + InputData { value } +}); +impl_action!(|onaction: change, event: ChangeEvent| -> ChangeData => |this: &Element, _| { + use stdweb::web::{FileList, IElement}; + use stdweb::web::html_element::{InputElement, TextAreaElement, SelectElement}; + use stdweb::unstable::TryInto; + match this.node_name().as_ref() { + "INPUT" => { + let input: InputElement = this.clone().try_into().unwrap(); + let is_file = input.get_attribute("type").map(|value| { + value.eq_ignore_ascii_case("file") + }) + .unwrap_or(false); + if is_file { + let files: FileList = js!( return @{input}.files; ) + .try_into() + .unwrap(); + ChangeData::Files(files) + } else { + ChangeData::Value(input.raw_value()) } } + "TEXTAREA" => { + let tae: TextAreaElement = this.clone().try_into().unwrap(); + ChangeData::Value(tae.value()) + } + "SELECT" => { + let se: SelectElement = this.clone().try_into().unwrap(); + ChangeData::Select(se) + } + _ => { + panic!("only an InputElement, TextAreaElement or SelectElement can have an onchange event listener"); + } } - touchcancel(event: TouchCancel) -> TouchCancel => |_, event| { event } - touchend(event: TouchEnd) -> TouchEnd => |_, event| { event } - touchenter(event: TouchEnter) -> TouchEnter => |_, event| { event } - touchmove(event: TouchMove) -> TouchMove => |_, event| { event } - touchstart(event: TouchStart) -> TouchStart => |_, event| { event } -} +}); /// A type representing data from `oninput` event. #[derive(Debug)] diff --git a/src/lib.rs b/src/lib.rs index bc74e9b5b51..6fb5c6dcdb1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -80,6 +80,8 @@ pub mod macros { pub mod app; pub mod callback; +#[cfg(feature = "web_sys")] +pub mod compat; pub mod components; pub mod format; pub mod html; From 640974d0025e5c0128da2050c519d2cf12da76a1 Mon Sep 17 00:00:00 2001 From: daxpedda Date: Sun, 15 Dec 2019 19:27:07 +0100 Subject: [PATCH 02/19] Improve macros? --- Cargo.toml | 6 ++ src/html/listener.rs | 148 +++++++++++++++++++++---------------------- 2 files changed, 78 insertions(+), 76 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index a45d5f15cd5..890f06b592d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -43,12 +43,18 @@ yew-macro = { version = "0.10.1", path = "crates/macro" } version = "0.3.32" optional = true features = [ + "DragEvent", "Element", "Event", "EventTarget", "FileList", + "FocusEvent", "HtmlSelectElement", + "KeyboardEvent", + "MouseEvent", + "PointerEvent", "TouchEvent", + "UiEvent", ] [target.'cfg(all(target_arch = "wasm32", not(target_os="wasi"), not(cargo_web)))'.dependencies] diff --git a/src/html/listener.rs b/src/html/listener.rs index 7336ea218af..54143386c50 100644 --- a/src/html/listener.rs +++ b/src/html/listener.rs @@ -11,32 +11,46 @@ use stdweb::{ web::{EventListenerHandle, FileList, INode}, }; #[cfg(feature = "web_sys")] -use web_sys::{HtmlSelectElement as SelectElement, FileList}; +use web_sys::{FileList, HtmlSelectElement as SelectElement}; macro_rules! impl_action { - (|onaction: $action:ident, $event:ident : $type:ident| -> $ret:ty => $convert:expr) => { + (|onaction: $action:ident, stdweb_event: $stdweb_type:ident, web_sys_event: $web_sys_type:ident| -> $ret:ty => $convert:expr) => { paste::expr! { - impl_action!(|action: [], name: [], $event: $type| -> $ret => $convert); + impl_action!(|action: [], name: [], stdweb_event: $stdweb_type, web_sys_event: $web_sys_type| -> $ret => $convert); } }; - (|onaction: $action:ident, name: $name:ident, $event:ident : $type:ident| -> $ret:ty => $convert:expr) => { + (|onaction: $action:ident, stdweb_event: $stdweb_type:ident, web_sys_event: $web_sys_type:ident| => $convert:expr) => { paste::expr! { - impl_action!(|action: [], name: $name, $event: $type| -> $ret => $convert); + #[cfg(feature = "stdweb")] + impl_action!(|action: [], name: [], stdweb_event: $stdweb_type, web_sys_event: $web_sys_type| -> $stdweb_type => $convert); + #[cfg(feature = "web_sys")] + impl_action!(|action: [], name: [], stdweb_event: $stdweb_type, web_sys_event: $web_sys_type| -> $web_sys_type => $convert); } }; - (|action: $action:ident, $event:ident : $type:ident| -> $ret:ty => $convert:expr) => { - impl_action!(|action: $action, name: $action, $event: $type| -> $ret => $convert); + (|onaction: $action:ident, name: $name:ident, stdweb_event: $stdweb_type:ident, web_sys_event: $web_sys_type:ident| => $convert:expr) => { + paste::expr! { + #[cfg(feature = "stdweb")] + impl_action!(|action: [], name: $name, stdweb_event: $stdweb_type, web_sys_event: $web_sys_type| -> $stdweb_type => $convert); + #[cfg(feature = "web_sys")] + impl_action!(|action: [], name: $name, stdweb_event: $stdweb_type, web_sys_event: $web_sys_type| -> $web_sys_type => $convert); + } + }; + (|action: $action:ident, stdweb_event: $stdweb_type:ident, web_sys_event: $web_sys_type:ident| => $convert:expr) => { + #[cfg(feature = "stdweb")] + impl_action!(|action: $action, name: $action, stdweb_event: $stdweb_type, web_sys_event: $web_sys_type| -> $stdweb_type => $convert); + #[cfg(feature = "web_sys")] + impl_action!(|action: $action, name: $action, stdweb_event: $stdweb_type, web_sys_event: $web_sys_type| -> $web_sys_type => $convert); }; - (|action: $action:ident, name: $name:ident, $event:ident : $type:ident| -> $ret:ty => $convert:expr) => { + (|action: $action:ident, name: $name:ident, stdweb_event: $stdweb_type:ident, web_sys_event: $web_sys_type:ident| -> $ret:ty => $convert:expr) => { /// An abstract implementation of a listener. pub mod $action { #[cfg(feature = "stdweb")] - use stdweb::web::{IEventTarget, Element, event::{IEvent, $type}}; + use stdweb::web::{IEventTarget, Element, event::{IEvent, $stdweb_type}}; #[cfg(feature = "web_sys")] use ::{ std::mem::ManuallyDrop, wasm_bindgen::{closure::Closure, JsCast}, - web_sys::{Element, EventTarget, $type}, + web_sys::{Element, EventTarget, $web_sys_type}, }; use super::*; @@ -62,16 +76,16 @@ macro_rules! impl_action { } #[cfg(feature = "web_sys")] - fn attach(&self, element: &Element) -> EventListenerHandle<$type> { + fn attach(&self, element: &Element) -> EventListenerHandle<$web_sys_type> { let this = element.clone(); let callback = self.callback.clone(); - let listener = move |event: $type| { + let listener = move |event: $web_sys_type| { event.stop_propagation(); callback.emit($convert(&this, event)); }; let target = EventTarget::from(element.clone()); - let listener = Closure::wrap(Box::new(listener) as Box); + let listener = Closure::wrap(Box::new(listener) as Box); target.add_event_listener_with_callback(stringify!($name), listener.as_ref().unchecked_ref()); return EventListenerHandle { @@ -85,7 +99,7 @@ macro_rules! impl_action { fn attach(&self, element: &Element) -> EventListenerHandle { let this = element.clone(); let callback = self.callback.clone(); - let listener = move |event: $type| { + let listener = move |event: $stdweb_type| { event.stop_propagation(); callback.emit($convert(&this, event)); }; @@ -96,68 +110,45 @@ macro_rules! impl_action { }; } -#[cfg(feature = "stdweb")] -mod internal { - use super::*; - - impl_action!(|action: touchcancel, event: TouchCancel| -> TouchCancel => |_, event| { event }); - impl_action!(|action: touchend, event: TouchEnd| -> TouchEnd => |_, event| { event }); - impl_action!(|action: touchenter, event: TouchEnter| -> TouchEnter => |_, event| { event }); - impl_action!(|action: touchmove, event: TouchMove| -> TouchMove => |_, event| { event }); - impl_action!(|action: touchstart, event: TouchStart| -> TouchStart => |_, event| { event }); -} - -#[cfg(feature = "web_sys")] -mod internal { - use super::*; - - impl_action!(|action: touchcancel, event: TouchEvent| -> TouchEvent => |_, event| { event }); - impl_action!(|action: touchend, event: TouchEvent| -> TouchEvent => |_, event| { event }); - impl_action!(|action: touchenter, event: TouchEvent| -> TouchEvent => |_, event| { event }); - impl_action!(|action: touchmove, event: TouchEvent| -> TouchEvent => |_, event| { event }); - impl_action!(|action: touchstart, event: TouchEvent| -> TouchEvent => |_, event| { event }); -} - -pub use internal::*; - // Inspired by: http://package.elm-lang.org/packages/elm-lang/html/2.0.0/Html-Events -impl_action!(|onaction: click, event: ClickEvent| -> ClickEvent => |_, event| { event }); -impl_action!(|onaction: doubleclick, name: dblclick, event: DoubleClickEvent| -> DoubleClickEvent => |_, event| { event }); -impl_action!(|onaction: keypress, event: KeyPressEvent| -> KeyPressEvent => |_, event| { event }); -impl_action!(|onaction: keydown, event: KeyDownEvent| -> KeyDownEvent => |_, event| { event }); -impl_action!(|onaction: keyup, event: KeyUpEvent| -> KeyUpEvent => |_, event| { event }); -impl_action!(|onaction: mousemove, event: MouseMoveEvent| -> MouseMoveEvent => |_, event| { event }); -impl_action!(|onaction: mousedown, event: MouseDownEvent| -> MouseDownEvent => |_, event| { event }); -impl_action!(|onaction: mouseup, event: MouseUpEvent| -> MouseUpEvent => |_, event| { event }); -impl_action!(|onaction: mouseover, event: MouseOverEvent| -> MouseOverEvent => |_, event| { event }); -impl_action!(|onaction: mouseout, event: MouseOutEvent| -> MouseOutEvent => |_, event| { event }); -impl_action!(|onaction: mouseenter, event: MouseEnterEvent| -> MouseEnterEvent => |_, event| { event }); -impl_action!(|onaction: mouseleave, event: MouseLeaveEvent| -> MouseLeaveEvent => |_, event| { event }); -impl_action!(|onaction: mousewheel, event: MouseWheelEvent| -> MouseWheelEvent => |_, event| { event }); -impl_action!(|onaction: gotpointercapture, event: GotPointerCaptureEvent| -> GotPointerCaptureEvent => |_, event| { event }); -impl_action!(|onaction: lostpointercapture, event: LostPointerCaptureEvent| -> LostPointerCaptureEvent => |_, event| { event }); -impl_action!(|onaction: pointercancel, event: PointerCancelEvent| -> PointerCancelEvent => |_, event| { event }); -impl_action!(|onaction: pointerdown, event: PointerDownEvent| -> PointerDownEvent => |_, event| { event }); -impl_action!(|onaction: pointerenter, event: PointerEnterEvent| -> PointerEnterEvent => |_, event| { event }); -impl_action!(|onaction: pointerleave, event: PointerLeaveEvent| -> PointerLeaveEvent => |_, event| { event }); -impl_action!(|onaction: pointermove, event: PointerMoveEvent| -> PointerMoveEvent => |_, event| { event }); -impl_action!(|onaction: pointerout, event: PointerOutEvent| -> PointerOutEvent => |_, event| { event }); -impl_action!(|onaction: pointerover, event: PointerOverEvent| -> PointerOverEvent => |_, event| { event }); -impl_action!(|onaction: pointerup, event: PointerUpEvent| -> PointerUpEvent => |_, event| { event }); -impl_action!(|onaction: scroll, event: ScrollEvent| -> ScrollEvent => |_, event| { event }); -impl_action!(|onaction: blur, event: BlurEvent| -> BlurEvent => |_, event| { event }); -impl_action!(|onaction: focus, event: FocusEvent| -> FocusEvent => |_, event| { event }); -impl_action!(|onaction: submit, event: SubmitEvent| -> SubmitEvent => |_, event| { event }); -impl_action!(|onaction: dragstart, event: DragStartEvent| -> DragStartEvent => |_, event| { event }); -impl_action!(|onaction: drag, event: DragEvent| -> DragEvent => |_, event| { event }); -impl_action!(|onaction: dragend, event: DragEndEvent| -> DragEndEvent => |_, event| { event }); -impl_action!(|onaction: dragenter, event: DragEnterEvent| -> DragEnterEvent => |_, event| { event }); -impl_action!(|onaction: dragleave, event: DragLeaveEvent| -> DragLeaveEvent => |_, event| { event }); -impl_action!(|onaction: dragover, event: DragOverEvent| -> DragOverEvent => |_, event| { event }); -impl_action!(|onaction: dragexit, event: DragExitEvent| -> DragExitEvent => |_, event| { event }); -impl_action!(|onaction: drop, event: DragDropEvent| -> DragDropEvent => |_, event| { event }); -impl_action!(|onaction: contextmenu, event: ContextMenuEvent| -> ContextMenuEvent => |_, event| { event }); -impl_action!(|onaction: input, event: InputEvent| -> InputData => |this: &Element, _| { +impl_action!(|onaction: click, stdweb_event: ClickEvent, web_sys_event: MouseEvent| => |_, event| { event }); +impl_action!(|onaction: doubleclick, name: dblclick, stdweb_event: DoubleClickEvent, web_sys_event: MouseEvent| => |_, event| { event }); +impl_action!(|onaction: keypress, stdweb_event: KeyPressEvent, web_sys_event: KeyboardEvent| => |_, event| { event }); +impl_action!(|onaction: keydown, stdweb_event: KeyDownEvent, web_sys_event: KeyboardEvent| => |_, event| { event }); +impl_action!(|onaction: keyup, stdweb_event: KeyUpEvent, web_sys_event: KeyboardEvent| => |_, event| { event }); +impl_action!(|onaction: mousemove, stdweb_event: MouseMoveEvent, web_sys_event: MouseEvent| => |_, event| { event }); +impl_action!(|onaction: mousedown, stdweb_event: MouseDownEvent, web_sys_event: MouseEvent| => |_, event| { event }); +impl_action!(|onaction: mouseup, stdweb_event: MouseUpEvent, web_sys_event: MouseEvent| => |_, event| { event }); +impl_action!(|onaction: mouseover, stdweb_event: MouseOverEvent, web_sys_event: MouseEvent| => |_, event| { event }); +impl_action!(|onaction: mouseout, stdweb_event: MouseOutEvent, web_sys_event: MouseEvent| => |_, event| { event }); +impl_action!(|onaction: mouseenter, stdweb_event: MouseEnterEvent, web_sys_event: MouseEvent| => |_, event| { event }); +impl_action!(|onaction: mouseleave, stdweb_event: MouseLeaveEvent, web_sys_event: MouseEvent| => |_, event| { event }); +#[cfg(feature = "stdweb")] +impl_action!(|onaction: mousewheel, stdweb_event: MouseWheelEvent, web_sys_event: MouseEvent| => |_, event| { event }); +impl_action!(|onaction: gotpointercapture, stdweb_event: GotPointerCaptureEvent, web_sys_event: PointerEvent| => |_, event| { event }); +impl_action!(|onaction: lostpointercapture, stdweb_event: LostPointerCaptureEvent, web_sys_event: PointerEvent| => |_, event| { event }); +impl_action!(|onaction: pointercancel, stdweb_event: PointerCancelEvent, web_sys_event: PointerEvent| => |_, event| { event }); +impl_action!(|onaction: pointerdown, stdweb_event: PointerDownEvent, web_sys_event: PointerEvent| => |_, event| { event }); +impl_action!(|onaction: pointerenter, stdweb_event: PointerEnterEvent, web_sys_event: PointerEvent| => |_, event| { event }); +impl_action!(|onaction: pointerleave, stdweb_event: PointerLeaveEvent, web_sys_event: PointerEvent| => |_, event| { event }); +impl_action!(|onaction: pointermove, stdweb_event: PointerMoveEvent, web_sys_event: PointerEvent| => |_, event| { event }); +impl_action!(|onaction: pointerout, stdweb_event: PointerOutEvent, web_sys_event: PointerEvent| => |_, event| { event }); +impl_action!(|onaction: pointerover, stdweb_event: PointerOverEvent, web_sys_event: PointerEvent| => |_, event| { event }); +impl_action!(|onaction: pointerup, stdweb_event: PointerUpEvent, web_sys_event: PointerEvent| => |_, event| { event }); +impl_action!(|onaction: scroll, stdweb_event: ScrollEvent, web_sys_event: UiEvent| => |_, event| { event }); +impl_action!(|onaction: blur, stdweb_event: BlurEvent, web_sys_event: FocusEvent| => |_, event| { event }); +impl_action!(|onaction: focus, stdweb_event: FocusEvent, web_sys_event: FocusEvent| => |_, event| { event }); +impl_action!(|onaction: submit, stdweb_event: SubmitEvent, web_sys_event: Event| => |_, event| { event }); +impl_action!(|onaction: dragstart, stdweb_event: DragStartEvent, web_sys_event: DragEvent| => |_, event| { event }); +impl_action!(|onaction: drag, stdweb_event: DragEvent, web_sys_event: DragEvent| => |_, event| { event }); +impl_action!(|onaction: dragend, stdweb_event: DragEndEvent, web_sys_event: DragEvent| => |_, event| { event }); +impl_action!(|onaction: dragenter, stdweb_event: DragEnterEvent, web_sys_event: DragEvent| => |_, event| { event }); +impl_action!(|onaction: dragleave, stdweb_event: DragLeaveEvent, web_sys_event: DragEvent| => |_, event| { event }); +impl_action!(|onaction: dragover, stdweb_event: DragOverEvent, web_sys_event: DragEvent| => |_, event| { event }); +impl_action!(|onaction: dragexit, name: dragend, stdweb_event: DragExitEvent, web_sys_event: DragEvent| => |_, event| { event }); +impl_action!(|onaction: drop, stdweb_event: DragDropEvent, web_sys_event: DragEvent| => |_, event| { event }); +impl_action!(|onaction: contextmenu, stdweb_event: ContextMenuEvent, web_sys_event: MouseEvent| => |_, event| { event }); +impl_action!(|onaction: input, stdweb_event: InputEvent, web_sys_event: Event| -> InputData => |this: &Element, _| { use stdweb::web::html_element::{InputElement, TextAreaElement}; use stdweb::unstable::TryInto; // Normally only InputElement or TextAreaElement can have an oninput event listener. In @@ -171,7 +162,7 @@ impl_action!(|onaction: input, event: InputEvent| -> InputData => |this: &Elemen .expect("only an InputElement or TextAreaElement or an element with contenteditable=true can have an oninput event listener"); InputData { value } }); -impl_action!(|onaction: change, event: ChangeEvent| -> ChangeData => |this: &Element, _| { +impl_action!(|onaction: change, stdweb_event: ChangeEvent, web_sys_event: Event| -> ChangeData => |this: &Element, _| { use stdweb::web::{FileList, IElement}; use stdweb::web::html_element::{InputElement, TextAreaElement, SelectElement}; use stdweb::unstable::TryInto; @@ -204,6 +195,11 @@ impl_action!(|onaction: change, event: ChangeEvent| -> ChangeData => |this: &Ele } } }); +impl_action!(|action: touchcancel, stdweb_event: TouchCancel, web_sys_event: TouchEvent| => |_, event| { event }); +impl_action!(|action: touchend, stdweb_event: TouchEnd, web_sys_event: TouchEvent| => |_, event| { event }); +impl_action!(|action: touchenter, stdweb_event: TouchEnter, web_sys_event: TouchEvent| => |_, event| { event }); +impl_action!(|action: touchmove, stdweb_event: TouchMove, web_sys_event: TouchEvent| => |_, event| { event }); +impl_action!(|action: touchstart, stdweb_event: TouchStart, web_sys_event: TouchEvent| => |_, event| { event }); /// A type representing data from `oninput` event. #[derive(Debug)] From f2492d849a0026de3cb5ce127e9a3f718f4be4b6 Mon Sep 17 00:00:00 2001 From: daxpedda Date: Wed, 18 Dec 2019 16:17:10 +0100 Subject: [PATCH 03/19] Remove generic from `EventListenerHandle`. --- Cargo.toml | 24 ++++++++++++------------ src/compat.rs | 24 ++++++++++++------------ src/html/listener.rs | 14 ++++++++++---- 3 files changed, 34 insertions(+), 28 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 890f06b592d..466754ea9de 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -43,18 +43,18 @@ yew-macro = { version = "0.10.1", path = "crates/macro" } version = "0.3.32" optional = true features = [ - "DragEvent", - "Element", - "Event", - "EventTarget", - "FileList", - "FocusEvent", - "HtmlSelectElement", - "KeyboardEvent", - "MouseEvent", - "PointerEvent", - "TouchEvent", - "UiEvent", + "DragEvent", + "Element", + "Event", + "EventTarget", + "FileList", + "FocusEvent", + "HtmlSelectElement", + "KeyboardEvent", + "MouseEvent", + "PointerEvent", + "TouchEvent", + "UiEvent", ] [target.'cfg(all(target_arch = "wasm32", not(target_os="wasi"), not(cargo_web)))'.dependencies] diff --git a/src/compat.rs b/src/compat.rs index 178ed855514..5de5c41964a 100644 --- a/src/compat.rs +++ b/src/compat.rs @@ -1,23 +1,23 @@ //! Gathering of items for compatibility between web-sys/js-sys and stdweb. -use ::{ - std::mem::ManuallyDrop, - wasm_bindgen::{closure::Closure, JsCast}, - web_sys::EventTarget, -}; +use std::mem::ManuallyDrop; +use wasm_bindgen::{closure::Closure, JsCast}; +use web_sys::{Event, EventTarget}; -pub struct EventListenerHandle { +pub struct EventListenerHandle { target: EventTarget, r#type: &'static str, - callback: ManuallyDrop>, + callback: ManuallyDrop>, } -impl EventListenerHandle { +impl EventListenerHandle { pub fn remove(self) { - self.target.remove_event_listener_with_callback( - &self.r#type, - self.callback.as_ref().unchecked_ref(), - ); + self.target + .remove_event_listener_with_callback( + &self.r#type, + self.callback.as_ref().unchecked_ref(), + ) + .expect("failed to remove event listener"); let _ = ManuallyDrop::into_inner(self.callback); } } diff --git a/src/html/listener.rs b/src/html/listener.rs index 54143386c50..b14e0c90199 100644 --- a/src/html/listener.rs +++ b/src/html/listener.rs @@ -76,17 +76,23 @@ macro_rules! impl_action { } #[cfg(feature = "web_sys")] - fn attach(&self, element: &Element) -> EventListenerHandle<$web_sys_type> { + fn attach(&self, element: Element) -> EventListenerHandle { let this = element.clone(); let callback = self.callback.clone(); - let listener = move |event: $web_sys_type| { + let listener = move |event: web_sys::Event| { event.stop_propagation(); + let event = event.dyn_into::<$web_sys_type>().expect("wrong event type"); callback.emit($convert(&this, event)); }; let target = EventTarget::from(element.clone()); - let listener = Closure::wrap(Box::new(listener) as Box); - target.add_event_listener_with_callback(stringify!($name), listener.as_ref().unchecked_ref()); + let listener = Closure::wrap(Box::new(listener) as Box); + target + .add_event_listener_with_callback( + stringify!($name), + listener.as_ref().unchecked_ref(), + ) + .expect("failed to add event listener"); return EventListenerHandle { target, From 8397709e6a7116163387026c08127ac1bec69eba Mon Sep 17 00:00:00 2001 From: daxpedda Date: Wed, 18 Dec 2019 16:28:29 +0100 Subject: [PATCH 04/19] Fix build. --- build.rs | 4 +--- src/html/listener.rs | 8 ++++---- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/build.rs b/build.rs index 8c275c5bdbb..6408a07c1bd 100644 --- a/build.rs +++ b/build.rs @@ -2,9 +2,7 @@ use std::env; pub fn main() { if cfg!(all(feature = "web_sys", feature = "stdweb")) { - panic!("don't use `web_sys` and `stdweb` simultaneously") - } - else if cfg!(not(any(feature = "web_sys", feature = "stdweb"))) { + } else if cfg!(not(any(feature = "web_sys", feature = "stdweb"))) { panic!("please select either `web_sys` or `stdweb`") } diff --git a/src/html/listener.rs b/src/html/listener.rs index b14e0c90199..7df1299d7fb 100644 --- a/src/html/listener.rs +++ b/src/html/listener.rs @@ -3,7 +3,7 @@ use crate::callback::Callback; use crate::compat::EventListenerHandle; use crate::virtual_dom::Listener; #[cfg(feature = "stdweb")] -use stdweb::html_element::SelectElement; +use stdweb::web::html_element::SelectElement; #[cfg(feature = "stdweb")] #[allow(unused_imports)] use stdweb::{ @@ -15,12 +15,12 @@ use web_sys::{FileList, HtmlSelectElement as SelectElement}; macro_rules! impl_action { (|onaction: $action:ident, stdweb_event: $stdweb_type:ident, web_sys_event: $web_sys_type:ident| -> $ret:ty => $convert:expr) => { - paste::expr! { + paste::item! { impl_action!(|action: [], name: [], stdweb_event: $stdweb_type, web_sys_event: $web_sys_type| -> $ret => $convert); } }; (|onaction: $action:ident, stdweb_event: $stdweb_type:ident, web_sys_event: $web_sys_type:ident| => $convert:expr) => { - paste::expr! { + paste::item! { #[cfg(feature = "stdweb")] impl_action!(|action: [], name: [], stdweb_event: $stdweb_type, web_sys_event: $web_sys_type| -> $stdweb_type => $convert); #[cfg(feature = "web_sys")] @@ -28,7 +28,7 @@ macro_rules! impl_action { } }; (|onaction: $action:ident, name: $name:ident, stdweb_event: $stdweb_type:ident, web_sys_event: $web_sys_type:ident| => $convert:expr) => { - paste::expr! { + paste::item! { #[cfg(feature = "stdweb")] impl_action!(|action: [], name: $name, stdweb_event: $stdweb_type, web_sys_event: $web_sys_type| -> $stdweb_type => $convert); #[cfg(feature = "web_sys")] From f4a64ec3c35650affd4363d8b561607b20ba7a0a Mon Sep 17 00:00:00 2001 From: daxpedda Date: Wed, 18 Dec 2019 19:46:52 +0100 Subject: [PATCH 05/19] A cleaner solution? --- Cargo.toml | 3 + src/compat.rs | 6 + src/html/listener.rs | 236 ----------------------------------- src/html/listener/mod.rs | 121 ++++++++++++++++++ src/html/listener/stdweb.rs | 95 ++++++++++++++ src/html/listener/web_sys.rs | 109 ++++++++++++++++ 6 files changed, 334 insertions(+), 236 deletions(-) delete mode 100644 src/html/listener.rs create mode 100644 src/html/listener/mod.rs create mode 100644 src/html/listener/stdweb.rs create mode 100644 src/html/listener/web_sys.rs diff --git a/Cargo.toml b/Cargo.toml index 466754ea9de..089a9b47ae6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -49,9 +49,12 @@ features = [ "EventTarget", "FileList", "FocusEvent", + "HtmlInputElement", "HtmlSelectElement", + "HtmlTextAreaElement", "KeyboardEvent", "MouseEvent", + "Node", "PointerEvent", "TouchEvent", "UiEvent", diff --git a/src/compat.rs b/src/compat.rs index 5de5c41964a..106af5a7018 100644 --- a/src/compat.rs +++ b/src/compat.rs @@ -4,6 +4,12 @@ use std::mem::ManuallyDrop; use wasm_bindgen::{closure::Closure, JsCast}; use web_sys::{Event, EventTarget}; +/// Handler to an event listener, only use is to cancel the event. +// We can't use `gloo`s implementation because it cancels the event upon dropping the handler, but +// we want the event to only be cancelled when the user desires. The main issue here is that +// `wasm-bindgen` doesn't support moving a closure to WASM, so the closure has to be "forgotten" +// and not be dropped, therefore the use of `ManuallyDrop` here. +#[derive(Debug)] pub struct EventListenerHandle { target: EventTarget, r#type: &'static str, diff --git a/src/html/listener.rs b/src/html/listener.rs deleted file mode 100644 index 7df1299d7fb..00000000000 --- a/src/html/listener.rs +++ /dev/null @@ -1,236 +0,0 @@ -use crate::callback::Callback; -#[cfg(feature = "web_sys")] -use crate::compat::EventListenerHandle; -use crate::virtual_dom::Listener; -#[cfg(feature = "stdweb")] -use stdweb::web::html_element::SelectElement; -#[cfg(feature = "stdweb")] -#[allow(unused_imports)] -use stdweb::{ - _js_impl, js, - web::{EventListenerHandle, FileList, INode}, -}; -#[cfg(feature = "web_sys")] -use web_sys::{FileList, HtmlSelectElement as SelectElement}; - -macro_rules! impl_action { - (|onaction: $action:ident, stdweb_event: $stdweb_type:ident, web_sys_event: $web_sys_type:ident| -> $ret:ty => $convert:expr) => { - paste::item! { - impl_action!(|action: [], name: [], stdweb_event: $stdweb_type, web_sys_event: $web_sys_type| -> $ret => $convert); - } - }; - (|onaction: $action:ident, stdweb_event: $stdweb_type:ident, web_sys_event: $web_sys_type:ident| => $convert:expr) => { - paste::item! { - #[cfg(feature = "stdweb")] - impl_action!(|action: [], name: [], stdweb_event: $stdweb_type, web_sys_event: $web_sys_type| -> $stdweb_type => $convert); - #[cfg(feature = "web_sys")] - impl_action!(|action: [], name: [], stdweb_event: $stdweb_type, web_sys_event: $web_sys_type| -> $web_sys_type => $convert); - } - }; - (|onaction: $action:ident, name: $name:ident, stdweb_event: $stdweb_type:ident, web_sys_event: $web_sys_type:ident| => $convert:expr) => { - paste::item! { - #[cfg(feature = "stdweb")] - impl_action!(|action: [], name: $name, stdweb_event: $stdweb_type, web_sys_event: $web_sys_type| -> $stdweb_type => $convert); - #[cfg(feature = "web_sys")] - impl_action!(|action: [], name: $name, stdweb_event: $stdweb_type, web_sys_event: $web_sys_type| -> $web_sys_type => $convert); - } - }; - (|action: $action:ident, stdweb_event: $stdweb_type:ident, web_sys_event: $web_sys_type:ident| => $convert:expr) => { - #[cfg(feature = "stdweb")] - impl_action!(|action: $action, name: $action, stdweb_event: $stdweb_type, web_sys_event: $web_sys_type| -> $stdweb_type => $convert); - #[cfg(feature = "web_sys")] - impl_action!(|action: $action, name: $action, stdweb_event: $stdweb_type, web_sys_event: $web_sys_type| -> $web_sys_type => $convert); - }; - (|action: $action:ident, name: $name:ident, stdweb_event: $stdweb_type:ident, web_sys_event: $web_sys_type:ident| -> $ret:ty => $convert:expr) => { - /// An abstract implementation of a listener. - pub mod $action { - #[cfg(feature = "stdweb")] - use stdweb::web::{IEventTarget, Element, event::{IEvent, $stdweb_type}}; - #[cfg(feature = "web_sys")] - use ::{ - std::mem::ManuallyDrop, - wasm_bindgen::{closure::Closure, JsCast}, - web_sys::{Element, EventTarget, $web_sys_type}, - }; - use super::*; - - /// A wrapper for a callback which attaches event listeners to elements. - #[derive(Clone, Debug)] - pub struct Wrapper { - callback: Callback, - } - - impl Wrapper { - /// Create a wrapper for an event-typed callback - pub fn new(callback: Callback) -> Self { - Wrapper { callback } - } - } - - /// And event type which keeps the returned type. - pub type Event = $ret; - - impl Listener for Wrapper { - fn kind(&self) -> &'static str { - stringify!($action) - } - - #[cfg(feature = "web_sys")] - fn attach(&self, element: Element) -> EventListenerHandle { - let this = element.clone(); - let callback = self.callback.clone(); - let listener = move |event: web_sys::Event| { - event.stop_propagation(); - let event = event.dyn_into::<$web_sys_type>().expect("wrong event type"); - callback.emit($convert(&this, event)); - }; - - let target = EventTarget::from(element.clone()); - let listener = Closure::wrap(Box::new(listener) as Box); - target - .add_event_listener_with_callback( - stringify!($name), - listener.as_ref().unchecked_ref(), - ) - .expect("failed to add event listener"); - - return EventListenerHandle { - target, - r#type: stringify!($name), - callback: ManuallyDrop::new(listener), - } - } - - #[cfg(feature = "stdweb")] - fn attach(&self, element: &Element) -> EventListenerHandle { - let this = element.clone(); - let callback = self.callback.clone(); - let listener = move |event: $stdweb_type| { - event.stop_propagation(); - callback.emit($convert(&this, event)); - }; - element.add_event_listener(listener) - } - } - } - }; -} - -// Inspired by: http://package.elm-lang.org/packages/elm-lang/html/2.0.0/Html-Events -impl_action!(|onaction: click, stdweb_event: ClickEvent, web_sys_event: MouseEvent| => |_, event| { event }); -impl_action!(|onaction: doubleclick, name: dblclick, stdweb_event: DoubleClickEvent, web_sys_event: MouseEvent| => |_, event| { event }); -impl_action!(|onaction: keypress, stdweb_event: KeyPressEvent, web_sys_event: KeyboardEvent| => |_, event| { event }); -impl_action!(|onaction: keydown, stdweb_event: KeyDownEvent, web_sys_event: KeyboardEvent| => |_, event| { event }); -impl_action!(|onaction: keyup, stdweb_event: KeyUpEvent, web_sys_event: KeyboardEvent| => |_, event| { event }); -impl_action!(|onaction: mousemove, stdweb_event: MouseMoveEvent, web_sys_event: MouseEvent| => |_, event| { event }); -impl_action!(|onaction: mousedown, stdweb_event: MouseDownEvent, web_sys_event: MouseEvent| => |_, event| { event }); -impl_action!(|onaction: mouseup, stdweb_event: MouseUpEvent, web_sys_event: MouseEvent| => |_, event| { event }); -impl_action!(|onaction: mouseover, stdweb_event: MouseOverEvent, web_sys_event: MouseEvent| => |_, event| { event }); -impl_action!(|onaction: mouseout, stdweb_event: MouseOutEvent, web_sys_event: MouseEvent| => |_, event| { event }); -impl_action!(|onaction: mouseenter, stdweb_event: MouseEnterEvent, web_sys_event: MouseEvent| => |_, event| { event }); -impl_action!(|onaction: mouseleave, stdweb_event: MouseLeaveEvent, web_sys_event: MouseEvent| => |_, event| { event }); -#[cfg(feature = "stdweb")] -impl_action!(|onaction: mousewheel, stdweb_event: MouseWheelEvent, web_sys_event: MouseEvent| => |_, event| { event }); -impl_action!(|onaction: gotpointercapture, stdweb_event: GotPointerCaptureEvent, web_sys_event: PointerEvent| => |_, event| { event }); -impl_action!(|onaction: lostpointercapture, stdweb_event: LostPointerCaptureEvent, web_sys_event: PointerEvent| => |_, event| { event }); -impl_action!(|onaction: pointercancel, stdweb_event: PointerCancelEvent, web_sys_event: PointerEvent| => |_, event| { event }); -impl_action!(|onaction: pointerdown, stdweb_event: PointerDownEvent, web_sys_event: PointerEvent| => |_, event| { event }); -impl_action!(|onaction: pointerenter, stdweb_event: PointerEnterEvent, web_sys_event: PointerEvent| => |_, event| { event }); -impl_action!(|onaction: pointerleave, stdweb_event: PointerLeaveEvent, web_sys_event: PointerEvent| => |_, event| { event }); -impl_action!(|onaction: pointermove, stdweb_event: PointerMoveEvent, web_sys_event: PointerEvent| => |_, event| { event }); -impl_action!(|onaction: pointerout, stdweb_event: PointerOutEvent, web_sys_event: PointerEvent| => |_, event| { event }); -impl_action!(|onaction: pointerover, stdweb_event: PointerOverEvent, web_sys_event: PointerEvent| => |_, event| { event }); -impl_action!(|onaction: pointerup, stdweb_event: PointerUpEvent, web_sys_event: PointerEvent| => |_, event| { event }); -impl_action!(|onaction: scroll, stdweb_event: ScrollEvent, web_sys_event: UiEvent| => |_, event| { event }); -impl_action!(|onaction: blur, stdweb_event: BlurEvent, web_sys_event: FocusEvent| => |_, event| { event }); -impl_action!(|onaction: focus, stdweb_event: FocusEvent, web_sys_event: FocusEvent| => |_, event| { event }); -impl_action!(|onaction: submit, stdweb_event: SubmitEvent, web_sys_event: Event| => |_, event| { event }); -impl_action!(|onaction: dragstart, stdweb_event: DragStartEvent, web_sys_event: DragEvent| => |_, event| { event }); -impl_action!(|onaction: drag, stdweb_event: DragEvent, web_sys_event: DragEvent| => |_, event| { event }); -impl_action!(|onaction: dragend, stdweb_event: DragEndEvent, web_sys_event: DragEvent| => |_, event| { event }); -impl_action!(|onaction: dragenter, stdweb_event: DragEnterEvent, web_sys_event: DragEvent| => |_, event| { event }); -impl_action!(|onaction: dragleave, stdweb_event: DragLeaveEvent, web_sys_event: DragEvent| => |_, event| { event }); -impl_action!(|onaction: dragover, stdweb_event: DragOverEvent, web_sys_event: DragEvent| => |_, event| { event }); -impl_action!(|onaction: dragexit, name: dragend, stdweb_event: DragExitEvent, web_sys_event: DragEvent| => |_, event| { event }); -impl_action!(|onaction: drop, stdweb_event: DragDropEvent, web_sys_event: DragEvent| => |_, event| { event }); -impl_action!(|onaction: contextmenu, stdweb_event: ContextMenuEvent, web_sys_event: MouseEvent| => |_, event| { event }); -impl_action!(|onaction: input, stdweb_event: InputEvent, web_sys_event: Event| -> InputData => |this: &Element, _| { - use stdweb::web::html_element::{InputElement, TextAreaElement}; - use stdweb::unstable::TryInto; - // Normally only InputElement or TextAreaElement can have an oninput event listener. In - // practice though any element with `contenteditable=true` may generate such events, - // therefore here we fall back to just returning the text content of the node. - // See https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/input_event. - let v1 = this.clone().try_into().map(|input: InputElement| input.raw_value()).ok(); - let v2 = this.clone().try_into().map(|input: TextAreaElement| input.value()).ok(); - let v3 = this.text_content(); - let value = v1.or(v2).or(v3) - .expect("only an InputElement or TextAreaElement or an element with contenteditable=true can have an oninput event listener"); - InputData { value } -}); -impl_action!(|onaction: change, stdweb_event: ChangeEvent, web_sys_event: Event| -> ChangeData => |this: &Element, _| { - use stdweb::web::{FileList, IElement}; - use stdweb::web::html_element::{InputElement, TextAreaElement, SelectElement}; - use stdweb::unstable::TryInto; - match this.node_name().as_ref() { - "INPUT" => { - let input: InputElement = this.clone().try_into().unwrap(); - let is_file = input.get_attribute("type").map(|value| { - value.eq_ignore_ascii_case("file") - }) - .unwrap_or(false); - if is_file { - let files: FileList = js!( return @{input}.files; ) - .try_into() - .unwrap(); - ChangeData::Files(files) - } else { - ChangeData::Value(input.raw_value()) - } - } - "TEXTAREA" => { - let tae: TextAreaElement = this.clone().try_into().unwrap(); - ChangeData::Value(tae.value()) - } - "SELECT" => { - let se: SelectElement = this.clone().try_into().unwrap(); - ChangeData::Select(se) - } - _ => { - panic!("only an InputElement, TextAreaElement or SelectElement can have an onchange event listener"); - } - } -}); -impl_action!(|action: touchcancel, stdweb_event: TouchCancel, web_sys_event: TouchEvent| => |_, event| { event }); -impl_action!(|action: touchend, stdweb_event: TouchEnd, web_sys_event: TouchEvent| => |_, event| { event }); -impl_action!(|action: touchenter, stdweb_event: TouchEnter, web_sys_event: TouchEvent| => |_, event| { event }); -impl_action!(|action: touchmove, stdweb_event: TouchMove, web_sys_event: TouchEvent| => |_, event| { event }); -impl_action!(|action: touchstart, stdweb_event: TouchStart, web_sys_event: TouchEvent| => |_, event| { event }); - -/// A type representing data from `oninput` event. -#[derive(Debug)] -pub struct InputData { - /// Inserted characters. Contains value from - /// [InputEvent](https://developer.mozilla.org/en-US/docs/Web/API/InputEvent/data). - pub value: String, -} - -// There is no '.../Web/API/ChangeEvent/data' (for onchange) similar to -// https://developer.mozilla.org/en-US/docs/Web/API/InputEvent/data (for oninput). -// ChangeData actually contains the value of the InputElement/TextAreaElement -// after `change` event occured or contains the SelectElement (see more at the -// variant ChangeData::Select) - -/// A type representing change of value(s) of an element after committed by user -/// ([onchange event](https://developer.mozilla.org/en-US/docs/Web/Events/change)). -#[derive(Debug)] -pub enum ChangeData { - /// Value of the element in cases of ``, `