Skip to main content

kozan_core/events/
context.rs

1//! Event dispatch context — mutable state during dispatch.
2//!
3//! Chrome puts phase, propagation flags, `current_target` all on the Event object.
4//! In Kozan, this state lives in `EventContext` (passed to handlers alongside the event).
5//! This keeps Event structs immutable and simple.
6
7use core::cell::Cell;
8
9/// The current phase of event dispatch.
10///
11/// Matches Chrome's `Event::PhaseType` and DOM spec values.
12#[derive(Copy, Clone, Debug, PartialEq, Eq)]
13#[repr(u8)]
14pub enum Phase {
15    None = 0,
16    Capturing = 1,
17    AtTarget = 2,
18    Bubbling = 3,
19}
20
21/// Mutable dispatch state passed to event handlers.
22///
23/// Uses `Cell` for interior mutability — handlers can call `stop_propagation()`
24/// etc. through a shared reference. Safe because dispatch is single-threaded.
25///
26/// # Chrome equivalence
27///
28/// | Chrome field                       | Kozan field                |
29/// |------------------------------------|----------------------------|
30/// | `event_phase_`                     | `phase`                    |
31/// | `propagation_stopped_`             | `propagation_stopped`      |
32/// | `immediate_propagation_stopped_`   | `immediate_stopped`        |
33/// | `default_prevented_`               | `default_prevented`        |
34/// | `current_target_`                  | `current_target`           |
35/// | `target_`                          | `target`                   |
36pub struct EventContext {
37    phase: Cell<Phase>,
38    propagation_stopped: Cell<bool>,
39    immediate_stopped: Cell<bool>,
40    default_prevented: Cell<bool>,
41    target: u32,
42    current_target: Cell<u32>,
43}
44
45impl EventContext {
46    pub(crate) fn new(target: u32) -> Self {
47        Self {
48            phase: Cell::new(Phase::None),
49            propagation_stopped: Cell::new(false),
50            immediate_stopped: Cell::new(false),
51            default_prevented: Cell::new(false),
52            target,
53            current_target: Cell::new(target),
54        }
55    }
56
57    /// The target node (where the event was originally dispatched).
58    #[inline]
59    pub fn target(&self) -> u32 {
60        self.target
61    }
62
63    /// The node currently being processed in the dispatch path.
64    #[inline]
65    pub fn current_target(&self) -> u32 {
66        self.current_target.get()
67    }
68
69    /// The current dispatch phase.
70    #[inline]
71    pub fn phase(&self) -> Phase {
72        self.phase.get()
73    }
74
75    /// Stop propagation to subsequent nodes.
76    /// Remaining listeners on the current node still fire.
77    /// (Chrome: `stopPropagation()`)
78    pub fn stop_propagation(&self) {
79        self.propagation_stopped.set(true);
80    }
81
82    /// Stop propagation AND prevent remaining listeners on the current node.
83    /// (Chrome: `stopImmediatePropagation()`)
84    pub fn stop_immediate_propagation(&self) {
85        self.propagation_stopped.set(true);
86        self.immediate_stopped.set(true);
87    }
88
89    /// Prevent the default action for this event.
90    /// Does NOT stop propagation — all listeners still fire.
91    /// (Chrome: `preventDefault()`)
92    pub fn prevent_default(&self) {
93        self.default_prevented.set(true);
94    }
95
96    /// Was `stop_propagation()` or `stop_immediate_propagation()` called?
97    #[inline]
98    pub fn is_propagation_stopped(&self) -> bool {
99        self.propagation_stopped.get()
100    }
101
102    /// Was `stop_immediate_propagation()` called?
103    #[inline]
104    pub fn is_immediate_stopped(&self) -> bool {
105        self.immediate_stopped.get()
106    }
107
108    /// Was `prevent_default()` called?
109    #[inline]
110    pub fn is_default_prevented(&self) -> bool {
111        self.default_prevented.get()
112    }
113
114    // Internal — used by dispatcher.
115    pub(crate) fn set_phase(&self, phase: Phase) {
116        self.phase.set(phase);
117    }
118
119    pub(crate) fn set_current_target(&self, index: u32) {
120        self.current_target.set(index);
121    }
122}