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
20pub type SharedEventCallback<E> = Rc<RefCell<dyn FnMut(E)>>;
22
23pub trait EventCallback<E>: 'static {
25 fn invoke(&mut self, event: E);
27
28 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
56pub struct Targeted<E, T> {
58 event: E,
59 el_ty: PhantomData<T>,
60}
61
62impl<E, T> Targeted<E, T> {
63 pub fn into_inner(self) -> E {
65 self.event
66 }
67
68 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
104pub 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#[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
138pub 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 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 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 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 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 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
402pub trait EventDescriptor: Clone {
404 type EventType: FromWasmAbi;
406
407 const BUBBLES: bool;
413
414 const CAPTURE: bool = false;
416
417 fn name(&self) -> Cow<'static, str>;
419
420 fn event_delegation_key(&self) -> Cow<'static, str>;
422
423 #[inline(always)]
426 fn options(&self) -> Option<&web_sys::AddEventListenerOptions> {
427 None
428 }
429}
430
431#[derive(Debug, Clone, PartialEq, Eq)]
433pub struct Capture<E> {
434 inner: E,
435}
436
437pub 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#[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 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 pub fn options_mut(&mut self) -> &mut web_sys::AddEventListenerOptions {
526 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 #[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 #[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, #[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, #[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 D O M Content Loaded: Event, #[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 copy: ClipboardEvent,
762 cut: ClipboardEvent,
763 paste: ClipboardEvent,
764
765 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
777use 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};