Skip to main content

kozan_core/events/
focus_event.rs

1//! Focus DOM events — Chrome: `blink/core/dom/events/focus_event.h`.
2//!
3//! DOM-level focus events dispatched through the tree.
4//! `Focus`/`Blur` do NOT bubble. `FocusIn`/`FocusOut` DO bubble.
5//! This mirrors the W3C spec and Chrome's behavior.
6
7use crate::id::RawId;
8use kozan_macros::Event;
9
10/// DOM `focus` event — fired when an element receives focus.
11/// Does NOT bubble (use `FocusInEvent` for bubbling).
12///
13/// Chrome: `FocusEvent` with type `"focus"`.
14#[derive(Debug, Clone, Event)]
15#[event()]
16#[non_exhaustive]
17pub struct FocusEvent {
18    /// The node that previously had focus (if any).
19    /// Uses `RawId` (not raw `u32`) — safe, generation-checked reference.
20    /// Chrome: `FocusEvent.relatedTarget` returns an `EventTarget`.
21    pub related_target: Option<RawId>,
22}
23
24/// DOM `blur` event — fired when an element loses focus.
25/// Does NOT bubble (use `FocusOutEvent` for bubbling).
26///
27/// Chrome: `FocusEvent` with type `"blur"`.
28#[derive(Debug, Clone, Event)]
29#[event()]
30#[non_exhaustive]
31pub struct BlurEvent {
32    /// The node that is receiving focus (if any).
33    pub related_target: Option<RawId>,
34}
35
36/// DOM `focusin` event — fired when an element is about to receive focus.
37/// Bubbles (unlike `FocusEvent`).
38///
39/// Chrome: `FocusEvent` with type `"focusin"`.
40#[derive(Debug, Clone, Event)]
41#[event(bubbles)]
42#[non_exhaustive]
43pub struct FocusInEvent {
44    /// The node that previously had focus (if any).
45    pub related_target: Option<RawId>,
46}
47
48/// DOM `focusout` event — fired when an element is about to lose focus.
49/// Bubbles (unlike `BlurEvent`).
50///
51/// Chrome: `FocusEvent` with type `"focusout"`.
52#[derive(Debug, Clone, Event)]
53#[event(bubbles)]
54#[non_exhaustive]
55pub struct FocusOutEvent {
56    /// The node that is receiving focus (if any).
57    pub related_target: Option<RawId>,
58}
59
60#[cfg(test)]
61mod tests {
62    use super::*;
63    use crate::events::{Bubbles, Cancelable, Event};
64    use crate::id::RawId;
65
66    #[test]
67    fn focus_does_not_bubble() {
68        let evt = FocusEvent {
69            related_target: None,
70        };
71        assert_eq!(evt.bubbles(), Bubbles::No);
72        assert_eq!(evt.cancelable(), Cancelable::No);
73    }
74
75    #[test]
76    fn blur_does_not_bubble() {
77        let evt = BlurEvent {
78            related_target: Some(RawId::new(42, 0)),
79        };
80        assert_eq!(evt.bubbles(), Bubbles::No);
81        assert_eq!(evt.cancelable(), Cancelable::No);
82    }
83
84    #[test]
85    fn focusin_bubbles() {
86        let evt = FocusInEvent {
87            related_target: None,
88        };
89        assert_eq!(evt.bubbles(), Bubbles::Yes);
90        assert_eq!(evt.cancelable(), Cancelable::No);
91    }
92
93    #[test]
94    fn focusout_bubbles() {
95        let evt = FocusOutEvent {
96            related_target: Some(RawId::new(7, 1)),
97        };
98        assert_eq!(evt.bubbles(), Bubbles::Yes);
99        assert_eq!(evt.cancelable(), Cancelable::No);
100    }
101}