Skip to main content

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}