wal_core/events/
mod.rs

1use std::{borrow::Cow, fmt::Debug, hash::Hash, ops::Deref};
2
3use gloo::events::EventListener;
4use wasm_bindgen::JsCast;
5use web_sys::Element;
6
7use crate::{component::callback::Callback, virtual_dom::dom};
8
9#[macro_use]
10mod macros;
11
12define_events!(
13    Event,
14    AnimationEvent,
15    DragEvent,
16    FocusEvent,
17    InputEvent,
18    KeyboardEvent,
19    MouseEvent,
20    PointerEvent,
21    ProgressEvent,
22    SubmitEvent,
23    TouchEvent,
24    TransitionEvent,
25    WheelEvent
26);
27
28pub trait EventCreator {
29    fn get_event_type(&self) -> Cow<'static, str>;
30    fn create_callback(&self) -> Box<dyn FnMut(&web_sys::Event)>;
31}
32
33impl Debug for dyn EventCreator {
34    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
35        format!("EventCreator of type {}", self.get_event_type()).fmt(f)
36    }
37}
38
39impl PartialEq for dyn EventCreator {
40    fn eq(&self, other: &Self) -> bool {
41        self.get_event_type() == other.get_event_type()
42    }
43}
44
45trait EventCreatorType {
46    type Creator: EventCreator;
47}
48
49pub struct UnspecializedEventCreator {
50    pub event_type: Cow<'static, str>,
51    pub callback: Callback<Event>,
52}
53
54impl EventCreator for UnspecializedEventCreator {
55    fn get_event_type(&self) -> Cow<'static, str> {
56        self.event_type.clone()
57    }
58
59    fn create_callback(&self) -> Box<dyn FnMut(&web_sys::Event)> {
60        let callback = self.callback.clone();
61        Box::new(move |event: &web_sys::Event| {
62            let event = Event(event.clone());
63            callback.emit(event);
64        })
65    }
66}
67
68event_creators! {
69    AnimationEventCreator(AnimationEvent),
70    DragEventCreator(DragEvent),
71    FocusEventCreator(FocusEvent),
72    InputEventCreator(InputEvent),
73    KeyboardEventCreator(KeyboardEvent),
74    MouseEventCreator(MouseEvent),
75    PointerEventCreator(PointerEvent),
76    ProgressEventCreator(ProgressEvent),
77    SubmitEventCreator(SubmitEvent),
78    TouchEventCreator(TouchEvent),
79    TransitionEventCreator(TransitionEvent),
80    WheelEventCreator(WheelEvent)
81}
82
83unspecialized_event_creators_constructor! {
84    onabort,
85    oncancel,
86    oncanplay,
87    oncanplaythrough,
88    onchange,
89    onclose,
90    oncopy,
91    oncuechange,
92    oncut,
93    ondurationchange,
94    onemptied,
95    onended,
96    onerror,
97    oninvalid,
98    onload,
99    onloadeddata,
100    onloadedmetadata,
101    onpaste,
102    onpause,
103    onplay,
104    onplaying,
105    onpointerlockchange,
106    onpointerlockerror,
107    onratechange,
108    onreset,
109    onresize,
110    onscroll,
111    onsecuritypolicyviolation,
112    onseeked,
113    onseeking,
114    onselect,
115    onselectionchange,
116    onselectstart,
117    onshow,
118    onslotchange,
119    onstalled,
120    onsuspend,
121    ontimeupdate,
122    ontoggle,
123    onvolumechange,
124    onwaiting,
125
126    // FormData Events
127    onformdata  // web_sys is missing `FormDataEvent`` so it is handled as Unspecialized Event
128}
129
130event_creators_constructor! {
131    // Animation Events
132    onanimationcancel(AnimationEvent),
133    onanimationend(AnimationEvent),
134    onanimationiteration(AnimationEvent),
135    onanimationstart(AnimationEvent),
136
137    // Drag Events
138    ondrag(DragEvent),
139    ondragend(DragEvent),
140    ondragenter(DragEvent),
141    ondragexit(DragEvent),
142    ondragleave(DragEvent),
143    ondragover(DragEvent),
144    ondragstart(DragEvent),
145    ondrop(DragEvent),
146
147    // Focus Events
148    onblur(FocusEvent),
149    onfocus(FocusEvent),
150    onfocusin(FocusEvent),
151    onfocusout(FocusEvent),
152
153    // Input Events
154    oninput(InputEvent),
155
156    // Keyboard Events
157    onkeydown(KeyboardEvent),
158    onkeypress(KeyboardEvent),
159    onkeyup(KeyboardEvent),
160
161    // Mouse Events
162    onauxclick(MouseEvent),
163    onclick(MouseEvent),
164    oncontextmenu(MouseEvent),
165    ondblclick(MouseEvent),
166    onmousedown(MouseEvent),
167    onmouseenter(MouseEvent),
168    onmouseleave(MouseEvent),
169    onmousemove(MouseEvent),
170    onmouseout(MouseEvent),
171    onmouseover(MouseEvent),
172    onmouseup(MouseEvent),
173
174    // Pointer Events
175    ongotpointercapture(PointerEvent),
176    onlostpointercapture(PointerEvent),
177    onpointercancel(PointerEvent),
178    onpointerdown(PointerEvent),
179    onpointerenter(PointerEvent),
180    onpointerleave(PointerEvent),
181    onpointermove(PointerEvent),
182    onpointerout(PointerEvent),
183    onpointerover(PointerEvent),
184    onpointerup(PointerEvent),
185
186    // Progress Events
187    onloadend(ProgressEvent),
188    onloadstart(ProgressEvent),
189    onprogress(ProgressEvent),
190
191    // Submit Events
192    onsubmit(SubmitEvent),
193
194    // Touch Events
195    ontouchcancel(TouchEvent),
196    ontouchend(TouchEvent),
197    ontouchmove(TouchEvent),
198    ontouchstart(TouchEvent),
199
200    // Transition Events
201    ontransitioncancel(TransitionEvent),
202    ontransitionend(TransitionEvent),
203    ontransitionrun(TransitionEvent),
204    ontransitionstart(TransitionEvent),
205
206    // Wheel Events
207    onwheel(WheelEvent)
208}
209
210#[derive(Debug)]
211pub struct EventHandler {
212    event_creator: Box<dyn EventCreator>,
213    event_listener: Option<EventListener>,
214}
215
216impl EventHandler {
217    pub fn new(event_creator: Box<dyn EventCreator>) -> Self {
218        Self {
219            event_creator,
220            event_listener: None,
221        }
222    }
223
224    pub(crate) fn attach(&mut self, element: &Element) {
225        let event_type = self.get_event_type();
226        let callback = self.event_creator.create_callback();
227        self.event_listener = Some(dom::create_event_listener(element, event_type, callback));
228    }
229
230    pub(crate) fn get_event_type(&self) -> Cow<'static, str> {
231        self.event_creator.get_event_type()
232    }
233}
234
235impl PartialEq for EventHandler {
236    fn eq(&self, other: &Self) -> bool {
237        let event_listener_eq = match (&self.event_listener, &other.event_listener) {
238            (Some(self_event_listener), Some(other_event_listener)) => {
239                self_event_listener.event_type() == other_event_listener.event_type()
240            }
241            (None, None) => true,
242            _ => false,
243        };
244        event_listener_eq && *self.event_creator == *other.event_creator
245    }
246}
247
248impl Eq for EventHandler {}
249
250impl Hash for EventHandler {
251    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
252        self.get_event_type().hash(state);
253    }
254}
255
256#[cfg(test)]
257mod tests {
258    use std::{cell::RefCell, rc::Rc};
259
260    use super::*;
261    use wasm_bindgen_test::*;
262
263    wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser);
264
265    #[wasm_bindgen_test]
266    fn onclick_sepcialized_event_handler_constructor_should_create_event_handler_of_correct_type() {
267        // Arrange
268        let callback = Callback::new(|_| {});
269
270        // Act
271        let event_creator = onclick(callback);
272
273        // Assert
274        let event_type = event_creator.get_event_type();
275        assert_eq!(event_type, "click");
276    }
277
278    #[wasm_bindgen_test]
279    fn onclick_specialized_event_handler_constructor_should_create_event_handler_with_correct_callback(
280    ) {
281        // Arrange
282        let was_callback_executed = Rc::new(RefCell::new(false));
283        let was_callback_executed_clone = was_callback_executed.clone();
284        let click = "click";
285        let callback = Callback::new(move |mouse_event: MouseEvent| {
286            assert_eq!(mouse_event.type_(), click);
287            *was_callback_executed_clone.borrow_mut() = true;
288        });
289        let web_sys_mouse_click_event = web_sys::MouseEvent::new(click).unwrap();
290
291        // Act
292        let event_creator = onclick(callback);
293
294        // Assert
295        let mut event_callback = event_creator.create_callback();
296        (*event_callback)(web_sys_mouse_click_event.as_ref());
297        assert!(*was_callback_executed.borrow());
298    }
299
300    #[wasm_bindgen_test]
301    fn onabort_unspecialized_event_handler_constructor_should_create_event_handler_of_correct_type()
302    {
303        // Arrange
304        let callback = Callback::new(|_| {});
305
306        // Act
307        let event_creator = onabort(callback);
308
309        // Assert
310        let event_type = event_creator.get_event_type();
311        assert_eq!(event_type, "abort");
312    }
313
314    #[wasm_bindgen_test]
315    fn onabort_unspecialized_event_handler_constructor_should_create_event_handler_with_correct_callback(
316    ) {
317        // Arrange
318        let was_callback_executed = Rc::new(RefCell::new(false));
319        let was_callback_executed_clone = was_callback_executed.clone();
320        let abort = "abort";
321        let callback = Callback::new(move |event: Event| {
322            assert_eq!(event.type_(), abort);
323            *was_callback_executed_clone.borrow_mut() = true;
324        });
325
326        // Act
327        let event_creator = onabort(callback);
328
329        // Assert
330        let mut event_callback = event_creator.create_callback();
331        (*event_callback)(web_sys::Event::new(abort).unwrap().as_ref());
332        assert!(*was_callback_executed.borrow());
333    }
334
335    #[wasm_bindgen_test]
336    fn define_mouse_event_should_define_correct_event() {
337        // Arrange
338        let click = "click";
339        let web_sys_mouse_click_event = web_sys::MouseEvent::new(click).unwrap();
340
341        // Act
342        let event = MouseEvent::new(web_sys_mouse_click_event);
343
344        // Assert
345        assert_eq!(event.type_(), click);
346    }
347
348    struct TestEventCreator {
349        event_type: Cow<'static, str>,
350        flag: Rc<RefCell<bool>>,
351    }
352
353    impl EventCreator for TestEventCreator {
354        fn get_event_type(&self) -> Cow<'static, str> {
355            self.event_type.clone()
356        }
357
358        fn create_callback(&self) -> Box<dyn FnMut(&web_sys::Event)> {
359            let flag = self.flag.clone();
360            Box::new(move |_| {
361                *flag.borrow_mut() = true;
362            })
363        }
364    }
365
366    #[wasm_bindgen_test]
367    fn event_handler_attach_should_attach_event_to_an_element() {
368        // Arrange
369        let window = web_sys::window().unwrap();
370        let document = window.document().unwrap();
371        let body = document.body().unwrap();
372
373        let flag = Rc::new(RefCell::new(false));
374        let event_creator = Box::new(TestEventCreator {
375            event_type: Cow::from("click"),
376            flag: flag.clone(),
377        });
378        let mut handler = EventHandler::new(event_creator);
379        handler.attach(&body);
380        let event = web_sys::Event::new("click").unwrap();
381
382        // Act
383        body.dispatch_event(&event).unwrap();
384
385        // Assert
386        assert!(*flag.borrow());
387    }
388
389    #[wasm_bindgen_test]
390    fn get_event_type_should_get_correct_event_type() {
391        // Arrange
392        let callback = Callback::new(|_| {});
393        let event_creator = onclick(callback);
394        let handler = EventHandler::new(event_creator);
395
396        // Act
397        let event_type = handler.get_event_type();
398
399        // Assert
400        assert_eq!(event_type, "click");
401    }
402}