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: (!cfg!(feature = "ssr")).then(|| 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
357 async fn resolve(self) -> Self::AsyncOutput {
358 self
359 }
360
361 fn keys(&self) -> Vec<NamedAttributeKey> {
362 vec![]
363 }
364}
365
366impl<E, F> NextAttribute for On<E, F>
367where
368 F: EventCallback<E::EventType>,
369 E: EventDescriptor + Send + 'static,
370 E::EventType: 'static,
371
372 E::EventType: From<crate::renderer::types::Event>,
373{
374 next_attr_output_type!(Self, NewAttr);
375
376 fn add_any_attr<NewAttr: Attribute>(
377 self,
378 new_attr: NewAttr,
379 ) -> Self::Output<NewAttr> {
380 next_attr_combine!(self, new_attr)
381 }
382}
383
384impl<E, F> ToTemplate for On<E, F> {
385 #[inline(always)]
386 fn to_template(
387 _buf: &mut String,
388 _class: &mut String,
389 _style: &mut String,
390 _inner_html: &mut String,
391 _position: &mut Position,
392 ) {
393 }
394}
395
396pub trait EventDescriptor: Clone {
398 type EventType: FromWasmAbi;
400
401 const BUBBLES: bool;
407
408 const CAPTURE: bool = false;
410
411 fn name(&self) -> Cow<'static, str>;
413
414 fn event_delegation_key(&self) -> Cow<'static, str>;
416
417 #[inline(always)]
420 fn options(&self) -> Option<&web_sys::AddEventListenerOptions> {
421 None
422 }
423}
424
425#[derive(Debug, Clone, PartialEq, Eq)]
427pub struct Capture<E> {
428 inner: E,
429}
430
431pub fn capture<E>(event: E) -> Capture<E> {
433 Capture { inner: event }
434}
435
436impl<E: EventDescriptor> EventDescriptor for Capture<E> {
437 type EventType = E::EventType;
438
439 const CAPTURE: bool = true;
440 const BUBBLES: bool = E::BUBBLES;
441
442 fn name(&self) -> Cow<'static, str> {
443 self.inner.name()
444 }
445
446 fn event_delegation_key(&self) -> Cow<'static, str> {
447 self.inner.event_delegation_key()
448 }
449}
450
451#[derive(Debug)]
453pub struct Custom<E: FromWasmAbi = web_sys::Event> {
454 name: Cow<'static, str>,
455 options: Option<SendWrapper<web_sys::AddEventListenerOptions>>,
456 _event_type: PhantomData<fn() -> E>,
457}
458
459impl<E: FromWasmAbi> Clone for Custom<E> {
460 fn clone(&self) -> Self {
461 Self {
462 name: self.name.clone(),
463 options: self.options.clone(),
464 _event_type: PhantomData,
465 }
466 }
467}
468
469impl<E: FromWasmAbi> EventDescriptor for Custom<E> {
470 type EventType = E;
471
472 fn name(&self) -> Cow<'static, str> {
473 self.name.clone()
474 }
475
476 fn event_delegation_key(&self) -> Cow<'static, str> {
477 format!("$$${}", self.name).into()
478 }
479
480 const BUBBLES: bool = false;
481
482 #[inline(always)]
483 fn options(&self) -> Option<&web_sys::AddEventListenerOptions> {
484 self.options.as_deref()
485 }
486}
487
488impl<E: FromWasmAbi> Custom<E> {
489 pub fn new(name: impl Into<Cow<'static, str>>) -> Self {
493 Self {
494 name: name.into(),
495 options: None,
496 _event_type: PhantomData,
497 }
498 }
499
500 pub fn options_mut(&mut self) -> &mut web_sys::AddEventListenerOptions {
520 self.options.get_or_insert_with(|| {
523 SendWrapper::new(web_sys::AddEventListenerOptions::new())
524 })
525 }
526}
527
528macro_rules! generate_event_types {
529 {$(
530 $( #[$does_not_bubble:ident] )?
531 $( $event:ident )+ : $web_event:ident
532 ),* $(,)?} => {
533 ::paste::paste! {
534 $(
535 #[doc = "The `" [< $($event)+ >] "` event, which receives [" $web_event "](web_sys::" $web_event ") as its argument."]
536 #[derive(Copy, Clone, Debug)]
537 #[allow(non_camel_case_types)]
538 pub struct [<$( $event )+ >];
539
540 impl EventDescriptor for [< $($event)+ >] {
541 type EventType = web_sys::$web_event;
542
543 #[inline(always)]
544 fn name(&self) -> Cow<'static, str> {
545 stringify!([< $($event)+ >]).into()
546 }
547
548 #[inline(always)]
549 fn event_delegation_key(&self) -> Cow<'static, str> {
550 concat!("$$$", stringify!([< $($event)+ >])).into()
551 }
552
553 const BUBBLES: bool = true $(&& generate_event_types!($does_not_bubble))?;
554 }
555 )*
556 }
557 };
558
559 (does_not_bubble) => { false }
560}
561
562generate_event_types! {
563 #[does_not_bubble]
567 after print: Event,
568 #[does_not_bubble]
569 before print: Event,
570 #[does_not_bubble]
571 before unload: BeforeUnloadEvent,
572 #[does_not_bubble]
573 gamepad connected: GamepadEvent,
574 #[does_not_bubble]
575 gamepad disconnected: GamepadEvent,
576 hash change: HashChangeEvent,
577 #[does_not_bubble]
578 language change: Event,
579 #[does_not_bubble]
580 message: MessageEvent,
581 #[does_not_bubble]
582 message error: MessageEvent,
583 #[does_not_bubble]
584 offline: Event,
585 #[does_not_bubble]
586 online: Event,
587 #[does_not_bubble]
588 page hide: PageTransitionEvent,
589 #[does_not_bubble]
590 page show: PageTransitionEvent,
591 pop state: PopStateEvent,
592 rejection handled: PromiseRejectionEvent,
593 #[does_not_bubble]
594 storage: StorageEvent,
595 #[does_not_bubble]
596 unhandled rejection: PromiseRejectionEvent,
597 #[does_not_bubble]
598 unload: Event,
599
600 #[does_not_bubble]
604 abort: UiEvent,
605 animation cancel: AnimationEvent,
606 animation end: AnimationEvent,
607 animation iteration: AnimationEvent,
608 animation start: AnimationEvent,
609 aux click: MouseEvent,
610 before input: InputEvent,
611 before toggle: Event, #[does_not_bubble]
613 blur: FocusEvent,
614 #[does_not_bubble]
615 can play: Event,
616 #[does_not_bubble]
617 can play through: Event,
618 change: Event,
619 click: MouseEvent,
620 #[does_not_bubble]
621 close: Event,
622 composition end: CompositionEvent,
623 composition start: CompositionEvent,
624 composition update: CompositionEvent,
625 context menu: MouseEvent,
626 #[does_not_bubble]
627 cue change: Event,
628 dbl click: MouseEvent,
629 drag: DragEvent,
630 drag end: DragEvent,
631 drag enter: DragEvent,
632 drag leave: DragEvent,
633 drag over: DragEvent,
634 drag start: DragEvent,
635 drop: DragEvent,
636 #[does_not_bubble]
637 duration change: Event,
638 #[does_not_bubble]
639 emptied: Event,
640 #[does_not_bubble]
641 ended: Event,
642 #[does_not_bubble]
643 error: ErrorEvent,
644 #[does_not_bubble]
645 focus: FocusEvent,
646 #[does_not_bubble]
647 focus in: FocusEvent,
648 #[does_not_bubble]
649 focus out: FocusEvent,
650 form data: Event, #[does_not_bubble]
652 got pointer capture: PointerEvent,
653 input: Event,
654 #[does_not_bubble]
655 invalid: Event,
656 key down: KeyboardEvent,
657 key press: KeyboardEvent,
658 key up: KeyboardEvent,
659 #[does_not_bubble]
660 load: Event,
661 #[does_not_bubble]
662 loaded data: Event,
663 #[does_not_bubble]
664 loaded metadata: Event,
665 #[does_not_bubble]
666 load start: Event,
667 lost pointer capture: PointerEvent,
668 mouse down: MouseEvent,
669 #[does_not_bubble]
670 mouse enter: MouseEvent,
671 #[does_not_bubble]
672 mouse leave: MouseEvent,
673 mouse move: MouseEvent,
674 mouse out: MouseEvent,
675 mouse over: MouseEvent,
676 mouse up: MouseEvent,
677 #[does_not_bubble]
678 pause: Event,
679 #[does_not_bubble]
680 play: Event,
681 #[does_not_bubble]
682 playing: Event,
683 pointer cancel: PointerEvent,
684 pointer down: PointerEvent,
685 #[does_not_bubble]
686 pointer enter: PointerEvent,
687 #[does_not_bubble]
688 pointer leave: PointerEvent,
689 pointer move: PointerEvent,
690 pointer out: PointerEvent,
691 pointer over: PointerEvent,
692 pointer up: PointerEvent,
693 #[does_not_bubble]
694 progress: ProgressEvent,
695 #[does_not_bubble]
696 rate change: Event,
697 reset: Event,
698 #[does_not_bubble]
699 resize: UiEvent,
700 #[does_not_bubble]
701 scroll: Event,
702 #[does_not_bubble]
703 scroll end: Event,
704 security policy violation: SecurityPolicyViolationEvent,
705 #[does_not_bubble]
706 seeked: Event,
707 #[does_not_bubble]
708 seeking: Event,
709 select: Event,
710 #[does_not_bubble]
711 selection change: Event,
712 select start: Event,
713 slot change: Event,
714 #[does_not_bubble]
715 stalled: Event,
716 submit: SubmitEvent,
717 #[does_not_bubble]
718 suspend: Event,
719 #[does_not_bubble]
720 time update: Event,
721 #[does_not_bubble]
722 toggle: Event,
723 touch cancel: TouchEvent,
724 touch end: TouchEvent,
725 touch move: TouchEvent,
726 touch start: TouchEvent,
727 transition cancel: TransitionEvent,
728 transition end: TransitionEvent,
729 transition run: TransitionEvent,
730 transition start: TransitionEvent,
731 #[does_not_bubble]
732 volume change: Event,
733 #[does_not_bubble]
734 waiting: Event,
735 webkit animation end: Event,
736 webkit animation iteration: Event,
737 webkit animation start: Event,
738 webkit transition end: Event,
739 wheel: WheelEvent,
740
741 D O M Content Loaded: Event, #[does_not_bubble]
746 device motion: DeviceMotionEvent,
747 #[does_not_bubble]
748 device orientation: DeviceOrientationEvent,
749 #[does_not_bubble]
750 orientation change: Event,
751
752 copy: ClipboardEvent,
756 cut: ClipboardEvent,
757 paste: ClipboardEvent,
758
759 fullscreen change: Event,
763 fullscreen error: Event,
764 pointer lock change: Event,
765 pointer lock error: Event,
766 #[does_not_bubble]
767 ready state change: Event,
768 visibility change: Event,
769}
770
771use super::{
773 attribute::{
774 maybe_next_attr_erasure_macros::next_attr_output_type, NextAttribute,
775 },
776 element::HasElementType,
777};
778#[doc(no_inline)]
779pub use web_sys::{
780 AnimationEvent, BeforeUnloadEvent, ClipboardEvent, CompositionEvent,
781 CustomEvent, DeviceMotionEvent, DeviceOrientationEvent, DragEvent,
782 ErrorEvent, Event, FocusEvent, GamepadEvent, HashChangeEvent, InputEvent,
783 KeyboardEvent, MessageEvent, MouseEvent, PageTransitionEvent, PointerEvent,
784 PopStateEvent, ProgressEvent, PromiseRejectionEvent,
785 SecurityPolicyViolationEvent, StorageEvent, SubmitEvent, TouchEvent,
786 TransitionEvent, UiEvent, WheelEvent,
787};