sauron_core/dom/
events.rs

1//! Create [events][0] Object
2//!
3//! [0]: https://developer.mozilla.org/en-US/docs/Web/Events
4use crate::dom::DomNode;
5use crate::dom::{document, window, Event};
6use crate::vdom;
7use crate::vdom::ComponentEventCallback;
8use crate::vdom::{Attribute, AttributeValue, EventCallback};
9use wasm_bindgen::JsCast;
10pub use web_sys::ClipboardEvent;
11pub use web_sys::{
12    AnimationEvent, FocusEvent, HashChangeEvent, KeyboardEvent, MouseEvent, Selection, TouchEvent,
13    TransitionEvent,
14};
15use web_sys::{
16    EventTarget, HtmlDetailsElement, HtmlElement, HtmlInputElement, HtmlSelectElement,
17    HtmlTextAreaElement,
18};
19
20#[derive(Clone, Copy)]
21#[repr(i16)]
22/// Mouse button used in the MouseEvent
23pub enum MouseButton {
24    /// Main button pressed, usually the left button or the un-initialized state
25    Primary = 0,
26
27    /// Auxiliary button pressed, usually the wheel button or the middle button (if present)
28    Auxiliary = 1,
29
30    /// Secondary button pressed, usually the right button
31    Secondary = 2,
32
33    /// Fourth button, typically the Browser Back button
34    Fourth = 3,
35
36    /// Fifth button, typically the Browser Forward button
37    Fifth = 4,
38}
39
40impl MouseButton {
41    /// check if the mouse event is on the primary button
42    pub fn is_primary(me: &MouseEvent) -> bool {
43        me.button() == MouseButton::Primary as i16
44    }
45
46    /// check if the mouse event is on the auxiliary button
47    pub fn is_auxiliary(me: &MouseEvent) -> bool {
48        me.button() == MouseButton::Auxiliary as i16
49    }
50}
51
52impl Event {
53    /// convert to web event
54    pub fn as_web(self) -> Option<web_sys::Event> {
55        match self {
56            Event::WebEvent(web_event) => Some(web_event),
57            _ => None,
58        }
59    }
60}
61
62impl From<MountEvent> for Event {
63    fn from(mount_event: MountEvent) -> Self {
64        Event::MountEvent(mount_event)
65    }
66}
67
68impl From<web_sys::Event> for Event {
69    fn from(web_event: web_sys::Event) -> Self {
70        Event::WebEvent(web_event)
71    }
72}
73
74impl From<web_sys::MouseEvent> for Event {
75    fn from(mouse_event: MouseEvent) -> Self {
76        let event: web_sys::Event = mouse_event
77            .dyn_into()
78            .expect("Unable to cast mouse event into event");
79        Event::from(event)
80    }
81}
82
83/// an event builder
84pub fn on<F, MSG>(event_name: &'static str, f: F) -> Attribute<MSG>
85where
86    F: FnMut(Event) -> MSG + 'static,
87    MSG: 'static,
88{
89    vdom::attr(
90        event_name,
91        AttributeValue::EventListener(EventCallback::from(f)),
92    )
93}
94
95/// on click event
96pub fn on_click<F, MSG>(mut f: F) -> Attribute<MSG>
97where
98    F: FnMut(MouseEvent) -> MSG + 'static,
99    MSG: 'static,
100{
101    on("click", move |event: Event| f(to_mouse_event(event)))
102}
103
104/// attach callback to the scroll event
105pub fn on_scroll<F, MSG>(mut f: F) -> Attribute<MSG>
106where
107    F: FnMut((i32, i32)) -> MSG + 'static,
108    MSG: 'static,
109{
110    on("scroll", move |event: Event| {
111        let web_event = event.as_web().expect("must be a web event");
112        let target = web_event.target().expect("can't get target");
113        if let Some(element) = target.dyn_ref::<web_sys::Element>() {
114            let scroll_top = element.scroll_top();
115            let scroll_left = element.scroll_left();
116            f((scroll_top, scroll_left))
117        } else {
118            let window = window();
119            let scroll_top = window.page_y_offset().expect("must get page offset") as i32;
120            let scroll_left = window.page_x_offset().expect("must get page offset") as i32;
121            f((scroll_top, scroll_left))
122        }
123    })
124}
125
126/// an event when a virtual Node is mounted the field node is the actual
127/// dom node where the virtual Node is created in the actual dom
128#[derive(Debug, Clone)]
129pub struct MountEvent {
130    /// the node where the virtual node is materialized into the actual dom
131    pub target_node: DomNode,
132}
133
134impl MountEvent {
135    /// create a native web event
136    pub fn create_web_event() -> web_sys::Event {
137        web_sys::Event::new("mount").expect("as event")
138    }
139}
140
141/// custom mount event
142pub fn on_mount<F, MSG>(mut f: F) -> Attribute<MSG>
143where
144    F: FnMut(MountEvent) -> MSG + 'static,
145    MSG: 'static,
146{
147    on("mount", move |event: Event| {
148        let web_event = event.as_web().expect("must be a web event");
149        let event_target = web_event.target().expect("must have a target");
150        let target_node: web_sys::Node = event_target.unchecked_into();
151        let me = MountEvent {
152            target_node: DomNode::from(target_node),
153        };
154        f(me)
155    })
156}
157
158/// custom mount event
159pub fn on_component_mount<F, MSG>(mut f: F) -> Attribute<MSG>
160where
161    F: FnMut(MountEvent) + 'static,
162    MSG: 'static,
163{
164    let cb = move |event: Event| {
165        let web_event = event.as_web().expect("must be a web event");
166        let event_target = web_event.target().expect("must have a target");
167        let target_node: web_sys::Node = event_target.unchecked_into();
168        let me = MountEvent {
169            target_node: DomNode::from(target_node),
170        };
171        f(me);
172    };
173    vdom::attr(
174        "mount",
175        AttributeValue::ComponentEventListener(ComponentEventCallback::from(cb)),
176    )
177}
178
179macro_rules! declare_events {
180
181    ( $(
182         $(#[$attr:meta])*
183         $name:ident => $event:ident => $mapper:ident => $ret:ty;
184       )*
185     ) => {
186        $(
187            doc_comment!{
188                concat!("attach an [",stringify!($name),"](https://developer.mozilla.org/en-US/docs/Web/API/GlobalEventHandlers/",stringify!($name),") event to the html element"),
189                $(#[$attr])*
190                #[inline]
191                pub fn $name<CB, MSG>(mut cb: CB) -> crate::vdom::Attribute<MSG>
192                    where CB: FnMut($ret) -> MSG + 'static,
193                          MSG: 'static,
194                    {
195                        on(stringify!($event), move|event:Event|{
196                            cb($mapper(event))
197                        })
198                }
199            }
200         )*
201    }
202}
203
204macro_rules! declare_html_events{
205    ( $(
206         $(#[$attr:meta])*
207         $name:ident => $event:ident => $mapper:ident => $ret:ty;
208       )*
209     ) => {
210        declare_events!{
211            $(
212                $(#[$attr])*
213                $name => $event => $mapper => $ret;
214            )*
215        }
216
217        /// html events
218        pub const HTML_EVENTS: &[&'static str] = &[$(stringify!($event),)*];
219    }
220}
221
222/// convert a generic event to MouseEvent
223fn to_mouse_event(event: Event) -> MouseEvent {
224    let web_event = event.as_web().expect("must be a web_sys event");
225    web_event.dyn_into().expect("Unable to cast to mouse event")
226}
227
228fn to_focus_event(event: Event) -> FocusEvent {
229    let web_event = event.as_web().expect("must be a web_sys event");
230    web_event.dyn_into().expect("Unable to cast to focus event")
231}
232
233fn to_keyboard_event(event: Event) -> KeyboardEvent {
234    let web_event = event.as_web().expect("must be a web_sys event");
235    web_event
236        .dyn_into()
237        .expect("unable to cast to keyboard event")
238}
239
240fn to_animation_event(event: Event) -> AnimationEvent {
241    let web_event = event.as_web().expect("must be a web_sys event");
242    web_event
243        .dyn_into()
244        .expect("unable to cast to animation event")
245}
246
247fn to_transition_event(event: Event) -> TransitionEvent {
248    let web_event = event.as_web().expect("must be a web_sys event");
249    web_event
250        .dyn_into()
251        .expect("unable to cast to transition event")
252}
253
254fn to_touch_event(event: Event) -> TouchEvent {
255    let web_event = event.as_web().expect("must be web sys event");
256    web_event.dyn_into().expect("unable to cast to touch event")
257}
258
259fn to_webevent(event: Event) -> web_sys::Event {
260    match event {
261        Event::WebEvent(event) => event,
262        _ => panic!("not a web_event"),
263    }
264}
265
266fn to_hashchange_event(event: Event) -> HashChangeEvent {
267    let web_event = event.as_web().expect("must be a web_sys event");
268    web_event
269        .dyn_into()
270        .expect("unable to cast to hashchange event")
271}
272
273/// TODO: expand this much farther by getting the InputEvent data, data_transfer, event_type,
274/// is_composing events.
275/// a custom InputEvent to contain the input string value
276#[derive(Debug)]
277pub struct InputEvent {
278    /// the actual dom event
279    pub event: web_sys::Event,
280}
281
282impl InputEvent {
283    fn new(event: web_sys::Event) -> Self {
284        InputEvent { event }
285    }
286
287    /// call prevent default on the underlying event
288    pub fn prevent_default(&self) {
289        self.event.prevent_default()
290    }
291
292    /// call stop_propagation on the underlying event
293    pub fn stop_propagation(&self) {
294        self.event.stop_propagation()
295    }
296
297    /// the input value
298    /// TODO: this should be optional since there will be custom component
299    /// aside from `input`, `textarea`, `select`
300    pub fn value(&self) -> String {
301        let target: EventTarget = self.event.target().expect("Unable to get event target");
302        if let Some(input) = target.dyn_ref::<HtmlInputElement>() {
303            input.value()
304        } else if let Some(textarea) = target.dyn_ref::<HtmlTextAreaElement>() {
305            textarea.value()
306        } else if let Some(select) = target.dyn_ref::<HtmlSelectElement>() {
307            select.value()
308        } else if let Some(html_elm) = target.dyn_ref::<HtmlElement>() {
309            if let Some(value) = html_elm.get_attribute("value") {
310                log::info!("got value: {}", value);
311                value
312            } else {
313                log::info!("no value..");
314                if let Some(content) = html_elm.get_attribute("content") {
315                    log::info!("got content: {}", content);
316                    content
317                } else {
318                    log::info!("no content either..");
319                    "".to_string()
320                }
321            }
322        } else {
323            panic!("fail in mapping event into input event");
324        }
325    }
326
327    /// create a native web event
328    pub fn create_web_event() -> web_sys::Event {
329        web_sys::Event::new("input").expect("as event")
330    }
331
332    /// create a native web event, with composed set to true
333    pub fn create_web_event_composed() -> web_sys::Event {
334        let event_init = web_sys::EventInit::new();
335        event_init.set_composed(true);
336        web_sys::Event::new_with_event_init_dict("input", &event_init).expect("event init")
337    }
338}
339
340fn to_input_event(event: Event) -> InputEvent {
341    let web_event = event.as_web().expect("must be a web event");
342    InputEvent::new(web_event)
343}
344
345fn to_checked(event: Event) -> bool {
346    let web_event = event.as_web().expect("must be a web event");
347    let target: EventTarget = web_event.target().expect("Unable to get event target");
348    if let Some(input) = target.dyn_ref::<HtmlInputElement>() {
349        input.checked()
350    } else {
351        panic!("must be a html input element");
352    }
353}
354
355fn to_open(event: Event) -> bool {
356    let web_event = event.as_web().expect("must be a web event");
357    let target: EventTarget = web_event.target().expect("Unable to get event target");
358    if let Some(details) = target.dyn_ref::<HtmlDetailsElement>() {
359        details.open()
360    } else {
361        panic!("must be a html input element");
362    }
363}
364
365/// Note: paste event happens before the data is inserted into the target element
366/// therefore trying to access the data on the target element triggered from paste will get an
367/// empty text
368fn to_clipboard_event(event: Event) -> ClipboardEvent {
369    event
370        .as_web()
371        .expect("must be a web event")
372        .dyn_into()
373        .expect("unable to cast to clipboard event")
374}
375
376fn to_selection(_event: Event) -> Option<Selection> {
377    if let Ok(Some(selection)) = document().get_selection() {
378        Some(selection)
379    } else {
380        None
381    }
382}
383
384// Mouse events
385declare_html_events! {
386    on_auxclick => auxclick => to_mouse_event => MouseEvent;
387    on_animationend => animationend => to_animation_event => AnimationEvent;
388    on_transitionend => transitionend => to_transition_event => TransitionEvent;
389    on_contextmenu => contextmenu => to_mouse_event => MouseEvent;
390    on_dblclick  => dblclick => to_mouse_event => MouseEvent;
391    on_mousedown => mousedown => to_mouse_event => MouseEvent;
392    on_mouseenter => mouseenter => to_mouse_event => MouseEvent;
393    on_mouseleave => mouseleave => to_mouse_event => MouseEvent;
394    on_mousemove => mousemove => to_mouse_event => MouseEvent;
395    on_mouseover => mouseover => to_mouse_event => MouseEvent;
396    on_mouseout => mouseout => to_mouse_event => MouseEvent;
397    on_mouseup => mouseup => to_mouse_event => MouseEvent;
398    on_pointerlockchange => pointerlockchange => to_mouse_event => MouseEvent;
399    on_pointerlockerror => pointerlockerror => to_mouse_event => MouseEvent;
400    on_popstate => popstate => to_webevent => web_sys::Event;
401    on_select => select => to_webevent => web_sys::Event;
402    on_wheel => wheel => to_mouse_event => MouseEvent;
403    on_doubleclick => dblclick => to_mouse_event => MouseEvent;
404    on_keydown => keydown => to_keyboard_event => KeyboardEvent;
405    on_keypress => keypress => to_keyboard_event => KeyboardEvent;
406    on_keyup => keyup => to_keyboard_event => KeyboardEvent;
407    on_toggle => toggle => to_open => bool;
408    on_touchstart => touchstart => to_touch_event => TouchEvent;
409    on_touchend => touchend => to_touch_event => TouchEvent;
410    on_touchmove => touchmove => to_touch_event => TouchEvent;
411    on_focus => focus => to_focus_event => FocusEvent;
412    on_blur => blur => to_focus_event => FocusEvent;
413    on_reset => reset => to_webevent => web_sys::Event;
414    on_submit => submit => to_webevent => web_sys::Event;
415    on_input => input => to_input_event => InputEvent;
416    on_checked => input => to_checked => bool;
417    on_paste => paste => to_clipboard_event => ClipboardEvent;
418    on_copy => copy => to_clipboard_event => ClipboardEvent;
419    on_change => change => to_input_event => InputEvent;
420    on_broadcast => broadcast => to_input_event => InputEvent;
421    on_hashchange => hashchange => to_hashchange_event => HashChangeEvent;
422    on_readystatechange => readystatechange => to_webevent => web_sys::Event;
423    on_selectionchange => selectionchange => to_selection => Option<Selection>;
424}