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}