tachys/html/
event.rs

1use crate::{
2    html::attribute::{
3        maybe_next_attr_erasure_macros::next_attr_combine, Attribute,
4        NamedAttributeKey,
5    },
6    renderer::{CastFrom, RemoveEventHandler, Rndr},
7    view::{Position, ToTemplate},
8};
9use send_wrapper::SendWrapper;
10use std::{
11    borrow::Cow,
12    cell::RefCell,
13    fmt::Debug,
14    marker::PhantomData,
15    ops::{Deref, DerefMut},
16    rc::Rc,
17};
18use wasm_bindgen::convert::FromWasmAbi;
19
20/// A cloneable event callback.
21pub type SharedEventCallback<E> = Rc<RefCell<dyn FnMut(E)>>;
22
23/// A function that can be called in response to an event.
24pub trait EventCallback<E>: 'static {
25    /// Runs the event handler.
26    fn invoke(&mut self, event: E);
27
28    /// Converts this into a cloneable/shared event handler.
29    fn into_shared(self) -> SharedEventCallback<E>;
30}
31
32impl<E: 'static> EventCallback<E> for SharedEventCallback<E> {
33    fn invoke(&mut self, event: E) {
34        let mut fun = self.borrow_mut();
35        fun(event)
36    }
37
38    fn into_shared(self) -> SharedEventCallback<E> {
39        self
40    }
41}
42
43impl<F, E> EventCallback<E> for F
44where
45    F: FnMut(E) + 'static,
46{
47    fn invoke(&mut self, event: E) {
48        self(event)
49    }
50
51    fn into_shared(self) -> SharedEventCallback<E> {
52        Rc::new(RefCell::new(self))
53    }
54}
55
56/// An event listener with a typed event target.
57pub struct Targeted<E, T> {
58    event: E,
59    el_ty: PhantomData<T>,
60}
61
62impl<E, T> Targeted<E, T> {
63    /// Returns the inner event.
64    pub fn into_inner(self) -> E {
65        self.event
66    }
67
68    /// Returns the event's target, as an HTML element of the correct type.
69    pub fn target(&self) -> T
70    where
71        T: CastFrom<crate::renderer::types::Element>,
72
73        crate::renderer::types::Event: From<E>,
74        E: Clone,
75    {
76        let ev = crate::renderer::types::Event::from(self.event.clone());
77        Rndr::event_target(&ev)
78    }
79}
80
81impl<E, T> Deref for Targeted<E, T> {
82    type Target = E;
83
84    fn deref(&self) -> &Self::Target {
85        &self.event
86    }
87}
88
89impl<E, T> DerefMut for Targeted<E, T> {
90    fn deref_mut(&mut self) -> &mut Self::Target {
91        &mut self.event
92    }
93}
94
95impl<E, T> From<E> for Targeted<E, T> {
96    fn from(event: E) -> Self {
97        Targeted {
98            event,
99            el_ty: PhantomData,
100        }
101    }
102}
103
104/// Creates an [`Attribute`] that will add an event listener to an element.
105pub fn on<E, F>(event: E, cb: F) -> On<E, F>
106where
107    F: FnMut(E::EventType) + 'static,
108    E: EventDescriptor + Send + 'static,
109    E::EventType: 'static,
110    E::EventType: From<crate::renderer::types::Event>,
111{
112    On {
113        event,
114        #[cfg(feature = "reactive_graph")]
115        owner: reactive_graph::owner::Owner::current().unwrap_or_default(),
116        cb: Some(SendWrapper::new(cb)),
117    }
118}
119
120/// Creates an [`Attribute`] that will add an event listener with a typed target to an element.
121#[allow(clippy::type_complexity)]
122pub fn on_target<E, T, F>(
123    event: E,
124    mut cb: F,
125) -> On<E, Box<dyn FnMut(E::EventType)>>
126where
127    T: HasElementType,
128    F: FnMut(Targeted<E::EventType, <T as HasElementType>::ElementType>)
129        + 'static,
130    E: EventDescriptor + Send + 'static,
131    E::EventType: 'static,
132
133    E::EventType: From<crate::renderer::types::Event>,
134{
135    on(event, Box::new(move |ev: E::EventType| cb(ev.into())))
136}
137
138/// An [`Attribute`] that adds an event listener to an element.
139pub struct On<E, F> {
140    event: E,
141    #[cfg(feature = "reactive_graph")]
142    owner: reactive_graph::owner::Owner,
143    cb: Option<SendWrapper<F>>,
144}
145
146impl<E, F> Clone for On<E, F>
147where
148    E: Clone,
149    F: Clone,
150{
151    fn clone(&self) -> Self {
152        Self {
153            event: self.event.clone(),
154            #[cfg(feature = "reactive_graph")]
155            owner: self.owner.clone(),
156            cb: self.cb.clone(),
157        }
158    }
159}
160
161impl<E, F> On<E, F>
162where
163    F: EventCallback<E::EventType>,
164    E: EventDescriptor + Send + 'static,
165    E::EventType: 'static,
166    E::EventType: From<crate::renderer::types::Event>,
167{
168    /// Attaches the event listener to the element.
169    pub fn attach(
170        self,
171        el: &crate::renderer::types::Element,
172    ) -> RemoveEventHandler<crate::renderer::types::Element> {
173        fn attach_inner(
174            el: &crate::renderer::types::Element,
175            cb: Box<dyn FnMut(crate::renderer::types::Event)>,
176            name: Cow<'static, str>,
177            // TODO investigate: does passing this as an option
178            // (rather than, say, having a const DELEGATED: bool)
179            // add to binary size?
180            delegation_key: Option<Cow<'static, str>>,
181        ) -> RemoveEventHandler<crate::renderer::types::Element> {
182            match delegation_key {
183                None => Rndr::add_event_listener(el, &name, cb),
184                Some(key) => {
185                    Rndr::add_event_listener_delegated(el, name, key, cb)
186                }
187            }
188        }
189
190        let mut cb = self.cb.expect("callback removed before attaching").take();
191
192        #[cfg(feature = "tracing")]
193        let span = tracing::Span::current();
194
195        let cb = Box::new(move |ev: crate::renderer::types::Event| {
196            #[cfg(all(debug_assertions, feature = "reactive_graph"))]
197            let _rx_guard =
198                reactive_graph::diagnostics::SpecialNonReactiveZone::enter();
199            #[cfg(feature = "tracing")]
200            let _tracing_guard = span.enter();
201
202            let ev = E::EventType::from(ev);
203
204            #[cfg(feature = "reactive_graph")]
205            self.owner.with(|| cb.invoke(ev));
206            #[cfg(not(feature = "reactive_graph"))]
207            cb.invoke(ev);
208        }) as Box<dyn FnMut(crate::renderer::types::Event)>;
209
210        attach_inner(
211            el,
212            cb,
213            self.event.name(),
214            (E::BUBBLES && cfg!(feature = "delegation"))
215                .then(|| self.event.event_delegation_key()),
216        )
217    }
218
219    /// Attaches the event listener to the element as a listener that is triggered during the capture phase,
220    /// meaning it will fire before any event listeners further down in the DOM.
221    pub fn attach_capture(
222        self,
223        el: &crate::renderer::types::Element,
224    ) -> RemoveEventHandler<crate::renderer::types::Element> {
225        fn attach_inner(
226            el: &crate::renderer::types::Element,
227            cb: Box<dyn FnMut(crate::renderer::types::Event)>,
228            name: Cow<'static, str>,
229        ) -> RemoveEventHandler<crate::renderer::types::Element> {
230            Rndr::add_event_listener_use_capture(el, &name, cb)
231        }
232
233        let mut cb = self.cb.expect("callback removed before attaching").take();
234
235        #[cfg(feature = "tracing")]
236        let span = tracing::Span::current();
237
238        let cb = Box::new(move |ev: crate::renderer::types::Event| {
239            #[cfg(all(debug_assertions, feature = "reactive_graph"))]
240            let _rx_guard =
241                reactive_graph::diagnostics::SpecialNonReactiveZone::enter();
242            #[cfg(feature = "tracing")]
243            let _tracing_guard = span.enter();
244
245            let ev = E::EventType::from(ev);
246
247            #[cfg(feature = "reactive_graph")]
248            self.owner.with(|| cb.invoke(ev));
249            #[cfg(not(feature = "reactive_graph"))]
250            cb.invoke(ev);
251        }) as Box<dyn FnMut(crate::renderer::types::Event)>;
252
253        attach_inner(el, cb, self.event.name())
254    }
255}
256
257impl<E, F> Debug for On<E, F>
258where
259    E: Debug,
260{
261    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
262        f.debug_tuple("On").field(&self.event).finish()
263    }
264}
265
266impl<E, F> Attribute for On<E, F>
267where
268    F: EventCallback<E::EventType>,
269    E: EventDescriptor + Send + 'static,
270    E::EventType: 'static,
271
272    E::EventType: From<crate::renderer::types::Event>,
273{
274    const MIN_LENGTH: usize = 0;
275    type AsyncOutput = Self;
276    // a function that can be called once to remove the event listener
277    type State = (
278        crate::renderer::types::Element,
279        Option<RemoveEventHandler<crate::renderer::types::Element>>,
280    );
281    type Cloneable = On<E, SharedEventCallback<E::EventType>>;
282    type CloneableOwned = On<E, SharedEventCallback<E::EventType>>;
283
284    #[inline(always)]
285    fn html_len(&self) -> usize {
286        0
287    }
288
289    #[inline(always)]
290    fn to_html(
291        self,
292        _buf: &mut String,
293        _class: &mut String,
294        _style: &mut String,
295        _inner_html: &mut String,
296    ) {
297    }
298
299    #[inline(always)]
300    fn hydrate<const FROM_SERVER: bool>(
301        self,
302        el: &crate::renderer::types::Element,
303    ) -> Self::State {
304        let cleanup = if E::CAPTURE {
305            self.attach_capture(el)
306        } else {
307            self.attach(el)
308        };
309        (el.clone(), Some(cleanup))
310    }
311
312    #[inline(always)]
313    fn build(self, el: &crate::renderer::types::Element) -> Self::State {
314        let cleanup = if E::CAPTURE {
315            self.attach_capture(el)
316        } else {
317            self.attach(el)
318        };
319        (el.clone(), Some(cleanup))
320    }
321
322    #[inline(always)]
323    fn rebuild(self, state: &mut Self::State) {
324        let (el, prev_cleanup) = state;
325        if let Some(prev) = prev_cleanup.take() {
326            if let Some(remove) = prev.into_inner() {
327                remove();
328            }
329        }
330        *prev_cleanup = Some(if E::CAPTURE {
331            self.attach_capture(el)
332        } else {
333            self.attach(el)
334        });
335    }
336
337    fn into_cloneable(self) -> Self::Cloneable {
338        On {
339            cb: self.cb.map(|cb| SendWrapper::new(cb.take().into_shared())),
340            #[cfg(feature = "reactive_graph")]
341            owner: self.owner,
342            event: self.event,
343        }
344    }
345
346    fn into_cloneable_owned(self) -> Self::CloneableOwned {
347        On {
348            cb: self.cb.map(|cb| SendWrapper::new(cb.take().into_shared())),
349            #[cfg(feature = "reactive_graph")]
350            owner: self.owner,
351            event: self.event,
352        }
353    }
354
355    fn dry_resolve(&mut self) {
356        // dry_resolve() only runs during SSR, and we should use it to
357        // synchronously remove and drop the SendWrapper value
358        // we don't need this value during SSR and leaving it here could drop it
359        // from a different thread
360        self.cb.take();
361    }
362
363    async fn resolve(self) -> Self::AsyncOutput {
364        self
365    }
366
367    fn keys(&self) -> Vec<NamedAttributeKey> {
368        vec![]
369    }
370}
371
372impl<E, F> NextAttribute for On<E, F>
373where
374    F: EventCallback<E::EventType>,
375    E: EventDescriptor + Send + 'static,
376    E::EventType: 'static,
377
378    E::EventType: From<crate::renderer::types::Event>,
379{
380    next_attr_output_type!(Self, NewAttr);
381
382    fn add_any_attr<NewAttr: Attribute>(
383        self,
384        new_attr: NewAttr,
385    ) -> Self::Output<NewAttr> {
386        next_attr_combine!(self, new_attr)
387    }
388}
389
390impl<E, F> ToTemplate for On<E, F> {
391    #[inline(always)]
392    fn to_template(
393        _buf: &mut String,
394        _class: &mut String,
395        _style: &mut String,
396        _inner_html: &mut String,
397        _position: &mut Position,
398    ) {
399    }
400}
401
402/// A trait for converting types into [web_sys events](web_sys).
403pub trait EventDescriptor: Clone {
404    /// The [`web_sys`] event type, such as [`web_sys::MouseEvent`].
405    type EventType: FromWasmAbi;
406
407    /// Indicates if this event bubbles. For example, `click` bubbles,
408    /// but `focus` does not.
409    ///
410    /// If this is true, then the event will be delegated globally if the `delegation`
411    /// feature is enabled. Otherwise, event listeners will be directly attached to the element.
412    const BUBBLES: bool;
413
414    /// Indicates if this event should be handled during the capture phase.
415    const CAPTURE: bool = false;
416
417    /// The name of the event, such as `click` or `mouseover`.
418    fn name(&self) -> Cow<'static, str>;
419
420    /// The key used for event delegation.
421    fn event_delegation_key(&self) -> Cow<'static, str>;
422
423    /// Return the options for this type. This is only used when you create a [`Custom`] event
424    /// handler.
425    #[inline(always)]
426    fn options(&self) -> Option<&web_sys::AddEventListenerOptions> {
427        None
428    }
429}
430
431/// A wrapper that tells the framework to handle an event during the capture phase.
432#[derive(Debug, Clone, PartialEq, Eq)]
433pub struct Capture<E> {
434    inner: E,
435}
436
437/// Wraps an event to indicate that it should be handled during the capture phase.
438pub fn capture<E>(event: E) -> Capture<E> {
439    Capture { inner: event }
440}
441
442impl<E: EventDescriptor> EventDescriptor for Capture<E> {
443    type EventType = E::EventType;
444
445    const CAPTURE: bool = true;
446    const BUBBLES: bool = E::BUBBLES;
447
448    fn name(&self) -> Cow<'static, str> {
449        self.inner.name()
450    }
451
452    fn event_delegation_key(&self) -> Cow<'static, str> {
453        self.inner.event_delegation_key()
454    }
455}
456
457/// A custom event.
458#[derive(Debug)]
459pub struct Custom<E: FromWasmAbi = web_sys::Event> {
460    name: Cow<'static, str>,
461    options: Option<SendWrapper<web_sys::AddEventListenerOptions>>,
462    _event_type: PhantomData<fn() -> E>,
463}
464
465impl<E: FromWasmAbi> Clone for Custom<E> {
466    fn clone(&self) -> Self {
467        Self {
468            name: self.name.clone(),
469            options: self.options.clone(),
470            _event_type: PhantomData,
471        }
472    }
473}
474
475impl<E: FromWasmAbi> EventDescriptor for Custom<E> {
476    type EventType = E;
477
478    fn name(&self) -> Cow<'static, str> {
479        self.name.clone()
480    }
481
482    fn event_delegation_key(&self) -> Cow<'static, str> {
483        format!("$$${}", self.name).into()
484    }
485
486    const BUBBLES: bool = false;
487
488    #[inline(always)]
489    fn options(&self) -> Option<&web_sys::AddEventListenerOptions> {
490        self.options.as_deref()
491    }
492}
493
494impl<E: FromWasmAbi> Custom<E> {
495    /// Creates a custom event type that can be used within
496    /// [`OnAttribute::on`](crate::prelude::OnAttribute::on), for events
497    /// which are not covered in the [`ev`](crate::html::event) module.
498    pub fn new(name: impl Into<Cow<'static, str>>) -> Self {
499        Self {
500            name: name.into(),
501            options: None,
502            _event_type: PhantomData,
503        }
504    }
505
506    /// Modify the [`AddEventListenerOptions`] used for this event listener.
507    ///
508    /// ```rust
509    /// # use tachys::prelude::*;
510    /// # use tachys::html;
511    /// # use tachys::html::event as ev;
512    /// # fn custom_event() -> impl Render {
513    /// let mut non_passive_wheel = ev::Custom::new("wheel");
514    /// non_passive_wheel.options_mut().set_passive(false);
515    ///
516    /// let canvas =
517    ///     html::element::canvas().on(non_passive_wheel, |e: ev::WheelEvent| {
518    ///         // handle event
519    ///     });
520    /// # canvas
521    /// # }
522    /// ```
523    ///
524    /// [`AddEventListenerOptions`]: web_sys::AddEventListenerOptions
525    pub fn options_mut(&mut self) -> &mut web_sys::AddEventListenerOptions {
526        // It is valid to construct a `SendWrapper` here because
527        // its inner data will only be accessed in the browser's main thread.
528        self.options.get_or_insert_with(|| {
529            SendWrapper::new(web_sys::AddEventListenerOptions::new())
530        })
531    }
532}
533
534macro_rules! generate_event_types {
535  {$(
536    $( #[$does_not_bubble:ident] )?
537    $( $event:ident )+ : $web_event:ident
538  ),* $(,)?} => {
539    ::paste::paste! {
540      $(
541        #[doc = "The `" [< $($event)+ >] "` event, which receives [" $web_event "](web_sys::" $web_event ") as its argument."]
542        #[derive(Copy, Clone, Debug)]
543        #[allow(non_camel_case_types)]
544        pub struct [<$( $event )+ >];
545
546        impl EventDescriptor for [< $($event)+ >] {
547          type EventType = web_sys::$web_event;
548
549          #[inline(always)]
550          fn name(&self) -> Cow<'static, str> {
551            stringify!([< $($event)+ >]).into()
552          }
553
554          #[inline(always)]
555          fn event_delegation_key(&self) -> Cow<'static, str> {
556            concat!("$$$", stringify!([< $($event)+ >])).into()
557          }
558
559          const BUBBLES: bool = true $(&& generate_event_types!($does_not_bubble))?;
560        }
561      )*
562    }
563  };
564
565  (does_not_bubble) => { false }
566}
567
568generate_event_types! {
569  // =========================================================
570  // WindowEventHandlersEventMap
571  // =========================================================
572  #[does_not_bubble]
573  after print: Event,
574  #[does_not_bubble]
575  before print: Event,
576  #[does_not_bubble]
577  before unload: BeforeUnloadEvent,
578  #[does_not_bubble]
579  gamepad connected: GamepadEvent,
580  #[does_not_bubble]
581  gamepad disconnected: GamepadEvent,
582  hash change: HashChangeEvent,
583  #[does_not_bubble]
584  language change: Event,
585  #[does_not_bubble]
586  message: MessageEvent,
587  #[does_not_bubble]
588  message error: MessageEvent,
589  #[does_not_bubble]
590  offline: Event,
591  #[does_not_bubble]
592  online: Event,
593  #[does_not_bubble]
594  page hide: PageTransitionEvent,
595  #[does_not_bubble]
596  page show: PageTransitionEvent,
597  pop state: PopStateEvent,
598  rejection handled: PromiseRejectionEvent,
599  #[does_not_bubble]
600  storage: StorageEvent,
601  #[does_not_bubble]
602  unhandled rejection: PromiseRejectionEvent,
603  #[does_not_bubble]
604  unload: Event,
605
606  // =========================================================
607  // GlobalEventHandlersEventMap
608  // =========================================================
609  #[does_not_bubble]
610  abort: UiEvent,
611  animation cancel: AnimationEvent,
612  animation end: AnimationEvent,
613  animation iteration: AnimationEvent,
614  animation start: AnimationEvent,
615  aux click: MouseEvent,
616  before input: InputEvent,
617  before toggle: Event, // web_sys does not include `ToggleEvent`
618  #[does_not_bubble]
619  blur: FocusEvent,
620  #[does_not_bubble]
621  can play: Event,
622  #[does_not_bubble]
623  can play through: Event,
624  change: Event,
625  click: MouseEvent,
626  #[does_not_bubble]
627  close: Event,
628  composition end: CompositionEvent,
629  composition start: CompositionEvent,
630  composition update: CompositionEvent,
631  context menu: MouseEvent,
632  #[does_not_bubble]
633  cue change: Event,
634  dbl click: MouseEvent,
635  drag: DragEvent,
636  drag end: DragEvent,
637  drag enter: DragEvent,
638  drag leave: DragEvent,
639  drag over: DragEvent,
640  drag start: DragEvent,
641  drop: DragEvent,
642  #[does_not_bubble]
643  duration change: Event,
644  #[does_not_bubble]
645  emptied: Event,
646  #[does_not_bubble]
647  ended: Event,
648  #[does_not_bubble]
649  error: ErrorEvent,
650  #[does_not_bubble]
651  focus: FocusEvent,
652  #[does_not_bubble]
653  focus in: FocusEvent,
654  #[does_not_bubble]
655  focus out: FocusEvent,
656  form data: Event, // web_sys does not include `FormDataEvent`
657  #[does_not_bubble]
658  got pointer capture: PointerEvent,
659  input: Event,
660  #[does_not_bubble]
661  invalid: Event,
662  key down: KeyboardEvent,
663  key press: KeyboardEvent,
664  key up: KeyboardEvent,
665  #[does_not_bubble]
666  load: Event,
667  #[does_not_bubble]
668  loaded data: Event,
669  #[does_not_bubble]
670  loaded metadata: Event,
671  #[does_not_bubble]
672  load start: Event,
673  lost pointer capture: PointerEvent,
674  mouse down: MouseEvent,
675  #[does_not_bubble]
676  mouse enter: MouseEvent,
677  #[does_not_bubble]
678  mouse leave: MouseEvent,
679  mouse move: MouseEvent,
680  mouse out: MouseEvent,
681  mouse over: MouseEvent,
682  mouse up: MouseEvent,
683  #[does_not_bubble]
684  pause: Event,
685  #[does_not_bubble]
686  play: Event,
687  #[does_not_bubble]
688  playing: Event,
689  pointer cancel: PointerEvent,
690  pointer down: PointerEvent,
691  #[does_not_bubble]
692  pointer enter: PointerEvent,
693  #[does_not_bubble]
694  pointer leave: PointerEvent,
695  pointer move: PointerEvent,
696  pointer out: PointerEvent,
697  pointer over: PointerEvent,
698  pointer up: PointerEvent,
699  #[does_not_bubble]
700  progress: ProgressEvent,
701  #[does_not_bubble]
702  rate change: Event,
703  reset: Event,
704  #[does_not_bubble]
705  resize: UiEvent,
706  #[does_not_bubble]
707  scroll: Event,
708  #[does_not_bubble]
709  scroll end: Event,
710  security policy violation: SecurityPolicyViolationEvent,
711  #[does_not_bubble]
712  seeked: Event,
713  #[does_not_bubble]
714  seeking: Event,
715  select: Event,
716  #[does_not_bubble]
717  selection change: Event,
718  select start: Event,
719  slot change: Event,
720  #[does_not_bubble]
721  stalled: Event,
722  submit: SubmitEvent,
723  #[does_not_bubble]
724  suspend: Event,
725  #[does_not_bubble]
726  time update: Event,
727  #[does_not_bubble]
728  toggle: Event,
729  touch cancel: TouchEvent,
730  touch end: TouchEvent,
731  touch move: TouchEvent,
732  touch start: TouchEvent,
733  transition cancel: TransitionEvent,
734  transition end: TransitionEvent,
735  transition run: TransitionEvent,
736  transition start: TransitionEvent,
737  #[does_not_bubble]
738  volume change: Event,
739  #[does_not_bubble]
740  waiting: Event,
741  webkit animation end: Event,
742  webkit animation iteration: Event,
743  webkit animation start: Event,
744  webkit transition end: Event,
745  wheel: WheelEvent,
746
747  // =========================================================
748  // WindowEventMap
749  // =========================================================
750  D O M Content Loaded: Event, // Hack for correct casing
751  #[does_not_bubble]
752  device motion: DeviceMotionEvent,
753  #[does_not_bubble]
754  device orientation: DeviceOrientationEvent,
755  #[does_not_bubble]
756  orientation change: Event,
757
758  // =========================================================
759  // DocumentAndElementEventHandlersEventMap
760  // =========================================================
761  copy: ClipboardEvent,
762  cut: ClipboardEvent,
763  paste: ClipboardEvent,
764
765  // =========================================================
766  // DocumentEventMap
767  // =========================================================
768  fullscreen change: Event,
769  fullscreen error: Event,
770  pointer lock change: Event,
771  pointer lock error: Event,
772  #[does_not_bubble]
773  ready state change: Event,
774  visibility change: Event,
775}
776
777// Export `web_sys` event types
778use super::{
779    attribute::{
780        maybe_next_attr_erasure_macros::next_attr_output_type, NextAttribute,
781    },
782    element::HasElementType,
783};
784#[doc(no_inline)]
785pub use web_sys::{
786    AnimationEvent, BeforeUnloadEvent, ClipboardEvent, CompositionEvent,
787    CustomEvent, DeviceMotionEvent, DeviceOrientationEvent, DragEvent,
788    ErrorEvent, Event, FocusEvent, GamepadEvent, HashChangeEvent, InputEvent,
789    KeyboardEvent, MessageEvent, MouseEvent, PageTransitionEvent, PointerEvent,
790    PopStateEvent, ProgressEvent, PromiseRejectionEvent,
791    SecurityPolicyViolationEvent, StorageEvent, SubmitEvent, TouchEvent,
792    TransitionEvent, UiEvent, WheelEvent,
793};