Skip to main content

dioxus_web/events/
mod.rs

1use before_input::WebBeforeInputData;
2use clipboard::WebClipboardData;
3use dioxus_html::{
4    BeforeInputData, ClipboardData, DragData, FormData, HtmlEventConverter, ImageData,
5    PlatformEventData,
6};
7use form::WebFormData;
8use load::WebImageEvent;
9use wasm_bindgen::JsCast;
10use web_sys::{Document, Element, Event};
11
12mod animation;
13mod before_input;
14mod cancel;
15mod clipboard;
16mod composition;
17mod drag;
18mod file;
19mod focus;
20mod form;
21mod keyboard;
22mod load;
23mod media;
24#[cfg(feature = "mounted")]
25mod mounted;
26mod mouse;
27mod pointer;
28mod resize;
29mod scroll;
30mod selection;
31mod toggle;
32mod touch;
33mod transition;
34mod visible;
35mod wheel;
36
37/// A wrapper for the websys event that allows us to give it the impls from dioxus-html
38pub(crate) struct Synthetic<T: 'static> {
39    /// The inner web sys event that the synthetic event wraps
40    pub event: T,
41}
42
43impl<T: 'static> Synthetic<T> {
44    /// Create a new synthetic event from a web sys event
45    pub fn new(event: T) -> Self {
46        Self { event }
47    }
48}
49
50pub(crate) struct WebEventConverter;
51
52#[inline(always)]
53fn downcast_event(event: &dioxus_html::PlatformEventData) -> &GenericWebSysEvent {
54    event
55        .downcast::<GenericWebSysEvent>()
56        .expect("event should be a GenericWebSysEvent")
57}
58
59/// Read the textual value of an editable element.
60///
61/// Mirrors what the wasm renderer reports for `value` across the input-shaped events
62/// (`oninput`, `onchange`, `onbeforeinput`, …): the element's `value` for
63/// `<input>`/`<textarea>`/`<select>` (with checkboxes coerced to `"true"`/`"false"`),
64/// falling back to `textContent` for `contenteditable` elements. Returns `None` only
65/// when the target is none of these.
66pub(crate) fn editable_element_value(element: &Element) -> Option<String> {
67    element
68        .dyn_ref()
69        .map(
70            |input: &web_sys::HtmlInputElement| match input.type_().as_str() {
71                // todo: special case more input types
72                "checkbox" => match input.checked() {
73                    true => "true".to_string(),
74                    false => "false".to_string(),
75                },
76                _ => input.value(),
77            },
78        )
79        .or_else(|| {
80            element
81                .dyn_ref()
82                .map(|input: &web_sys::HtmlTextAreaElement| input.value())
83        })
84        // select elements are NOT input events - because - why woudn't they be??
85        .or_else(|| {
86            element
87                .dyn_ref()
88                .map(|input: &web_sys::HtmlSelectElement| input.value())
89        })
90        .or_else(|| {
91            element
92                .dyn_ref::<web_sys::HtmlElement>()
93                .and_then(|el| el.text_content())
94        })
95}
96
97macro_rules! with_web_event_converters {
98    ($macro:ident) => {
99        $macro! {
100            convert_animation_data(AnimationData) => web_sys::AnimationEvent;
101            convert_before_input_data(BeforeInputData) => web_sys::InputEvent => |event| {
102                let event = downcast_event(event);
103                BeforeInputData::new(WebBeforeInputData::new(
104                    event.element.clone(),
105                    event.raw.clone().unchecked_into::<web_sys::InputEvent>(),
106                ))
107            };
108            convert_cancel_data(CancelData) => web_sys::Event;
109            convert_clipboard_data(ClipboardData) => web_sys::ClipboardEvent => |event| {
110                let event = downcast_event(event);
111                ClipboardData::new(WebClipboardData::new(
112                    event.raw.clone().unchecked_into::<web_sys::ClipboardEvent>(),
113                ))
114            };
115            convert_composition_data(CompositionData) => web_sys::CompositionEvent;
116            convert_drag_data(DragData) => web_sys::DragEvent => |event| {
117                let event = downcast_event(event);
118                DragData::new(Synthetic::new(
119                    event.raw.clone().unchecked_into::<web_sys::DragEvent>(),
120                ))
121            };
122            convert_focus_data(FocusData) => web_sys::FocusEvent;
123            convert_form_data(FormData) => web_sys::Event => |event| {
124                let event = downcast_event(event);
125                FormData::new(WebFormData::new(event.element.clone(), event.raw.clone()))
126            };
127            convert_image_data(ImageData) => web_sys::Event => |event| {
128                let event = downcast_event(event);
129                ImageData::new(WebImageEvent::new(
130                    event.raw.clone(),
131                    event.raw.type_() == "error",
132                ))
133            };
134            convert_keyboard_data(KeyboardData) => web_sys::KeyboardEvent;
135            convert_media_data(MediaData) => web_sys::Event;
136            convert_mounted_data(MountedData) => web_sys::Element => |event| {
137                #[cfg(feature = "mounted")]
138                {
139                    Synthetic::new(
140                        event
141                            .downcast::<web_sys::Element>()
142                            .expect("event should be a web_sys::Element")
143                            .clone(),
144                    )
145                    .into()
146                }
147                #[cfg(not(feature = "mounted"))]
148                {
149                    let _ = event;
150                    panic!("mounted events require the `mounted` feature on dioxus-web")
151                }
152            };
153            convert_mouse_data(MouseData) => web_sys::MouseEvent;
154            convert_pointer_data(PointerData) => web_sys::PointerEvent;
155            convert_resize_data(ResizeData) => web_sys::CustomEvent => |event| {
156                Synthetic::<web_sys::ResizeObserverEntry>::from(downcast_event(event).raw.clone()).into()
157            };
158            convert_scroll_data(ScrollData) => web_sys::Event;
159            convert_selection_data(SelectionData) => web_sys::Event;
160            convert_toggle_data(ToggleData) => web_sys::Event;
161            convert_touch_data(TouchData) => web_sys::TouchEvent;
162            convert_transition_data(TransitionData) => web_sys::TransitionEvent;
163            convert_visible_data(VisibleData) => web_sys::CustomEvent => |event| {
164                Synthetic::<web_sys::IntersectionObserverEntry>::from(downcast_event(event).raw.clone()).into()
165            };
166            convert_wheel_data(WheelData) => web_sys::WheelEvent;
167        }
168    };
169}
170
171macro_rules! expand_web_event_converter {
172    (
173        $(
174            $converter:ident($data:ident) => $web_ty:ty $(=> |$event:ident| $body:block)?;
175        )*
176    ) => {
177        macro_rules! web_event_type_matches {
178            $(
179                ($event_name:ident, $converter) => {
180                    $event_name.is_instance_of::<$web_ty>()
181                };
182            )*
183        }
184
185        impl HtmlEventConverter for WebEventConverter {
186            $(
187                expand_web_event_converter!(@method $converter, $data, $web_ty $(, $event, $body)?);
188            )*
189        }
190    };
191
192    (@method $converter:ident, $data:ident, $web_ty:ty) => {
193        #[inline(always)]
194        fn $converter(&self, event: &PlatformEventData) -> dioxus_html::$data {
195            Synthetic::new(downcast_event(event).raw.clone().unchecked_into::<$web_ty>()).into()
196        }
197    };
198
199    (@method $converter:ident, $data:ident, $web_ty:ty, $event:ident, $body:block) => {
200        #[inline(always)]
201        fn $converter(&self, $event: &PlatformEventData) -> dioxus_html::$data $body
202    };
203}
204
205with_web_event_converters!(expand_web_event_converter);
206
207macro_rules! expand_web_event_changes {
208    (
209        enum Event {
210            $(
211                #[convert = $converter:ident]
212                #[events = [
213                    $(
214                        $( #[$attr:meta] )*
215                        $name:ident => $raw:ident,
216                    )*
217                ]]
218                $(#[raw = [$($raw_only:ident),* $(,)?]])?
219                $group:ident($data:ident),
220            )*
221        }
222    ) => {
223        pub(crate) fn event_type_matches(name: &str, event: &web_sys::Event) -> bool {
224            let m = match name {
225                $(
226                    $( stringify!($raw) )|* $($(| stringify!($raw_only))*)? => {
227                        web_event_type_matches!(event, $converter)
228                    }
229                )*
230                _ => true,
231            };
232            if !m {
233                tracing::warn!("Ignoring \"{name}\": not the expected type: {event:?}");
234            }
235            m
236        }
237    };
238}
239
240dioxus_html::with_html_event_groups!(expand_web_event_changes);
241
242/// A extension trait for web-sys events that provides a way to get the event as a web-sys event.
243pub trait WebEventExt {
244    /// The web specific event type
245    type WebEvent;
246
247    /// Try to downcast this event as a `web-sys` event.
248    fn try_as_web_event(&self) -> Option<Self::WebEvent>;
249
250    /// Downcast this event as a `web-sys` event.
251    #[inline(always)]
252    fn as_web_event(&self) -> Self::WebEvent
253    where
254        Self::WebEvent: 'static,
255    {
256        self.try_as_web_event().unwrap_or_else(|| {
257            panic!(
258                "Error downcasting to `web-sys`, event should be a {}.",
259                std::any::type_name::<Self::WebEvent>()
260            )
261        })
262    }
263}
264
265struct GenericWebSysEvent {
266    raw: Event,
267    element: Element,
268}
269
270// todo: some of these events are being casted to the wrong event type.
271// We need tests that simulate clicks/etc and make sure every event type works.
272pub(crate) fn virtual_event_from_websys_event(
273    event: web_sys::Event,
274    target: Element,
275) -> PlatformEventData {
276    PlatformEventData::new(Box::new(GenericWebSysEvent {
277        raw: event,
278        element: target,
279    }))
280}
281
282pub(crate) fn load_document() -> Document {
283    web_sys::window()
284        .expect("should have access to the Window")
285        .document()
286        .expect("should have access to the Document")
287}