Skip to main content

dioxus_web/events/
mod.rs

1use dioxus_html::{DragData, FormData, HtmlEventConverter, ImageData, PlatformEventData};
2use form::WebFormData;
3use load::WebImageEvent;
4use wasm_bindgen::JsCast;
5use web_sys::{Document, Element, Event};
6
7mod animation;
8mod cancel;
9mod clipboard;
10mod composition;
11mod drag;
12mod file;
13mod focus;
14mod form;
15mod keyboard;
16mod load;
17mod media;
18#[cfg(feature = "mounted")]
19mod mounted;
20mod mouse;
21mod pointer;
22mod resize;
23mod scroll;
24mod selection;
25mod toggle;
26mod touch;
27mod transition;
28mod visible;
29mod wheel;
30
31/// A wrapper for the websys event that allows us to give it the impls from dioxus-html
32pub(crate) struct Synthetic<T: 'static> {
33    /// The inner web sys event that the synthetic event wraps
34    pub event: T,
35}
36
37impl<T: 'static> Synthetic<T> {
38    /// Create a new synthetic event from a web sys event
39    pub fn new(event: T) -> Self {
40        Self { event }
41    }
42}
43
44pub(crate) struct WebEventConverter;
45
46#[inline(always)]
47fn downcast_event(event: &dioxus_html::PlatformEventData) -> &GenericWebSysEvent {
48    event
49        .downcast::<GenericWebSysEvent>()
50        .expect("event should be a GenericWebSysEvent")
51}
52
53macro_rules! with_web_event_converters {
54    ($macro:ident) => {
55        $macro! {
56            convert_animation_data(AnimationData) => web_sys::AnimationEvent;
57            convert_cancel_data(CancelData) => web_sys::Event;
58            convert_clipboard_data(ClipboardData) => web_sys::Event;
59            convert_composition_data(CompositionData) => web_sys::CompositionEvent;
60            convert_drag_data(DragData) => web_sys::DragEvent => |event| {
61                let event = downcast_event(event);
62                DragData::new(Synthetic::new(
63                    event.raw.clone().unchecked_into::<web_sys::DragEvent>(),
64                ))
65            };
66            convert_focus_data(FocusData) => web_sys::FocusEvent;
67            convert_form_data(FormData) => web_sys::Event => |event| {
68                let event = downcast_event(event);
69                FormData::new(WebFormData::new(event.element.clone(), event.raw.clone()))
70            };
71            convert_image_data(ImageData) => web_sys::Event => |event| {
72                let event = downcast_event(event);
73                ImageData::new(WebImageEvent::new(
74                    event.raw.clone(),
75                    event.raw.type_() == "error",
76                ))
77            };
78            convert_keyboard_data(KeyboardData) => web_sys::KeyboardEvent;
79            convert_media_data(MediaData) => web_sys::Event;
80            convert_mounted_data(MountedData) => web_sys::Element => |event| {
81                #[cfg(feature = "mounted")]
82                {
83                    Synthetic::new(
84                        event
85                            .downcast::<web_sys::Element>()
86                            .expect("event should be a web_sys::Element")
87                            .clone(),
88                    )
89                    .into()
90                }
91                #[cfg(not(feature = "mounted"))]
92                {
93                    let _ = event;
94                    panic!("mounted events require the `mounted` feature on dioxus-web")
95                }
96            };
97            convert_mouse_data(MouseData) => web_sys::MouseEvent;
98            convert_pointer_data(PointerData) => web_sys::PointerEvent;
99            convert_resize_data(ResizeData) => web_sys::CustomEvent => |event| {
100                Synthetic::<web_sys::ResizeObserverEntry>::from(downcast_event(event).raw.clone()).into()
101            };
102            convert_scroll_data(ScrollData) => web_sys::Event;
103            convert_selection_data(SelectionData) => web_sys::Event;
104            convert_toggle_data(ToggleData) => web_sys::Event;
105            convert_touch_data(TouchData) => web_sys::TouchEvent;
106            convert_transition_data(TransitionData) => web_sys::TransitionEvent;
107            convert_visible_data(VisibleData) => web_sys::CustomEvent => |event| {
108                Synthetic::<web_sys::IntersectionObserverEntry>::from(downcast_event(event).raw.clone()).into()
109            };
110            convert_wheel_data(WheelData) => web_sys::WheelEvent;
111        }
112    };
113}
114
115macro_rules! expand_web_event_converter {
116    (
117        $(
118            $converter:ident($data:ident) => $web_ty:ty $(=> |$event:ident| $body:block)?;
119        )*
120    ) => {
121        macro_rules! web_event_type_matches {
122            $(
123                ($event_name:ident, $converter) => {
124                    $event_name.is_instance_of::<$web_ty>()
125                };
126            )*
127        }
128
129        impl HtmlEventConverter for WebEventConverter {
130            $(
131                expand_web_event_converter!(@method $converter, $data, $web_ty $(, $event, $body)?);
132            )*
133        }
134    };
135
136    (@method $converter:ident, $data:ident, $web_ty:ty) => {
137        #[inline(always)]
138        fn $converter(&self, event: &PlatformEventData) -> dioxus_html::$data {
139            Synthetic::new(downcast_event(event).raw.clone().unchecked_into::<$web_ty>()).into()
140        }
141    };
142
143    (@method $converter:ident, $data:ident, $web_ty:ty, $event:ident, $body:block) => {
144        #[inline(always)]
145        fn $converter(&self, $event: &PlatformEventData) -> dioxus_html::$data $body
146    };
147}
148
149with_web_event_converters!(expand_web_event_converter);
150
151macro_rules! expand_web_event_changes {
152    (
153        enum Event {
154            $(
155                #[convert = $converter:ident]
156                #[events = [
157                    $(
158                        $( #[$attr:meta] )*
159                        $name:ident => $raw:ident,
160                    )*
161                ]]
162                $(#[raw = [$($raw_only:ident),* $(,)?]])?
163                $group:ident($data:ident),
164            )*
165        }
166    ) => {
167        pub(crate) fn event_type_matches(name: &str, event: &web_sys::Event) -> bool {
168            let m = match name {
169                $(
170                    $( stringify!($raw) )|* $($(| stringify!($raw_only))*)? => {
171                        web_event_type_matches!(event, $converter)
172                    }
173                )*
174                _ => true,
175            };
176            if !m {
177                tracing::warn!("Ignoring \"{name}\": not the expected type: {event:?}");
178            }
179            m
180        }
181    };
182}
183
184dioxus_html::with_html_event_groups!(expand_web_event_changes);
185
186/// A extension trait for web-sys events that provides a way to get the event as a web-sys event.
187pub trait WebEventExt {
188    /// The web specific event type
189    type WebEvent;
190
191    /// Try to downcast this event as a `web-sys` event.
192    fn try_as_web_event(&self) -> Option<Self::WebEvent>;
193
194    /// Downcast this event as a `web-sys` event.
195    #[inline(always)]
196    fn as_web_event(&self) -> Self::WebEvent
197    where
198        Self::WebEvent: 'static,
199    {
200        self.try_as_web_event().unwrap_or_else(|| {
201            panic!(
202                "Error downcasting to `web-sys`, event should be a {}.",
203                std::any::type_name::<Self::WebEvent>()
204            )
205        })
206    }
207}
208
209struct GenericWebSysEvent {
210    raw: Event,
211    element: Element,
212}
213
214// todo: some of these events are being casted to the wrong event type.
215// We need tests that simulate clicks/etc and make sure every event type works.
216pub(crate) fn virtual_event_from_websys_event(
217    event: web_sys::Event,
218    target: Element,
219) -> PlatformEventData {
220    PlatformEventData::new(Box::new(GenericWebSysEvent {
221        raw: event,
222        element: target,
223    }))
224}
225
226pub(crate) fn load_document() -> Document {
227    web_sys::window()
228        .expect("should have access to the Window")
229        .document()
230        .expect("should have access to the Document")
231}