kozan_core/events/event_target.rs
1//! `EventTarget` — base trait for anything that can receive events.
2//!
3//! Like Chrome's `EventTarget` (`core/dom/events/event_target.h`).
4//! Both DOM nodes and `Window` implement this.
5//!
6//! All methods have default implementations.
7//! Derives generate `HasHandle` — `EventTarget` is an empty impl.
8
9use super::dispatcher::EventStoreAccess;
10use super::{Event, EventContext, ListenerId, ListenerOptions};
11use crate::dom::traits::HasHandle;
12
13/// Base trait for anything that can receive events.
14///
15/// Like Chrome's `EventTarget` class. Both DOM nodes and `Window` implement this.
16///
17/// # Methods
18///
19/// | Method | Description |
20/// |---------------------|--------------------------------------------------|
21/// | [`on()`](Self::on) | Add a typed event listener |
22/// | [`on_capture()`](Self::on_capture) | Add a capture-phase listener |
23/// | [`on_once()`](Self::on_once) | Add a one-shot listener (auto-removed) |
24/// | [`on_with_options()`](Self::on_with_options)| Add with explicit options |
25/// | [`off()`](Self::off) | Remove a listener by ID |
26/// | [`dispatch_event()`](Self::dispatch_event)| Dispatch an event (capture → target → bubble) |
27///
28/// # Example
29///
30/// ```ignore
31/// // Add a click listener to a button.
32/// btn.on::<ClickEvent>(|event, ctx| {
33/// println!("Button clicked!");
34/// });
35///
36/// // Dispatch an event.
37/// btn.dispatch_event(&ClickEvent);
38/// ```
39pub trait EventTarget: HasHandle {
40 /// Add a typed event listener. Returns an ID for removal.
41 ///
42 /// The callback receives `&E` (the concrete event) and `&EventContext`
43 /// (dispatch state: phase, target, propagation control).
44 fn on<E: Event>(&self, callback: impl FnMut(&E, &EventContext) + 'static) -> ListenerId {
45 self.on_with_options::<E>(callback, ListenerOptions::default())
46 }
47
48 /// Add a listener with explicit options.
49 ///
50 /// Options: `capture` (fire during capture phase), `passive` (no preventDefault),
51 /// `once` (auto-remove after first call).
52 fn on_with_options<E: Event>(
53 &self,
54 mut callback: impl FnMut(&E, &EventContext) + 'static,
55 options: ListenerOptions,
56 ) -> ListenerId {
57 let h = self.handle();
58 let erased: super::listener::EventCallback =
59 Box::new(move |event: &dyn Event, ctx: &EventContext| {
60 if let Some(typed) = event.as_any().downcast_ref::<E>() {
61 callback(typed, ctx);
62 }
63 });
64 h.cell.write(|doc| {
65 let map = doc.ensure_event_listeners(h.id.index());
66 map.add::<E>(erased, options)
67 })
68 }
69
70 /// Add a capture-phase listener.
71 ///
72 /// Fires during the capture phase (root → target) instead of bubble phase.
73 fn on_capture<E: Event>(
74 &self,
75 callback: impl FnMut(&E, &EventContext) + 'static,
76 ) -> ListenerId {
77 self.on_with_options::<E>(callback, ListenerOptions::capture())
78 }
79
80 /// Add a one-shot listener that auto-removes after first call.
81 fn on_once<E: Event>(&self, callback: impl FnMut(&E, &EventContext) + 'static) -> ListenerId {
82 self.on_with_options::<E>(callback, ListenerOptions::once())
83 }
84
85 /// Remove a listener by its ID.
86 fn off(&self, id: ListenerId) {
87 let h = self.handle();
88 h.cell.write(|doc| {
89 if let Some(map) = doc.event_listeners_mut(h.id.index()) {
90 map.remove(id);
91 }
92 });
93 }
94
95 /// Dispatch an event to this target.
96 ///
97 /// Runs the full capture → target → bubble pipeline.
98 /// Returns `true` if the default action was NOT prevented.
99 fn dispatch_event(&self, event: &dyn Event) -> bool {
100 let h = self.handle();
101 if !h.is_alive() {
102 return false;
103 }
104 let mut store = EventStoreAccess::new(h.cell);
105 super::dispatch(h.cell, h.id, event, &mut store)
106 }
107}