Skip to main content

azul_core/
events.rs

1//! Event and callback filtering module
2
3#[cfg(not(feature = "std"))]
4use alloc::string::{String, ToString};
5use alloc::{
6    boxed::Box,
7    collections::{btree_map::BTreeMap, btree_set::BTreeSet},
8    vec::Vec,
9};
10
11use azul_css::{
12    props::{
13        basic::{LayoutPoint, LayoutRect, LayoutSize},
14        property::CssProperty,
15    },
16    AzString, LayoutDebugMessage,
17};
18use rust_fontconfig::FcFontCache;
19
20use crate::{
21    callbacks::Update,
22    dom::{DomId, DomNodeId, On},
23    geom::{LogicalPosition, LogicalRect},
24    gl::OptionGlContextPtr,
25    gpu::GpuEventChanges,
26    hit_test::{FullHitTest, HitTestItem, ScrollPosition},
27    id::NodeId,
28    resources::{ImageCache, RendererResources},
29    styled_dom::{ChangedCssProperty, NodeHierarchyItemId},
30    task::Instant,
31    window::RawWindowHandle,
32    FastBTreeSet, FastHashMap,
33};
34
35/// Easing functions für smooth scrolling (für Scroll-Animationen)
36#[derive(Debug, Clone, Copy, PartialEq, Eq)]
37pub enum EasingFunction {
38    Linear,
39    EaseInOut,
40    EaseOut,
41}
42
43pub type RestyleNodes = BTreeMap<NodeId, Vec<ChangedCssProperty>>;
44pub type RelayoutNodes = BTreeMap<NodeId, Vec<ChangedCssProperty>>;
45pub type RelayoutWords = BTreeMap<NodeId, AzString>;
46
47#[derive(Debug, Clone, PartialEq)]
48pub struct FocusChange {
49    pub old: Option<DomNodeId>,
50    pub new: Option<DomNodeId>,
51}
52
53#[derive(Debug, Clone, PartialEq)]
54pub struct CallbackToCall {
55    pub node_id: NodeId,
56    pub hit_test_item: Option<HitTestItem>,
57    pub event_filter: EventFilter,
58}
59
60#[derive(Debug, Copy, Clone, PartialEq, Eq)]
61pub enum ProcessEventResult {
62    DoNothing = 0,
63    ShouldReRenderCurrentWindow = 1,
64    ShouldUpdateDisplayListCurrentWindow = 2,
65    // GPU transforms changed: do another hit-test and recurse
66    // until nothing has changed anymore
67    UpdateHitTesterAndProcessAgain = 3,
68    // Only refresh the display (in case of pure scroll or GPU-only events)
69    ShouldRegenerateDomCurrentWindow = 4,
70    ShouldRegenerateDomAllWindows = 5,
71}
72
73impl ProcessEventResult {
74    pub fn order(&self) -> usize {
75        use self::ProcessEventResult::*;
76        match self {
77            DoNothing => 0,
78            ShouldReRenderCurrentWindow => 1,
79            ShouldUpdateDisplayListCurrentWindow => 2,
80            UpdateHitTesterAndProcessAgain => 3,
81            ShouldRegenerateDomCurrentWindow => 4,
82            ShouldRegenerateDomAllWindows => 5,
83        }
84    }
85}
86
87impl PartialOrd for ProcessEventResult {
88    fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
89        self.order().partial_cmp(&other.order())
90    }
91}
92
93impl Ord for ProcessEventResult {
94    fn cmp(&self, other: &Self) -> core::cmp::Ordering {
95        self.order().cmp(&other.order())
96    }
97}
98
99impl ProcessEventResult {
100    pub fn max_self(self, other: Self) -> Self {
101        self.max(other)
102    }
103}
104
105// Phase 3.5: New Event System Types
106
107/// Tracks the origin of an event for proper handling.
108///
109/// This allows the system to distinguish between user input, programmatic
110/// changes, and synthetic events generated by UI components.
111#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
112#[repr(C)]
113pub enum EventSource {
114    /// Direct user input (mouse, keyboard, touch, gamepad)
115    User,
116    /// API call (programmatic scroll, focus change, etc.)
117    Programmatic,
118    /// Generated from UI interaction (scrollbar drag, synthetic events)
119    Synthetic,
120    /// Generated from lifecycle hooks (mount, unmount, resize)
121    Lifecycle,
122}
123
124/// Event propagation phase (similar to DOM Level 2 Events).
125///
126/// Events can be intercepted at different phases:
127/// - **Capture**: Event travels from root down to target (rarely used)
128/// - **Target**: Event is at the target element
129/// - **Bubble**: Event travels from target back up to root (most common)
130#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
131#[repr(C)]
132pub enum EventPhase {
133    /// Event travels from root down to target
134    Capture,
135    /// Event is at the target element
136    Target,
137    /// Event bubbles from target back up to root
138    Bubble,
139}
140
141impl Default for EventPhase {
142    fn default() -> Self {
143        EventPhase::Bubble
144    }
145}
146
147/// Mouse button identifier for mouse events.
148#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
149#[repr(C)]
150pub enum MouseButton {
151    Left,
152    Middle,
153    Right,
154    Other(u8),
155}
156
157/// Scroll delta mode (how scroll deltas should be interpreted).
158#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
159#[repr(C)]
160pub enum ScrollDeltaMode {
161    /// Delta is in pixels
162    Pixel,
163    /// Delta is in lines (e.g., 3 lines of text)
164    Line,
165    /// Delta is in pages
166    Page,
167}
168
169/// Scroll direction for conditional event filtering.
170#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
171#[repr(C)]
172pub enum ScrollDirection {
173    Up,
174    Down,
175    Left,
176    Right,
177}
178
179// ============================================================================
180// W3C CSSOM View Module - Scroll Into View Types
181// ============================================================================
182
183/// W3C-compliant scroll-into-view options
184///
185/// These options control how an element is scrolled into view, following
186/// the CSSOM View Module specification.
187#[repr(C)]
188#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
189pub struct ScrollIntoViewOptions {
190    /// Vertical alignment: start, center, end, nearest (default: nearest)
191    pub block: ScrollLogicalPosition,
192    /// Horizontal alignment: start, center, end, nearest (default: nearest)
193    /// Note: Named `inline_axis` to avoid conflict with C keyword `inline`
194    pub inline_axis: ScrollLogicalPosition,
195    /// Animation behavior: auto, instant, smooth (default: auto)
196    pub behavior: ScrollIntoViewBehavior,
197}
198
199impl ScrollIntoViewOptions {
200    /// Create options with "nearest" alignment for both axes
201    pub fn nearest() -> Self {
202        Self {
203            block: ScrollLogicalPosition::Nearest,
204            inline_axis: ScrollLogicalPosition::Nearest,
205            behavior: ScrollIntoViewBehavior::Auto,
206        }
207    }
208    
209    /// Create options with "center" alignment for both axes
210    pub fn center() -> Self {
211        Self {
212            block: ScrollLogicalPosition::Center,
213            inline_axis: ScrollLogicalPosition::Center,
214            behavior: ScrollIntoViewBehavior::Auto,
215        }
216    }
217    
218    /// Create options with "start" alignment for both axes
219    pub fn start() -> Self {
220        Self {
221            block: ScrollLogicalPosition::Start,
222            inline_axis: ScrollLogicalPosition::Start,
223            behavior: ScrollIntoViewBehavior::Auto,
224        }
225    }
226    
227    /// Create options to align the end of the target with the end of the viewport
228    pub fn end() -> Self {
229        Self {
230            block: ScrollLogicalPosition::End,
231            inline_axis: ScrollLogicalPosition::End,
232            behavior: ScrollIntoViewBehavior::Auto,
233        }
234    }
235    
236    /// Set instant scroll behavior
237    pub fn with_instant(mut self) -> Self {
238        self.behavior = ScrollIntoViewBehavior::Instant;
239        self
240    }
241    
242    /// Set smooth scroll behavior
243    pub fn with_smooth(mut self) -> Self {
244        self.behavior = ScrollIntoViewBehavior::Smooth;
245        self
246    }
247}
248
249/// Scroll alignment for vertical (block) or horizontal (inline) axis
250///
251/// Determines where the target element should be positioned within
252/// the scroll container's visible area.
253#[repr(C)]
254#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Default)]
255pub enum ScrollLogicalPosition {
256    /// Align target's start edge with container's start edge
257    Start,
258    /// Center target within container
259    Center,
260    /// Align target's end edge with container's end edge
261    End,
262    /// Minimum scroll distance to make target fully visible (default)
263    #[default]
264    Nearest,
265}
266
267/// Scroll animation behavior for scrollIntoView API
268///
269/// This is distinct from the CSS `scroll-behavior` property, as it also
270/// supports the `Instant` option which CSS does not have.
271#[repr(C)]
272#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Default)]
273pub enum ScrollIntoViewBehavior {
274    /// Respect CSS scroll-behavior property (default)
275    #[default]
276    Auto,
277    /// Immediate jump without animation
278    Instant,
279    /// Animated smooth scroll
280    Smooth,
281}
282
283/// Reason why a lifecycle event was triggered.
284#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
285#[repr(C)]
286pub enum LifecycleReason {
287    /// First appearance in DOM
288    InitialMount,
289    /// Removed and re-added to DOM
290    Remount,
291    /// Layout bounds changed
292    Resize,
293    /// Props or state changed
294    Update,
295}
296
297/// Keyboard modifier keys state.
298#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Default)]
299#[repr(C)]
300pub struct KeyModifiers {
301    pub shift: bool,
302    pub ctrl: bool,
303    pub alt: bool,
304    pub meta: bool,
305}
306
307impl KeyModifiers {
308    pub fn new() -> Self {
309        Self::default()
310    }
311
312    pub fn with_shift(mut self) -> Self {
313        self.shift = true;
314        self
315    }
316
317    pub fn with_ctrl(mut self) -> Self {
318        self.ctrl = true;
319        self
320    }
321
322    pub fn with_alt(mut self) -> Self {
323        self.alt = true;
324        self
325    }
326
327    pub fn with_meta(mut self) -> Self {
328        self.meta = true;
329        self
330    }
331
332    pub fn is_empty(&self) -> bool {
333        !self.shift && !self.ctrl && !self.alt && !self.meta
334    }
335}
336
337/// Type-specific event data for mouse events.
338#[derive(Debug, Clone, PartialEq)]
339pub struct MouseEventData {
340    /// Position of the mouse cursor
341    pub position: LogicalPosition,
342    /// Which button was pressed/released
343    pub button: MouseButton,
344    /// Bitmask of currently pressed buttons
345    pub buttons: u8,
346    /// Modifier keys state
347    pub modifiers: KeyModifiers,
348}
349
350/// Type-specific event data for keyboard events.
351#[derive(Debug, Clone, PartialEq)]
352pub struct KeyboardEventData {
353    /// The virtual key code
354    pub key_code: u32,
355    /// The character produced (if any)
356    pub char_code: Option<char>,
357    /// Modifier keys state
358    pub modifiers: KeyModifiers,
359    /// Whether this is a repeat event
360    pub repeat: bool,
361}
362
363/// Type-specific event data for scroll events.
364#[derive(Debug, Clone, PartialEq)]
365pub struct ScrollEventData {
366    /// Scroll delta (dx, dy)
367    pub delta: LogicalPosition,
368    /// How the delta should be interpreted
369    pub delta_mode: ScrollDeltaMode,
370}
371
372/// Type-specific event data for touch events.
373#[derive(Debug, Clone, PartialEq)]
374pub struct TouchEventData {
375    /// Touch identifier
376    pub id: u64,
377    /// Touch position
378    pub position: LogicalPosition,
379    /// Touch force/pressure (0.0 - 1.0)
380    pub force: f32,
381}
382
383/// Type-specific event data for clipboard events.
384#[derive(Debug, Clone, PartialEq)]
385pub struct ClipboardEventData {
386    /// The clipboard content (for paste events)
387    pub content: Option<String>,
388}
389
390/// Type-specific event data for lifecycle events.
391#[derive(Debug, Clone, PartialEq)]
392pub struct LifecycleEventData {
393    /// Why this lifecycle event was triggered
394    pub reason: LifecycleReason,
395    /// Previous layout bounds (for resize events)
396    pub previous_bounds: Option<LogicalRect>,
397    /// Current layout bounds
398    pub current_bounds: LogicalRect,
399}
400
401/// Type-specific event data for window events.
402#[derive(Debug, Clone, PartialEq)]
403pub struct WindowEventData {
404    /// Window size (for resize events)
405    pub size: Option<LogicalRect>,
406    /// Window position (for move events)
407    pub position: Option<LogicalPosition>,
408}
409
410/// Union of all possible event data types.
411#[derive(Debug, Clone, PartialEq)]
412pub enum EventData {
413    /// Mouse event data
414    Mouse(MouseEventData),
415    /// Keyboard event data
416    Keyboard(KeyboardEventData),
417    /// Scroll event data
418    Scroll(ScrollEventData),
419    /// Touch event data
420    Touch(TouchEventData),
421    /// Clipboard event data
422    Clipboard(ClipboardEventData),
423    /// Lifecycle event data
424    Lifecycle(LifecycleEventData),
425    /// Window event data
426    Window(WindowEventData),
427    /// No additional data
428    None,
429}
430
431/// High-level event type classification.
432///
433/// This enum categorizes all possible events that can occur in the UI.
434/// It extends the existing event system with new event types for
435/// lifecycle, clipboard, media, and form handling.
436#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
437#[repr(C)]
438pub enum EventType {
439    // Mouse Events
440    /// Mouse cursor is over the element
441    MouseOver,
442    /// Mouse cursor entered the element
443    MouseEnter,
444    /// Mouse cursor left the element
445    MouseLeave,
446    /// Mouse left the element OR moved to a child element (W3C `mouseout`, bubbles)
447    MouseOut,
448    /// Mouse button pressed
449    MouseDown,
450    /// Mouse button released
451    MouseUp,
452    /// Mouse click (down + up on same element)
453    Click,
454    /// Mouse double-click
455    DoubleClick,
456    /// Right-click / context menu
457    ContextMenu,
458
459    // Keyboard Events
460    /// Key pressed down
461    KeyDown,
462    /// Key released
463    KeyUp,
464    /// Character input (respects locale/keyboard layout)
465    KeyPress,
466
467    // IME Composition Events
468    /// IME composition started
469    CompositionStart,
470    /// IME composition updated (intermediate text changed)
471    CompositionUpdate,
472    /// IME composition ended (final text committed)
473    CompositionEnd,
474
475    // Focus Events
476    /// Element received focus
477    Focus,
478    /// Element lost focus
479    Blur,
480    /// Focus entered element or its children
481    FocusIn,
482    /// Focus left element and its children
483    FocusOut,
484
485    // Input Events
486    /// Input value is being changed (fires on every keystroke)
487    Input,
488    /// Input value has changed (fires after editing complete)
489    Change,
490    /// Form submitted
491    Submit,
492    /// Form reset
493    Reset,
494    /// Form validation failed
495    Invalid,
496
497    // Scroll Events
498    /// Element is being scrolled
499    Scroll,
500    /// Scroll started
501    ScrollStart,
502    /// Scroll ended
503    ScrollEnd,
504
505    // Drag Events
506    /// Drag operation started
507    DragStart,
508    /// Element is being dragged
509    Drag,
510    /// Drag operation ended
511    DragEnd,
512    /// Dragged element entered drop target
513    DragEnter,
514    /// Dragged element is over drop target
515    DragOver,
516    /// Dragged element left drop target
517    DragLeave,
518    /// Element was dropped
519    Drop,
520
521    // Touch Events
522    /// Touch started
523    TouchStart,
524    /// Touch moved
525    TouchMove,
526    /// Touch ended
527    TouchEnd,
528    /// Touch cancelled
529    TouchCancel,
530
531    // Gesture Events
532    /// Long press detected (touch or mouse held down)
533    LongPress,
534    /// Swipe gesture to the left
535    SwipeLeft,
536    /// Swipe gesture to the right
537    SwipeRight,
538    /// Swipe gesture upward
539    SwipeUp,
540    /// Swipe gesture downward
541    SwipeDown,
542    /// Pinch-in gesture (zoom out)
543    PinchIn,
544    /// Pinch-out gesture (zoom in)
545    PinchOut,
546    /// Clockwise rotation gesture
547    RotateClockwise,
548    /// Counter-clockwise rotation gesture
549    RotateCounterClockwise,
550
551    // Clipboard Events
552    /// Content copied to clipboard
553    Copy,
554    /// Content cut to clipboard
555    Cut,
556    /// Content pasted from clipboard
557    Paste,
558
559    // Media Events
560    /// Media playback started
561    Play,
562    /// Media playback paused
563    Pause,
564    /// Media playback ended
565    Ended,
566    /// Media time updated
567    TimeUpdate,
568    /// Media volume changed
569    VolumeChange,
570    /// Media error occurred
571    MediaError,
572
573    // Lifecycle Events
574    /// Component was mounted to the DOM
575    Mount,
576    /// Component will be unmounted from the DOM
577    Unmount,
578    /// Component was updated
579    Update,
580    /// Component layout bounds changed
581    Resize,
582
583    // Window Events
584    /// Window resized
585    WindowResize,
586    /// Window moved
587    WindowMove,
588    /// Window close requested
589    WindowClose,
590    /// Window received focus
591    WindowFocusIn,
592    /// Window lost focus
593    WindowFocusOut,
594    /// System theme changed
595    ThemeChange,
596    /// Window DPI/scale factor changed (moved to different monitor)
597    WindowDpiChanged,
598    /// Window moved to a different monitor
599    WindowMonitorChanged,
600
601    // Application Events
602    /// A monitor/display was connected
603    MonitorConnected,
604    /// A monitor/display was disconnected
605    MonitorDisconnected,
606
607    // File Events
608    /// File is being hovered
609    FileHover,
610    /// File was dropped
611    FileDrop,
612    /// File hover cancelled
613    FileHoverCancel,
614}
615
616/// Unified event wrapper (similar to React's SyntheticEvent).
617///
618/// All events in the system are wrapped in this structure, providing
619/// a consistent interface and enabling event propagation control.
620#[derive(Debug, Clone, PartialEq)]
621pub struct SyntheticEvent {
622    /// The type of event
623    pub event_type: EventType,
624
625    /// Where the event came from
626    pub source: EventSource,
627
628    /// Current propagation phase
629    pub phase: EventPhase,
630
631    /// Target node that the event was dispatched to
632    pub target: DomNodeId,
633
634    /// Current node in the propagation path
635    pub current_target: DomNodeId,
636
637    /// Timestamp when event was created
638    pub timestamp: Instant,
639
640    /// Type-specific event data
641    pub data: EventData,
642
643    /// Whether propagation has been stopped
644    pub stopped: bool,
645
646    /// Whether immediate propagation has been stopped
647    pub stopped_immediate: bool,
648
649    /// Whether default action has been prevented
650    pub prevented_default: bool,
651}
652
653impl SyntheticEvent {
654    /// Create a new synthetic event.
655    ///
656    /// # Parameters
657    /// - `timestamp`: Current time from `(system_callbacks.get_system_time_fn.cb)()`
658    pub fn new(
659        event_type: EventType,
660        source: EventSource,
661        target: DomNodeId,
662        timestamp: Instant,
663        data: EventData,
664    ) -> Self {
665        Self {
666            event_type,
667            source,
668            phase: EventPhase::Target,
669            target,
670            current_target: target,
671            timestamp,
672            data,
673            stopped: false,
674            stopped_immediate: false,
675            prevented_default: false,
676        }
677    }
678
679    /// Stop event propagation after the current phase completes.
680    ///
681    /// This prevents the event from reaching handlers in subsequent phases
682    /// (e.g., stopping during capture prevents bubble phase).
683    pub fn stop_propagation(&mut self) {
684        self.stopped = true;
685    }
686
687    /// Stop event propagation immediately.
688    ///
689    /// This prevents any further handlers from being called, even on the
690    /// current target element.
691    pub fn stop_immediate_propagation(&mut self) {
692        self.stopped_immediate = true;
693        self.stopped = true;
694    }
695
696    /// Prevent the default action associated with this event.
697    ///
698    /// For example, prevents form submission on Enter key, or prevents
699    /// text selection on drag.
700    pub fn prevent_default(&mut self) {
701        self.prevented_default = true;
702    }
703
704    /// Check if propagation was stopped.
705    pub fn is_propagation_stopped(&self) -> bool {
706        self.stopped
707    }
708
709    /// Check if immediate propagation was stopped.
710    pub fn is_immediate_propagation_stopped(&self) -> bool {
711        self.stopped_immediate
712    }
713
714    /// Check if default action was prevented.
715    pub fn is_default_prevented(&self) -> bool {
716        self.prevented_default
717    }
718}
719
720// Phase 3.5, Step 3: Event Propagation System
721
722/// Result of event propagation through DOM tree.
723#[derive(Debug, Clone)]
724pub struct PropagationResult {
725    /// Callbacks that should be invoked, in order
726    pub callbacks_to_invoke: Vec<(NodeId, EventFilter)>,
727    /// Whether default action should be prevented
728    pub default_prevented: bool,
729}
730
731/// Get the path from root to target node in the DOM tree.
732///
733/// This is used for event propagation - we need to know which nodes
734/// are ancestors of the target to implement capture/bubble phases.
735///
736/// Returns nodes in order from root to target (inclusive).
737pub fn get_dom_path(
738    node_hierarchy: &crate::id::NodeHierarchy,
739    target_node: NodeHierarchyItemId,
740) -> Vec<NodeId> {
741    let mut path = Vec::new();
742    let target_node_id = match target_node.into_crate_internal() {
743        Some(id) => id,
744        None => return path,
745    };
746
747    let hier_ref = node_hierarchy.as_ref();
748
749    // Build path from target to root
750    let mut current = Some(target_node_id);
751    while let Some(node_id) = current {
752        path.push(node_id);
753        current = hier_ref.get(node_id).and_then(|node| node.parent);
754    }
755
756    // Reverse to get root → target order
757    path.reverse();
758    path
759}
760
761/// Propagate event through DOM tree with capture and bubble phases.
762///
763/// This implements DOM Level 2 event propagation:
764/// 1. **Capture Phase**: Event travels from root down to target
765/// 2. **Target Phase**: Event is at the target element
766/// 3. **Bubble Phase**: Event travels from target back up to root
767///
768/// The event can be stopped at any point via `stopPropagation()` or
769/// `stopImmediatePropagation()`.
770pub fn propagate_event(
771    event: &mut SyntheticEvent,
772    node_hierarchy: &crate::id::NodeHierarchy,
773    callbacks: &BTreeMap<NodeId, Vec<EventFilter>>,
774) -> PropagationResult {
775    let path = get_dom_path(node_hierarchy, event.target.node);
776    if path.is_empty() {
777        return PropagationResult::default();
778    }
779
780    let ancestors = &path[..path.len().saturating_sub(1)];
781    let target_node_id = *path.last().unwrap();
782
783    let mut result = PropagationResult::default();
784
785    // Phase 1: Capture (root → target)
786    propagate_phase(
787        event,
788        ancestors.iter().copied(),
789        EventPhase::Capture,
790        callbacks,
791        &mut result,
792    );
793
794    // Phase 2: Target
795    if !event.stopped {
796        propagate_target_phase(event, target_node_id, callbacks, &mut result);
797    }
798
799    // Phase 3: Bubble (target → root)
800    if !event.stopped {
801        propagate_phase(
802            event,
803            ancestors.iter().rev().copied(),
804            EventPhase::Bubble,
805            callbacks,
806            &mut result,
807        );
808    }
809
810    result.default_prevented = event.prevented_default;
811    result
812}
813
814/// Process a single propagation phase (Capture or Bubble)
815fn propagate_phase(
816    event: &mut SyntheticEvent,
817    nodes: impl Iterator<Item = NodeId>,
818    phase: EventPhase,
819    callbacks: &BTreeMap<NodeId, Vec<EventFilter>>,
820    result: &mut PropagationResult,
821) {
822    event.phase = phase;
823
824    for node_id in nodes {
825        if event.stopped_immediate || event.stopped {
826            return;
827        }
828
829        event.current_target = DomNodeId {
830            dom: event.target.dom,
831            node: NodeHierarchyItemId::from_crate_internal(Some(node_id)),
832        };
833
834        collect_matching_callbacks(event, node_id, phase, callbacks, result);
835    }
836}
837
838/// Process the target phase
839fn propagate_target_phase(
840    event: &mut SyntheticEvent,
841    target_node_id: NodeId,
842    callbacks: &BTreeMap<NodeId, Vec<EventFilter>>,
843    result: &mut PropagationResult,
844) {
845    event.phase = EventPhase::Target;
846    event.current_target = event.target;
847
848    collect_matching_callbacks(event, target_node_id, EventPhase::Target, callbacks, result);
849}
850
851/// Collect callbacks that match the current phase for a node
852fn collect_matching_callbacks(
853    event: &SyntheticEvent,
854    node_id: NodeId,
855    phase: EventPhase,
856    callbacks: &BTreeMap<NodeId, Vec<EventFilter>>,
857    result: &mut PropagationResult,
858) {
859    let Some(node_callbacks) = callbacks.get(&node_id) else {
860        return;
861    };
862
863    let matching = node_callbacks
864        .iter()
865        .take_while(|_| !event.stopped_immediate)
866        .filter(|filter| matches_filter_phase(filter, event, phase))
867        .map(|filter| (node_id, *filter));
868
869    result.callbacks_to_invoke.extend(matching);
870}
871
872impl Default for PropagationResult {
873    fn default() -> Self {
874        Self {
875            callbacks_to_invoke: Vec::new(),
876            default_prevented: false,
877        }
878    }
879}
880
881// =============================================================================
882// DEFAULT ACTIONS (W3C UI Events / HTML5 Activation Behavior)
883// =============================================================================
884
885/// Default actions are built-in behaviors that occur in response to events.
886///
887/// Per W3C DOM Event specification:
888/// > A default action is an action that the implementation is expected to take
889/// > in response to an event, unless that action is cancelled by the script.
890///
891/// Examples:
892/// - Tab key → move focus to next focusable element
893/// - Enter/Space on button → activate (click) the button
894/// - Escape → clear focus or close modal
895/// - Arrow keys in listbox → move selection
896///
897/// Default actions are processed AFTER all event callbacks have been invoked,
898/// and only if `event.prevent_default()` was NOT called.
899#[derive(Debug, Clone, PartialEq, Eq, Hash)]
900#[repr(C, u8)]
901pub enum DefaultAction {
902    /// Move focus to the next focusable element (Tab key)
903    FocusNext,
904    /// Move focus to the previous focusable element (Shift+Tab)
905    FocusPrevious,
906    /// Move focus to the first focusable element
907    FocusFirst,
908    /// Move focus to the last focusable element
909    FocusLast,
910    /// Clear focus from the currently focused element (Escape key)
911    ClearFocus,
912    /// Activate the focused element (Enter/Space on activatable elements)
913    /// This generates a synthetic Click event on the target
914    ActivateFocusedElement {
915        target: DomNodeId,
916    },
917    /// Submit the form containing the focused element (Enter in form input)
918    SubmitForm {
919        form_node: DomNodeId,
920    },
921    /// Close the current modal/dialog (Escape key when modal is open)
922    CloseModal {
923        modal_node: DomNodeId,
924    },
925    /// Scroll the focused scrollable container
926    ScrollFocusedContainer {
927        direction: ScrollDirection,
928        amount: ScrollAmount,
929    },
930    /// Select all text in the focused text input (Ctrl+A / Cmd+A)
931    SelectAllText,
932    /// No default action for this event
933    None,
934}
935
936/// Amount to scroll for keyboard-based scrolling
937#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
938#[repr(C)]
939pub enum ScrollAmount {
940    /// Scroll by one line (arrow keys)
941    Line,
942    /// Scroll by one page (Page Up/Down)
943    Page,
944    /// Scroll to start/end (Home/End)
945    Document,
946}
947
948/// Result of determining what default action should occur for an event.
949///
950/// This is computed AFTER event dispatch, based on:
951/// 1. The event type
952/// 2. The target element's type/role
953/// 3. Whether `prevent_default()` was called
954#[derive(Debug, Clone)]
955#[repr(C)]
956pub struct DefaultActionResult {
957    /// The default action to perform (if any)
958    pub action: DefaultAction,
959    /// Whether the action was prevented by a callback
960    pub prevented: bool,
961}
962
963impl Default for DefaultActionResult {
964    fn default() -> Self {
965        Self {
966            action: DefaultAction::None,
967            prevented: false,
968        }
969    }
970}
971
972impl DefaultActionResult {
973    /// Create a new result with a specific action
974    pub fn new(action: DefaultAction) -> Self {
975        Self {
976            action,
977            prevented: false,
978        }
979    }
980
981    /// Create a prevented result (callback called prevent_default)
982    pub fn prevented() -> Self {
983        Self {
984            action: DefaultAction::None,
985            prevented: true,
986        }
987    }
988
989    /// Check if there's an action to perform
990    pub fn has_action(&self) -> bool {
991        !self.prevented && !matches!(self.action, DefaultAction::None)
992    }
993}
994
995/// Trait for elements that have activation behavior (can be "clicked" via keyboard).
996///
997/// Per HTML5 spec, elements with activation behavior include:
998/// - `<button>` elements
999/// - `<input type="submit">`, `<input type="button">`, `<input type="reset">`
1000/// - `<a>` elements with href
1001/// - `<area>` elements with href
1002/// - Any element with a click handler (implicit activation)
1003///
1004/// When an element with activation behavior is focused and the user presses
1005/// Enter or Space, a synthetic click event is generated.
1006pub trait ActivationBehavior {
1007    /// Returns true if this element can be activated via keyboard (Enter/Space)
1008    fn has_activation_behavior(&self) -> bool;
1009
1010    /// Returns true if this element is currently activatable
1011    /// (e.g., not disabled, not aria-disabled="true")
1012    fn is_activatable(&self) -> bool;
1013}
1014
1015/// Trait to query if a node is focusable for tab navigation
1016pub trait Focusable {
1017    /// Returns the tabindex value for this element (-1, 0, or positive)
1018    fn get_tabindex(&self) -> Option<i32>;
1019
1020    /// Returns true if this element can receive focus
1021    fn is_focusable(&self) -> bool;
1022
1023    /// Returns true if this element should be in the tab order
1024    fn is_in_tab_order(&self) -> bool {
1025        match self.get_tabindex() {
1026            None => self.is_naturally_focusable(),
1027            Some(i) => i >= 0,
1028        }
1029    }
1030
1031    /// Returns true if this element type is naturally focusable
1032    /// (button, input, select, textarea, a[href])
1033    fn is_naturally_focusable(&self) -> bool;
1034}
1035
1036/// Check if an event filter matches the given event in the current phase.
1037///
1038/// This is used during event propagation to determine which callbacks
1039/// should be invoked at each phase.
1040fn matches_filter_phase(
1041    filter: &EventFilter,
1042    event: &SyntheticEvent,
1043    current_phase: EventPhase,
1044) -> bool {
1045    // For now, we match based on the filter type
1046    // In the future, this will also check EventPhase and EventConditions
1047
1048    match filter {
1049        EventFilter::Hover(hover_filter) => {
1050            matches_hover_filter(hover_filter, event, current_phase)
1051        }
1052        EventFilter::Focus(focus_filter) => {
1053            matches_focus_filter(focus_filter, event, current_phase)
1054        }
1055        EventFilter::Window(window_filter) => {
1056            matches_window_filter(window_filter, event, current_phase)
1057        }
1058        EventFilter::Not(_) => {
1059            // Not filters are inverted - will be implemented in future
1060            false
1061        }
1062        EventFilter::Component(_) | EventFilter::Application(_) => {
1063            // Lifecycle and application events - will be implemented in future
1064            false
1065        }
1066    }
1067}
1068
1069/// Check if a hover filter matches the event.
1070fn matches_hover_filter(
1071    filter: &HoverEventFilter,
1072    event: &SyntheticEvent,
1073    _phase: EventPhase,
1074) -> bool {
1075    use HoverEventFilter::*;
1076
1077    match (filter, &event.event_type) {
1078        (MouseOver, EventType::MouseOver) => true,
1079        (MouseDown, EventType::MouseDown) => true,
1080        (LeftMouseDown, EventType::MouseDown) => {
1081            // Check if it's left button
1082            if let EventData::Mouse(mouse_data) = &event.data {
1083                mouse_data.button == MouseButton::Left
1084            } else {
1085                false
1086            }
1087        }
1088        (RightMouseDown, EventType::MouseDown) => {
1089            if let EventData::Mouse(mouse_data) = &event.data {
1090                mouse_data.button == MouseButton::Right
1091            } else {
1092                false
1093            }
1094        }
1095        (MiddleMouseDown, EventType::MouseDown) => {
1096            if let EventData::Mouse(mouse_data) = &event.data {
1097                mouse_data.button == MouseButton::Middle
1098            } else {
1099                false
1100            }
1101        }
1102        (MouseUp, EventType::MouseUp) => true,
1103        (LeftMouseUp, EventType::MouseUp) => {
1104            if let EventData::Mouse(mouse_data) = &event.data {
1105                mouse_data.button == MouseButton::Left
1106            } else {
1107                false
1108            }
1109        }
1110        (RightMouseUp, EventType::MouseUp) => {
1111            if let EventData::Mouse(mouse_data) = &event.data {
1112                mouse_data.button == MouseButton::Right
1113            } else {
1114                false
1115            }
1116        }
1117        (MiddleMouseUp, EventType::MouseUp) => {
1118            if let EventData::Mouse(mouse_data) = &event.data {
1119                mouse_data.button == MouseButton::Middle
1120            } else {
1121                false
1122            }
1123        }
1124        (MouseEnter, EventType::MouseEnter) => true,
1125        (MouseLeave, EventType::MouseLeave) => true,
1126        (Scroll, EventType::Scroll) => true,
1127        (ScrollStart, EventType::ScrollStart) => true,
1128        (ScrollEnd, EventType::ScrollEnd) => true,
1129        (TextInput, EventType::Input) => true,
1130        (VirtualKeyDown, EventType::KeyDown) => true,
1131        (VirtualKeyUp, EventType::KeyUp) => true,
1132        (HoveredFile, EventType::FileHover) => true,
1133        (DroppedFile, EventType::FileDrop) => true,
1134        (HoveredFileCancelled, EventType::FileHoverCancel) => true,
1135        (TouchStart, EventType::TouchStart) => true,
1136        (TouchMove, EventType::TouchMove) => true,
1137        (TouchEnd, EventType::TouchEnd) => true,
1138        (TouchCancel, EventType::TouchCancel) => true,
1139        (DragStart, EventType::DragStart) => true,
1140        (Drag, EventType::Drag) => true,
1141        (DragEnd, EventType::DragEnd) => true,
1142        (DragEnter, EventType::DragEnter) => true,
1143        (DragOver, EventType::DragOver) => true,
1144        (DragLeave, EventType::DragLeave) => true,
1145        (Drop, EventType::Drop) => true,
1146        (DoubleClick, EventType::DoubleClick) => true,
1147        _ => false,
1148    }
1149}
1150
1151/// Check if a focus filter matches the event.
1152fn matches_focus_filter(
1153    filter: &FocusEventFilter,
1154    event: &SyntheticEvent,
1155    _phase: EventPhase,
1156) -> bool {
1157    use FocusEventFilter::*;
1158
1159    match (filter, &event.event_type) {
1160        (MouseOver, EventType::MouseOver) => true,
1161        (MouseDown, EventType::MouseDown) => true,
1162        (LeftMouseDown, EventType::MouseDown) => {
1163            if let EventData::Mouse(mouse_data) = &event.data {
1164                mouse_data.button == MouseButton::Left
1165            } else {
1166                false
1167            }
1168        }
1169        (RightMouseDown, EventType::MouseDown) => {
1170            if let EventData::Mouse(mouse_data) = &event.data {
1171                mouse_data.button == MouseButton::Right
1172            } else {
1173                false
1174            }
1175        }
1176        (MiddleMouseDown, EventType::MouseDown) => {
1177            if let EventData::Mouse(mouse_data) = &event.data {
1178                mouse_data.button == MouseButton::Middle
1179            } else {
1180                false
1181            }
1182        }
1183        (MouseUp, EventType::MouseUp) => true,
1184        (LeftMouseUp, EventType::MouseUp) => {
1185            if let EventData::Mouse(mouse_data) = &event.data {
1186                mouse_data.button == MouseButton::Left
1187            } else {
1188                false
1189            }
1190        }
1191        (RightMouseUp, EventType::MouseUp) => {
1192            if let EventData::Mouse(mouse_data) = &event.data {
1193                mouse_data.button == MouseButton::Right
1194            } else {
1195                false
1196            }
1197        }
1198        (MiddleMouseUp, EventType::MouseUp) => {
1199            if let EventData::Mouse(mouse_data) = &event.data {
1200                mouse_data.button == MouseButton::Middle
1201            } else {
1202                false
1203            }
1204        }
1205        (MouseEnter, EventType::MouseEnter) => true,
1206        (MouseLeave, EventType::MouseLeave) => true,
1207        (Scroll, EventType::Scroll) => true,
1208        (ScrollStart, EventType::ScrollStart) => true,
1209        (ScrollEnd, EventType::ScrollEnd) => true,
1210        (TextInput, EventType::Input) => true,
1211        (VirtualKeyDown, EventType::KeyDown) => true,
1212        (VirtualKeyUp, EventType::KeyUp) => true,
1213        (FocusReceived, EventType::Focus) => true,
1214        (FocusLost, EventType::Blur) => true,
1215        (DragStart, EventType::DragStart) => true,
1216        (Drag, EventType::Drag) => true,
1217        (DragEnd, EventType::DragEnd) => true,
1218        (DragEnter, EventType::DragEnter) => true,
1219        (DragOver, EventType::DragOver) => true,
1220        (DragLeave, EventType::DragLeave) => true,
1221        (Drop, EventType::Drop) => true,
1222        _ => false,
1223    }
1224}
1225
1226/// Check if a window filter matches the event.
1227fn matches_window_filter(
1228    filter: &WindowEventFilter,
1229    event: &SyntheticEvent,
1230    _phase: EventPhase,
1231) -> bool {
1232    use WindowEventFilter::*;
1233
1234    match (filter, &event.event_type) {
1235        (MouseOver, EventType::MouseOver) => true,
1236        (MouseDown, EventType::MouseDown) => true,
1237        (LeftMouseDown, EventType::MouseDown) => {
1238            if let EventData::Mouse(mouse_data) = &event.data {
1239                mouse_data.button == MouseButton::Left
1240            } else {
1241                false
1242            }
1243        }
1244        (RightMouseDown, EventType::MouseDown) => {
1245            if let EventData::Mouse(mouse_data) = &event.data {
1246                mouse_data.button == MouseButton::Right
1247            } else {
1248                false
1249            }
1250        }
1251        (MiddleMouseDown, EventType::MouseDown) => {
1252            if let EventData::Mouse(mouse_data) = &event.data {
1253                mouse_data.button == MouseButton::Middle
1254            } else {
1255                false
1256            }
1257        }
1258        (MouseUp, EventType::MouseUp) => true,
1259        (LeftMouseUp, EventType::MouseUp) => {
1260            if let EventData::Mouse(mouse_data) = &event.data {
1261                mouse_data.button == MouseButton::Left
1262            } else {
1263                false
1264            }
1265        }
1266        (RightMouseUp, EventType::MouseUp) => {
1267            if let EventData::Mouse(mouse_data) = &event.data {
1268                mouse_data.button == MouseButton::Right
1269            } else {
1270                false
1271            }
1272        }
1273        (MiddleMouseUp, EventType::MouseUp) => {
1274            if let EventData::Mouse(mouse_data) = &event.data {
1275                mouse_data.button == MouseButton::Middle
1276            } else {
1277                false
1278            }
1279        }
1280        (MouseEnter, EventType::MouseEnter) => true,
1281        (MouseLeave, EventType::MouseLeave) => true,
1282        (Scroll, EventType::Scroll) => true,
1283        (ScrollStart, EventType::ScrollStart) => true,
1284        (ScrollEnd, EventType::ScrollEnd) => true,
1285        (TextInput, EventType::Input) => true,
1286        (VirtualKeyDown, EventType::KeyDown) => true,
1287        (VirtualKeyUp, EventType::KeyUp) => true,
1288        (HoveredFile, EventType::FileHover) => true,
1289        (DroppedFile, EventType::FileDrop) => true,
1290        (HoveredFileCancelled, EventType::FileHoverCancel) => true,
1291        (Resized, EventType::WindowResize) => true,
1292        (Moved, EventType::WindowMove) => true,
1293        (TouchStart, EventType::TouchStart) => true,
1294        (TouchMove, EventType::TouchMove) => true,
1295        (TouchEnd, EventType::TouchEnd) => true,
1296        (TouchCancel, EventType::TouchCancel) => true,
1297        (FocusReceived, EventType::Focus) => true,
1298        (FocusLost, EventType::Blur) => true,
1299        (CloseRequested, EventType::WindowClose) => true,
1300        (ThemeChanged, EventType::ThemeChange) => true,
1301        (WindowFocusReceived, EventType::WindowFocusIn) => true,
1302        (WindowFocusLost, EventType::WindowFocusOut) => true,
1303        (DragStart, EventType::DragStart) => true,
1304        (Drag, EventType::Drag) => true,
1305        (DragEnd, EventType::DragEnd) => true,
1306        (DragEnter, EventType::DragEnter) => true,
1307        (DragOver, EventType::DragOver) => true,
1308        (DragLeave, EventType::DragLeave) => true,
1309        (Drop, EventType::Drop) => true,
1310        _ => false,
1311    }
1312}
1313
1314// Phase 3.5, Step 4: Lifecycle Event Detection
1315
1316/// Detect lifecycle events by comparing old and new DOM state.
1317///
1318/// This is the simple, index-based lifecycle detection that doesn't account for
1319/// node reordering. For more sophisticated reconciliation that can detect moves,
1320/// use `detect_lifecycle_events_with_reconciliation`.
1321///
1322/// Generates Mount, Unmount, and Resize events by comparing DOM hierarchies.
1323pub fn detect_lifecycle_events(
1324    old_dom_id: DomId,
1325    new_dom_id: DomId,
1326    old_hierarchy: Option<&crate::id::NodeHierarchy>,
1327    new_hierarchy: Option<&crate::id::NodeHierarchy>,
1328    old_layout: Option<&BTreeMap<NodeId, LogicalRect>>,
1329    new_layout: Option<&BTreeMap<NodeId, LogicalRect>>,
1330    timestamp: Instant,
1331) -> Vec<SyntheticEvent> {
1332    let old_nodes = collect_node_ids(old_hierarchy);
1333    let new_nodes = collect_node_ids(new_hierarchy);
1334
1335    let mut events = Vec::new();
1336
1337    // Mount events: nodes in new but not in old
1338    if let Some(layout) = new_layout {
1339        for &node_id in new_nodes.difference(&old_nodes) {
1340            events.push(create_mount_event(node_id, new_dom_id, layout, &timestamp));
1341        }
1342    }
1343
1344    // Unmount events: nodes in old but not in new
1345    if let Some(layout) = old_layout {
1346        for &node_id in old_nodes.difference(&new_nodes) {
1347            events.push(create_unmount_event(
1348                node_id, old_dom_id, layout, &timestamp,
1349            ));
1350        }
1351    }
1352
1353    // Resize events: nodes in both with changed bounds
1354    if let (Some(old_l), Some(new_l)) = (old_layout, new_layout) {
1355        for &node_id in old_nodes.intersection(&new_nodes) {
1356            if let Some(ev) = create_resize_event(node_id, new_dom_id, old_l, new_l, &timestamp) {
1357                events.push(ev);
1358            }
1359        }
1360    }
1361
1362    events
1363}
1364
1365fn collect_node_ids(hierarchy: Option<&crate::id::NodeHierarchy>) -> BTreeSet<NodeId> {
1366    hierarchy
1367        .map(|h| h.as_ref().linear_iter().collect())
1368        .unwrap_or_default()
1369}
1370
1371fn create_lifecycle_event(
1372    event_type: EventType,
1373    node_id: NodeId,
1374    dom_id: DomId,
1375    timestamp: &Instant,
1376    data: LifecycleEventData,
1377) -> SyntheticEvent {
1378    let dom_node_id = DomNodeId {
1379        dom: dom_id,
1380        node: NodeHierarchyItemId::from_crate_internal(Some(node_id)),
1381    };
1382    SyntheticEvent {
1383        event_type,
1384        source: EventSource::Lifecycle,
1385        phase: EventPhase::Target,
1386        target: dom_node_id,
1387        current_target: dom_node_id,
1388        timestamp: timestamp.clone(),
1389        data: EventData::Lifecycle(data),
1390        stopped: false,
1391        stopped_immediate: false,
1392        prevented_default: false,
1393    }
1394}
1395
1396fn create_mount_event(
1397    node_id: NodeId,
1398    dom_id: DomId,
1399    layout: &BTreeMap<NodeId, LogicalRect>,
1400    timestamp: &Instant,
1401) -> SyntheticEvent {
1402    let current_bounds = layout.get(&node_id).copied().unwrap_or(LogicalRect::zero());
1403    create_lifecycle_event(
1404        EventType::Mount,
1405        node_id,
1406        dom_id,
1407        timestamp,
1408        LifecycleEventData {
1409            reason: LifecycleReason::InitialMount,
1410            previous_bounds: None,
1411            current_bounds,
1412        },
1413    )
1414}
1415
1416fn create_unmount_event(
1417    node_id: NodeId,
1418    dom_id: DomId,
1419    layout: &BTreeMap<NodeId, LogicalRect>,
1420    timestamp: &Instant,
1421) -> SyntheticEvent {
1422    let previous_bounds = layout.get(&node_id).copied().unwrap_or(LogicalRect::zero());
1423    create_lifecycle_event(
1424        EventType::Unmount,
1425        node_id,
1426        dom_id,
1427        timestamp,
1428        LifecycleEventData {
1429            reason: LifecycleReason::InitialMount,
1430            previous_bounds: Some(previous_bounds),
1431            current_bounds: LogicalRect::zero(),
1432        },
1433    )
1434}
1435
1436fn create_resize_event(
1437    node_id: NodeId,
1438    dom_id: DomId,
1439    old_layout: &BTreeMap<NodeId, LogicalRect>,
1440    new_layout: &BTreeMap<NodeId, LogicalRect>,
1441    timestamp: &Instant,
1442) -> Option<SyntheticEvent> {
1443    let old_bounds = *old_layout.get(&node_id)?;
1444    let new_bounds = *new_layout.get(&node_id)?;
1445
1446    if old_bounds.size == new_bounds.size {
1447        return None;
1448    }
1449
1450    Some(create_lifecycle_event(
1451        EventType::Resize,
1452        node_id,
1453        dom_id,
1454        timestamp,
1455        LifecycleEventData {
1456            reason: LifecycleReason::Resize,
1457            previous_bounds: Some(old_bounds),
1458            current_bounds: new_bounds,
1459        },
1460    ))
1461}
1462
1463/// Result of lifecycle event detection with reconciliation.
1464///
1465/// Contains both the generated lifecycle events and a mapping from old to new
1466/// node IDs for state migration (focus, scroll, etc.).
1467#[derive(Debug, Clone, Default)]
1468pub struct LifecycleEventResult {
1469    /// Lifecycle events (Mount, Unmount, Resize, Update)
1470    pub events: Vec<SyntheticEvent>,
1471    /// Maps old NodeId -> new NodeId for matched nodes.
1472    /// Use this to migrate focus, scroll state, and other node-specific state.
1473    pub node_id_mapping: crate::FastHashMap<NodeId, NodeId>,
1474}
1475
1476/// Detect lifecycle events using reconciliation with stable keys and content hashing.
1477///
1478/// This is the advanced lifecycle detection that can correctly identify:
1479/// - **Moves**: When a node changes position but keeps its identity (via key or hash)
1480/// - **Mounts**: When a new node appears
1481/// - **Unmounts**: When an existing node disappears
1482/// - **Resizes**: When a node's layout bounds change
1483/// - **Updates**: When a keyed node's content changes
1484///
1485/// The reconciliation strategy is:
1486/// 1. **Stable Key Match:** Nodes with `.with_reconciliation_key()` are matched by key (O(1))
1487/// 2. **Hash Match:** Nodes without keys are matched by content hash (enables reorder detection)
1488/// 3. **Fallback:** Unmatched nodes generate Mount/Unmount events
1489///
1490/// # Arguments
1491/// * `dom_id` - The DOM identifier
1492/// * `old_node_data` - Node data from the previous frame
1493/// * `new_node_data` - Node data from the current frame
1494/// * `old_layout` - Layout bounds from the previous frame
1495/// * `new_layout` - Layout bounds from the current frame
1496/// * `timestamp` - Current timestamp for events
1497///
1498/// # Returns
1499/// A `LifecycleEventResult` containing:
1500/// - `events`: Lifecycle events to dispatch
1501/// - `node_id_mapping`: Mapping from old to new NodeIds for state migration
1502///
1503/// # Example
1504/// ```rust
1505/// let result = detect_lifecycle_events_with_reconciliation(
1506///     dom_id,
1507///     &old_node_data,
1508///     &new_node_data,
1509///     &old_layout,
1510///     &new_layout,
1511///     timestamp,
1512/// );
1513///
1514/// // Dispatch lifecycle events
1515/// for event in result.events {
1516///     dispatch_event(event);
1517/// }
1518///
1519/// // Migrate focus to new node ID
1520/// if let Some(focused) = focus_manager.focused_node {
1521///     if let Some(&new_id) = result.node_id_mapping.get(&focused) {
1522///         focus_manager.focused_node = Some(new_id);
1523///     } else {
1524///         // Focused node was unmounted
1525///         focus_manager.focused_node = None;
1526///     }
1527/// }
1528/// ```
1529pub fn detect_lifecycle_events_with_reconciliation(
1530    dom_id: DomId,
1531    old_node_data: &[crate::dom::NodeData],
1532    new_node_data: &[crate::dom::NodeData],
1533    old_layout: &crate::FastHashMap<NodeId, LogicalRect>,
1534    new_layout: &crate::FastHashMap<NodeId, LogicalRect>,
1535    timestamp: Instant,
1536) -> LifecycleEventResult {
1537    let diff_result = crate::diff::reconcile_dom(
1538        old_node_data,
1539        new_node_data,
1540        old_layout,
1541        new_layout,
1542        dom_id,
1543        timestamp,
1544    );
1545
1546    LifecycleEventResult {
1547        events: diff_result.events,
1548        node_id_mapping: crate::diff::create_migration_map(&diff_result.node_moves),
1549    }
1550}
1551
1552// Phase 3.5: Event Filter System
1553
1554/// Event filter that only fires when an element is hovered over.
1555#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
1556#[repr(C)]
1557pub enum HoverEventFilter {
1558    /// Mouse moved over the hovered element
1559    MouseOver,
1560    /// Any mouse button pressed on the hovered element
1561    MouseDown,
1562    /// Left mouse button pressed on the hovered element
1563    LeftMouseDown,
1564    /// Right mouse button pressed on the hovered element
1565    RightMouseDown,
1566    /// Middle mouse button pressed on the hovered element
1567    MiddleMouseDown,
1568    /// Any mouse button released on the hovered element
1569    MouseUp,
1570    /// Left mouse button released on the hovered element
1571    LeftMouseUp,
1572    /// Right mouse button released on the hovered element
1573    RightMouseUp,
1574    /// Middle mouse button released on the hovered element
1575    MiddleMouseUp,
1576    /// Mouse entered the hovered element bounds
1577    MouseEnter,
1578    /// Mouse left the hovered element bounds
1579    MouseLeave,
1580    /// Scroll event on the hovered element
1581    Scroll,
1582    /// Scroll started on the hovered element
1583    ScrollStart,
1584    /// Scroll ended on the hovered element
1585    ScrollEnd,
1586    /// Text input received while element is hovered
1587    TextInput,
1588    /// Virtual key pressed while element is hovered
1589    VirtualKeyDown,
1590    /// Virtual key released while element is hovered
1591    VirtualKeyUp,
1592    /// File is being hovered over the element
1593    HoveredFile,
1594    /// File was dropped onto the element
1595    DroppedFile,
1596    /// File hover was cancelled
1597    HoveredFileCancelled,
1598    /// Touch started on the hovered element
1599    TouchStart,
1600    /// Touch moved on the hovered element
1601    TouchMove,
1602    /// Touch ended on the hovered element
1603    TouchEnd,
1604    /// Touch was cancelled on the hovered element
1605    TouchCancel,
1606    /// Pen/stylus made contact on the hovered element
1607    PenDown,
1608    /// Pen/stylus moved while in contact on the hovered element
1609    PenMove,
1610    /// Pen/stylus lifted from the hovered element
1611    PenUp,
1612    /// Pen/stylus entered proximity of the hovered element
1613    PenEnter,
1614    /// Pen/stylus left proximity of the hovered element
1615    PenLeave,
1616    /// Drag started on the hovered element
1617    DragStart,
1618    /// Drag in progress on the hovered element
1619    Drag,
1620    /// Drag ended on the hovered element
1621    DragEnd,
1622    /// Dragged element entered this element (drop target)
1623    DragEnter,
1624    /// Dragged element is over this element (drop target, fires continuously)
1625    DragOver,
1626    /// Dragged element left this element (drop target)
1627    DragLeave,
1628    /// Element was dropped on this element (drop target)
1629    Drop,
1630    /// Double-click detected on the hovered element
1631    DoubleClick,
1632    /// Long press detected on the hovered element
1633    LongPress,
1634    /// Swipe left gesture on the hovered element
1635    SwipeLeft,
1636    /// Swipe right gesture on the hovered element
1637    SwipeRight,
1638    /// Swipe up gesture on the hovered element
1639    SwipeUp,
1640    /// Swipe down gesture on the hovered element
1641    SwipeDown,
1642    /// Pinch-in (zoom out) gesture on the hovered element
1643    PinchIn,
1644    /// Pinch-out (zoom in) gesture on the hovered element
1645    PinchOut,
1646    /// Clockwise rotation gesture on the hovered element
1647    RotateClockwise,
1648    /// Counter-clockwise rotation gesture on the hovered element
1649    RotateCounterClockwise,
1650
1651    // W3C MouseOut event (bubbling version of MouseLeave)
1652    /// Mouse left the element OR moved to a child element (W3C `mouseout`, bubbles)
1653    MouseOut,
1654
1655    // W3C Focus events (bubbling versions)
1656    /// Focus is about to move INTO this element or a descendant (W3C `focusin`, bubbles)
1657    FocusIn,
1658    /// Focus is about to move OUT of this element or a descendant (W3C `focusout`, bubbles)
1659    FocusOut,
1660
1661    // IME Composition events
1662    /// IME composition started (W3C `compositionstart`)
1663    CompositionStart,
1664    /// IME composition updated (W3C `compositionupdate`)
1665    CompositionUpdate,
1666    /// IME composition ended (W3C `compositionend`)
1667    CompositionEnd,
1668
1669    // Internal System Events (not exposed to user callbacks)
1670    #[doc(hidden)]
1671    /// Internal: Single click for text cursor placement
1672    SystemTextSingleClick,
1673    #[doc(hidden)]
1674    /// Internal: Double click for word selection
1675    SystemTextDoubleClick,
1676    #[doc(hidden)]
1677    /// Internal: Triple click for paragraph/line selection
1678    SystemTextTripleClick,
1679}
1680
1681impl HoverEventFilter {
1682    /// Check if this is an internal system event that should not be exposed to user callbacks
1683    pub const fn is_system_internal(&self) -> bool {
1684        matches!(
1685            self,
1686            HoverEventFilter::SystemTextSingleClick
1687                | HoverEventFilter::SystemTextDoubleClick
1688                | HoverEventFilter::SystemTextTripleClick
1689        )
1690    }
1691
1692    pub fn to_focus_event_filter(&self) -> Option<FocusEventFilter> {
1693        match self {
1694            HoverEventFilter::MouseOver => Some(FocusEventFilter::MouseOver),
1695            HoverEventFilter::MouseDown => Some(FocusEventFilter::MouseDown),
1696            HoverEventFilter::LeftMouseDown => Some(FocusEventFilter::LeftMouseDown),
1697            HoverEventFilter::RightMouseDown => Some(FocusEventFilter::RightMouseDown),
1698            HoverEventFilter::MiddleMouseDown => Some(FocusEventFilter::MiddleMouseDown),
1699            HoverEventFilter::MouseUp => Some(FocusEventFilter::MouseUp),
1700            HoverEventFilter::LeftMouseUp => Some(FocusEventFilter::LeftMouseUp),
1701            HoverEventFilter::RightMouseUp => Some(FocusEventFilter::RightMouseUp),
1702            HoverEventFilter::MiddleMouseUp => Some(FocusEventFilter::MiddleMouseUp),
1703            HoverEventFilter::MouseEnter => Some(FocusEventFilter::MouseEnter),
1704            HoverEventFilter::MouseLeave => Some(FocusEventFilter::MouseLeave),
1705            HoverEventFilter::Scroll => Some(FocusEventFilter::Scroll),
1706            HoverEventFilter::ScrollStart => Some(FocusEventFilter::ScrollStart),
1707            HoverEventFilter::ScrollEnd => Some(FocusEventFilter::ScrollEnd),
1708            HoverEventFilter::TextInput => Some(FocusEventFilter::TextInput),
1709            HoverEventFilter::VirtualKeyDown => Some(FocusEventFilter::VirtualKeyDown),
1710            HoverEventFilter::VirtualKeyUp => Some(FocusEventFilter::VirtualKeyDown),
1711            HoverEventFilter::HoveredFile => None,
1712            HoverEventFilter::DroppedFile => None,
1713            HoverEventFilter::HoveredFileCancelled => None,
1714            HoverEventFilter::TouchStart => None,
1715            HoverEventFilter::TouchMove => None,
1716            HoverEventFilter::TouchEnd => None,
1717            HoverEventFilter::TouchCancel => None,
1718            HoverEventFilter::PenDown => Some(FocusEventFilter::PenDown),
1719            HoverEventFilter::PenMove => Some(FocusEventFilter::PenMove),
1720            HoverEventFilter::PenUp => Some(FocusEventFilter::PenUp),
1721            HoverEventFilter::PenEnter => None,
1722            HoverEventFilter::PenLeave => None,
1723            HoverEventFilter::DragStart => Some(FocusEventFilter::DragStart),
1724            HoverEventFilter::Drag => Some(FocusEventFilter::Drag),
1725            HoverEventFilter::DragEnd => Some(FocusEventFilter::DragEnd),
1726            HoverEventFilter::DragEnter => Some(FocusEventFilter::DragEnter),
1727            HoverEventFilter::DragOver => Some(FocusEventFilter::DragOver),
1728            HoverEventFilter::DragLeave => Some(FocusEventFilter::DragLeave),
1729            HoverEventFilter::Drop => Some(FocusEventFilter::Drop),
1730            HoverEventFilter::DoubleClick => Some(FocusEventFilter::DoubleClick),
1731            HoverEventFilter::LongPress => Some(FocusEventFilter::LongPress),
1732            HoverEventFilter::SwipeLeft => Some(FocusEventFilter::SwipeLeft),
1733            HoverEventFilter::SwipeRight => Some(FocusEventFilter::SwipeRight),
1734            HoverEventFilter::SwipeUp => Some(FocusEventFilter::SwipeUp),
1735            HoverEventFilter::SwipeDown => Some(FocusEventFilter::SwipeDown),
1736            HoverEventFilter::PinchIn => Some(FocusEventFilter::PinchIn),
1737            HoverEventFilter::PinchOut => Some(FocusEventFilter::PinchOut),
1738            HoverEventFilter::RotateClockwise => Some(FocusEventFilter::RotateClockwise),
1739            HoverEventFilter::RotateCounterClockwise => {
1740                Some(FocusEventFilter::RotateCounterClockwise)
1741            }
1742            HoverEventFilter::MouseOut => Some(FocusEventFilter::MouseLeave), // mouseout → closest focus equivalent
1743            HoverEventFilter::FocusIn => Some(FocusEventFilter::FocusIn),
1744            HoverEventFilter::FocusOut => Some(FocusEventFilter::FocusOut),
1745            HoverEventFilter::CompositionStart => Some(FocusEventFilter::CompositionStart),
1746            HoverEventFilter::CompositionUpdate => Some(FocusEventFilter::CompositionUpdate),
1747            HoverEventFilter::CompositionEnd => Some(FocusEventFilter::CompositionEnd),
1748            // System internal events - don't convert to focus events
1749            HoverEventFilter::SystemTextSingleClick => None,
1750            HoverEventFilter::SystemTextDoubleClick => None,
1751            HoverEventFilter::SystemTextTripleClick => None,
1752        }
1753    }
1754}
1755
1756/// Event filter similar to `HoverEventFilter` that only fires when the element is focused.
1757///
1758/// **Important**: In order for this to fire, the item must have a `tabindex` attribute
1759/// (to indicate that the item is focus-able).
1760#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
1761#[repr(C)]
1762pub enum FocusEventFilter {
1763    /// Mouse moved over the focused element
1764    MouseOver,
1765    /// Any mouse button pressed on the focused element
1766    MouseDown,
1767    /// Left mouse button pressed on the focused element
1768    LeftMouseDown,
1769    /// Right mouse button pressed on the focused element
1770    RightMouseDown,
1771    /// Middle mouse button pressed on the focused element
1772    MiddleMouseDown,
1773    /// Any mouse button released on the focused element
1774    MouseUp,
1775    /// Left mouse button released on the focused element
1776    LeftMouseUp,
1777    /// Right mouse button released on the focused element
1778    RightMouseUp,
1779    /// Middle mouse button released on the focused element
1780    MiddleMouseUp,
1781    /// Mouse entered the focused element bounds
1782    MouseEnter,
1783    /// Mouse left the focused element bounds
1784    MouseLeave,
1785    /// Scroll event on the focused element
1786    Scroll,
1787    /// Scroll started on the focused element
1788    ScrollStart,
1789    /// Scroll ended on the focused element
1790    ScrollEnd,
1791    /// Text input received while element is focused
1792    TextInput,
1793    /// Virtual key pressed while element is focused
1794    VirtualKeyDown,
1795    /// Virtual key released while element is focused
1796    VirtualKeyUp,
1797    /// Element received keyboard focus
1798    FocusReceived,
1799    /// Element lost keyboard focus
1800    FocusLost,
1801    /// Pen/stylus made contact on the focused element
1802    PenDown,
1803    /// Pen/stylus moved while in contact on the focused element
1804    PenMove,
1805    /// Pen/stylus lifted from the focused element
1806    PenUp,
1807    /// Drag started on the focused element
1808    DragStart,
1809    /// Drag in progress on the focused element
1810    Drag,
1811    /// Drag ended on the focused element
1812    DragEnd,
1813    /// Dragged element entered this focused element (drop target)
1814    DragEnter,
1815    /// Dragged element is over this focused element (drop target)
1816    DragOver,
1817    /// Dragged element left this focused element (drop target)
1818    DragLeave,
1819    /// Element was dropped on this focused element (drop target)
1820    Drop,
1821    /// Double-click detected on the focused element
1822    DoubleClick,
1823    /// Long press detected on the focused element
1824    LongPress,
1825    /// Swipe left gesture on the focused element
1826    SwipeLeft,
1827    /// Swipe right gesture on the focused element
1828    SwipeRight,
1829    /// Swipe up gesture on the focused element
1830    SwipeUp,
1831    /// Swipe down gesture on the focused element
1832    SwipeDown,
1833    /// Pinch-in (zoom out) gesture on the focused element
1834    PinchIn,
1835    /// Pinch-out (zoom in) gesture on the focused element
1836    PinchOut,
1837    /// Clockwise rotation gesture on the focused element
1838    RotateClockwise,
1839    /// Counter-clockwise rotation gesture on the focused element
1840    RotateCounterClockwise,
1841
1842    // W3C Focus events (bubbling versions, fires on focused element when focus changes)
1843    /// Focus moved into this element or a descendant (W3C `focusin`)
1844    FocusIn,
1845    /// Focus moved out of this element or a descendant (W3C `focusout`)
1846    FocusOut,
1847
1848    // IME Composition events
1849    /// IME composition started (W3C `compositionstart`)
1850    CompositionStart,
1851    /// IME composition updated (W3C `compositionupdate`)
1852    CompositionUpdate,
1853    /// IME composition ended (W3C `compositionend`)
1854    CompositionEnd,
1855}
1856
1857/// Event filter that fires when any action fires on the entire window
1858/// (regardless of whether any element is hovered or focused over).
1859#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
1860#[repr(C)]
1861pub enum WindowEventFilter {
1862    /// Mouse moved anywhere in window
1863    MouseOver,
1864    /// Any mouse button pressed anywhere in window
1865    MouseDown,
1866    /// Left mouse button pressed anywhere in window
1867    LeftMouseDown,
1868    /// Right mouse button pressed anywhere in window
1869    RightMouseDown,
1870    /// Middle mouse button pressed anywhere in window
1871    MiddleMouseDown,
1872    /// Any mouse button released anywhere in window
1873    MouseUp,
1874    /// Left mouse button released anywhere in window
1875    LeftMouseUp,
1876    /// Right mouse button released anywhere in window
1877    RightMouseUp,
1878    /// Middle mouse button released anywhere in window
1879    MiddleMouseUp,
1880    /// Mouse entered the window
1881    MouseEnter,
1882    /// Mouse left the window
1883    MouseLeave,
1884    /// Scroll event anywhere in window
1885    Scroll,
1886    /// Scroll started anywhere in window
1887    ScrollStart,
1888    /// Scroll ended anywhere in window
1889    ScrollEnd,
1890    /// Text input received in window
1891    TextInput,
1892    /// Virtual key pressed in window
1893    VirtualKeyDown,
1894    /// Virtual key released in window
1895    VirtualKeyUp,
1896    /// File is being hovered over the window
1897    HoveredFile,
1898    /// File was dropped onto the window
1899    DroppedFile,
1900    /// File hover was cancelled
1901    HoveredFileCancelled,
1902    /// Window was resized
1903    Resized,
1904    /// Window was moved
1905    Moved,
1906    /// Touch started anywhere in window
1907    TouchStart,
1908    /// Touch moved anywhere in window
1909    TouchMove,
1910    /// Touch ended anywhere in window
1911    TouchEnd,
1912    /// Touch was cancelled
1913    TouchCancel,
1914    /// Window received focus
1915    FocusReceived,
1916    /// Window lost focus
1917    FocusLost,
1918    /// Window close was requested
1919    CloseRequested,
1920    /// System theme changed (light/dark mode)
1921    ThemeChanged,
1922    /// Window received OS-level focus
1923    WindowFocusReceived,
1924    /// Window lost OS-level focus
1925    WindowFocusLost,
1926    /// Pen/stylus made contact anywhere in window
1927    PenDown,
1928    /// Pen/stylus moved while in contact anywhere in window
1929    PenMove,
1930    /// Pen/stylus lifted anywhere in window
1931    PenUp,
1932    /// Pen/stylus entered window proximity
1933    PenEnter,
1934    /// Pen/stylus left window proximity
1935    PenLeave,
1936    /// Drag started anywhere in window
1937    DragStart,
1938    /// Drag in progress anywhere in window
1939    Drag,
1940    /// Drag ended anywhere in window
1941    DragEnd,
1942    /// Dragged element entered a drop target in window
1943    DragEnter,
1944    /// Dragged element is over a drop target in window
1945    DragOver,
1946    /// Dragged element left a drop target in window
1947    DragLeave,
1948    /// Element was dropped on a drop target in window
1949    Drop,
1950    /// Double-click detected anywhere in window
1951    DoubleClick,
1952    /// Long press detected anywhere in window
1953    LongPress,
1954    /// Swipe left gesture anywhere in window
1955    SwipeLeft,
1956    /// Swipe right gesture anywhere in window
1957    SwipeRight,
1958    /// Swipe up gesture anywhere in window
1959    SwipeUp,
1960    /// Swipe down gesture anywhere in window
1961    SwipeDown,
1962    /// Pinch-in (zoom out) gesture anywhere in window
1963    PinchIn,
1964    /// Pinch-out (zoom in) gesture anywhere in window
1965    PinchOut,
1966    /// Clockwise rotation gesture anywhere in window
1967    RotateClockwise,
1968    /// Counter-clockwise rotation gesture anywhere in window
1969    RotateCounterClockwise,
1970    /// The window's DPI scale factor changed (e.g., moved to a monitor with
1971    /// different scaling). The new DPI is available via `CallbackInfo::get_hidpi_factor()`.
1972    DpiChanged,
1973    /// The window moved to a different monitor. The new monitor is available
1974    /// via `CallbackInfo::get_current_monitor()`.
1975    MonitorChanged,
1976}
1977
1978impl WindowEventFilter {
1979    pub fn to_hover_event_filter(&self) -> Option<HoverEventFilter> {
1980        match self {
1981            WindowEventFilter::MouseOver => Some(HoverEventFilter::MouseOver),
1982            WindowEventFilter::MouseDown => Some(HoverEventFilter::MouseDown),
1983            WindowEventFilter::LeftMouseDown => Some(HoverEventFilter::LeftMouseDown),
1984            WindowEventFilter::RightMouseDown => Some(HoverEventFilter::RightMouseDown),
1985            WindowEventFilter::MiddleMouseDown => Some(HoverEventFilter::MiddleMouseDown),
1986            WindowEventFilter::MouseUp => Some(HoverEventFilter::MouseUp),
1987            WindowEventFilter::LeftMouseUp => Some(HoverEventFilter::LeftMouseUp),
1988            WindowEventFilter::RightMouseUp => Some(HoverEventFilter::RightMouseUp),
1989            WindowEventFilter::MiddleMouseUp => Some(HoverEventFilter::MiddleMouseUp),
1990            WindowEventFilter::Scroll => Some(HoverEventFilter::Scroll),
1991            WindowEventFilter::ScrollStart => Some(HoverEventFilter::ScrollStart),
1992            WindowEventFilter::ScrollEnd => Some(HoverEventFilter::ScrollEnd),
1993            WindowEventFilter::TextInput => Some(HoverEventFilter::TextInput),
1994            WindowEventFilter::VirtualKeyDown => Some(HoverEventFilter::VirtualKeyDown),
1995            WindowEventFilter::VirtualKeyUp => Some(HoverEventFilter::VirtualKeyDown),
1996            WindowEventFilter::HoveredFile => Some(HoverEventFilter::HoveredFile),
1997            WindowEventFilter::DroppedFile => Some(HoverEventFilter::DroppedFile),
1998            WindowEventFilter::HoveredFileCancelled => Some(HoverEventFilter::HoveredFileCancelled),
1999            // MouseEnter and MouseLeave on the **window** - does not mean a mouseenter
2000            // and a mouseleave on the hovered element
2001            WindowEventFilter::MouseEnter => None,
2002            WindowEventFilter::MouseLeave => None,
2003            WindowEventFilter::Resized => None,
2004            WindowEventFilter::Moved => None,
2005            WindowEventFilter::TouchStart => Some(HoverEventFilter::TouchStart),
2006            WindowEventFilter::TouchMove => Some(HoverEventFilter::TouchMove),
2007            WindowEventFilter::TouchEnd => Some(HoverEventFilter::TouchEnd),
2008            WindowEventFilter::TouchCancel => Some(HoverEventFilter::TouchCancel),
2009            WindowEventFilter::FocusReceived => None,
2010            WindowEventFilter::FocusLost => None,
2011            WindowEventFilter::CloseRequested => None,
2012            WindowEventFilter::ThemeChanged => None,
2013            WindowEventFilter::WindowFocusReceived => None, // specific to window!
2014            WindowEventFilter::WindowFocusLost => None,     // specific to window!
2015            WindowEventFilter::PenDown => Some(HoverEventFilter::PenDown),
2016            WindowEventFilter::PenMove => Some(HoverEventFilter::PenMove),
2017            WindowEventFilter::PenUp => Some(HoverEventFilter::PenUp),
2018            WindowEventFilter::PenEnter => Some(HoverEventFilter::PenEnter),
2019            WindowEventFilter::PenLeave => Some(HoverEventFilter::PenLeave),
2020            WindowEventFilter::DragStart => Some(HoverEventFilter::DragStart),
2021            WindowEventFilter::Drag => Some(HoverEventFilter::Drag),
2022            WindowEventFilter::DragEnd => Some(HoverEventFilter::DragEnd),
2023            WindowEventFilter::DragEnter => Some(HoverEventFilter::DragEnter),
2024            WindowEventFilter::DragOver => Some(HoverEventFilter::DragOver),
2025            WindowEventFilter::DragLeave => Some(HoverEventFilter::DragLeave),
2026            WindowEventFilter::Drop => Some(HoverEventFilter::Drop),
2027            WindowEventFilter::DoubleClick => Some(HoverEventFilter::DoubleClick),
2028            WindowEventFilter::LongPress => Some(HoverEventFilter::LongPress),
2029            WindowEventFilter::SwipeLeft => Some(HoverEventFilter::SwipeLeft),
2030            WindowEventFilter::SwipeRight => Some(HoverEventFilter::SwipeRight),
2031            WindowEventFilter::SwipeUp => Some(HoverEventFilter::SwipeUp),
2032            WindowEventFilter::SwipeDown => Some(HoverEventFilter::SwipeDown),
2033            WindowEventFilter::PinchIn => Some(HoverEventFilter::PinchIn),
2034            WindowEventFilter::PinchOut => Some(HoverEventFilter::PinchOut),
2035            WindowEventFilter::RotateClockwise => Some(HoverEventFilter::RotateClockwise),
2036            WindowEventFilter::RotateCounterClockwise => {
2037                Some(HoverEventFilter::RotateCounterClockwise)
2038            }
2039            // Window-specific events with no hover equivalent
2040            WindowEventFilter::DpiChanged => None,
2041            WindowEventFilter::MonitorChanged => None,
2042        }
2043    }
2044}
2045
2046/// The inverse of an `onclick` event filter, fires when an item is *not* hovered / focused.
2047/// This is useful for cleanly implementing things like popover dialogs or dropdown boxes that
2048/// want to close when the user clicks any where *but* the item itself.
2049#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
2050#[repr(C, u8)]
2051pub enum NotEventFilter {
2052    Hover(HoverEventFilter),
2053    Focus(FocusEventFilter),
2054}
2055
2056impl NotEventFilter {
2057    pub fn as_event_filter(&self) -> EventFilter {
2058        match self {
2059            NotEventFilter::Hover(e) => EventFilter::Hover(*e),
2060            NotEventFilter::Focus(e) => EventFilter::Focus(*e),
2061        }
2062    }
2063}
2064
2065/// Defines events related to the lifecycle of a DOM node itself.
2066#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
2067#[repr(C)]
2068pub enum ComponentEventFilter {
2069    /// Fired after the component is first mounted into the DOM.
2070    AfterMount,
2071    /// Fired just before the component is removed from the DOM.
2072    BeforeUnmount,
2073    /// Fired when the node's layout rectangle has been resized.
2074    NodeResized,
2075    /// Fired to trigger the default action for an accessibility component.
2076    DefaultAction,
2077    /// Fired when the component becomes selected.
2078    Selected,
2079}
2080
2081/// Defines application-level events not tied to a specific window or node.
2082#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
2083#[repr(C)]
2084pub enum ApplicationEventFilter {
2085    /// Fired when a new hardware device is connected.
2086    DeviceConnected,
2087    /// Fired when a hardware device is disconnected.
2088    DeviceDisconnected,
2089    /// Fired when a new monitor/display is connected to the system.
2090    /// Callback receives updated monitor list via `CallbackInfo::get_monitors()`.
2091    MonitorConnected,
2092    /// Fired when a monitor/display is disconnected from the system.
2093    MonitorDisconnected,
2094}
2095
2096/// Sets the target for what events can reach the callbacks specifically.
2097///
2098/// This determines the condition under which an event is fired, such as whether
2099/// the node is hovered, focused, or if the event is window-global.
2100#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
2101#[repr(C, u8)]
2102pub enum EventFilter {
2103    /// Calls the attached callback when the mouse is actively over the
2104    /// given element.
2105    Hover(HoverEventFilter),
2106    /// Inverse of `Hover` - calls the attached callback if the mouse is **not**
2107    /// over the given element. This is particularly useful for popover menus
2108    /// where you want to close the menu when the user clicks anywhere else but
2109    /// the menu itself.
2110    Not(NotEventFilter),
2111    /// Calls the attached callback when the element is currently focused.
2112    Focus(FocusEventFilter),
2113    /// Calls the callback when anything related to the window is happening.
2114    /// The "hit item" will be the root item of the DOM.
2115    /// For example, this can be useful for tracking the mouse position
2116    /// (in relation to the window). In difference to `Desktop`, this only
2117    /// fires when the window is focused.
2118    ///
2119    /// This can also be good for capturing controller input, touch input
2120    /// (i.e. global gestures that aren't attached to any component, but rather
2121    /// the "window" itself).
2122    Window(WindowEventFilter),
2123    /// API stub: Something happened with the node itself (node resized, created or removed).
2124    Component(ComponentEventFilter),
2125    /// Something happened with the application (started, shutdown, device plugged in).
2126    Application(ApplicationEventFilter),
2127}
2128
2129impl EventFilter {
2130    pub const fn is_focus_callback(&self) -> bool {
2131        match self {
2132            EventFilter::Focus(_) => true,
2133            _ => false,
2134        }
2135    }
2136    pub const fn is_window_callback(&self) -> bool {
2137        match self {
2138            EventFilter::Window(_) => true,
2139            _ => false,
2140        }
2141    }
2142}
2143
2144/// Creates a function inside an impl <enum type> block that returns a single
2145/// variant if the enum is that variant.
2146macro_rules! get_single_enum_type {
2147    ($fn_name:ident, $enum_name:ident:: $variant:ident($return_type:ty)) => {
2148        pub fn $fn_name(&self) -> Option<$return_type> {
2149            use self::$enum_name::*;
2150            match self {
2151                $variant(e) => Some(*e),
2152                _ => None,
2153            }
2154        }
2155    };
2156}
2157
2158impl EventFilter {
2159    get_single_enum_type!(as_hover_event_filter, EventFilter::Hover(HoverEventFilter));
2160    get_single_enum_type!(as_focus_event_filter, EventFilter::Focus(FocusEventFilter));
2161    get_single_enum_type!(as_not_event_filter, EventFilter::Not(NotEventFilter));
2162    get_single_enum_type!(
2163        as_window_event_filter,
2164        EventFilter::Window(WindowEventFilter)
2165    );
2166}
2167
2168/// Convert from `On` enum to `EventFilter`.
2169///
2170/// This determines which specific filter variant is used based on the event type.
2171/// For example, `On::TextInput` becomes a Focus event filter, while `On::VirtualKeyDown`
2172/// becomes a Window event filter (since it's global to the window).
2173impl From<On> for EventFilter {
2174    fn from(input: On) -> EventFilter {
2175        use crate::dom::On::*;
2176        match input {
2177            MouseOver => EventFilter::Hover(HoverEventFilter::MouseOver),
2178            MouseDown => EventFilter::Hover(HoverEventFilter::MouseDown),
2179            LeftMouseDown => EventFilter::Hover(HoverEventFilter::LeftMouseDown),
2180            MiddleMouseDown => EventFilter::Hover(HoverEventFilter::MiddleMouseDown),
2181            RightMouseDown => EventFilter::Hover(HoverEventFilter::RightMouseDown),
2182            MouseUp => EventFilter::Hover(HoverEventFilter::MouseUp),
2183            LeftMouseUp => EventFilter::Hover(HoverEventFilter::LeftMouseUp),
2184            MiddleMouseUp => EventFilter::Hover(HoverEventFilter::MiddleMouseUp),
2185            RightMouseUp => EventFilter::Hover(HoverEventFilter::RightMouseUp),
2186
2187            MouseEnter => EventFilter::Hover(HoverEventFilter::MouseEnter),
2188            MouseLeave => EventFilter::Hover(HoverEventFilter::MouseLeave),
2189            Scroll => EventFilter::Hover(HoverEventFilter::Scroll),
2190            TextInput => EventFilter::Focus(FocusEventFilter::TextInput), // focus!
2191            VirtualKeyDown => EventFilter::Window(WindowEventFilter::VirtualKeyDown), // window!
2192            VirtualKeyUp => EventFilter::Window(WindowEventFilter::VirtualKeyUp), // window!
2193            HoveredFile => EventFilter::Hover(HoverEventFilter::HoveredFile),
2194            DroppedFile => EventFilter::Hover(HoverEventFilter::DroppedFile),
2195            HoveredFileCancelled => EventFilter::Hover(HoverEventFilter::HoveredFileCancelled),
2196            FocusReceived => EventFilter::Focus(FocusEventFilter::FocusReceived), // focus!
2197            FocusLost => EventFilter::Focus(FocusEventFilter::FocusLost),         // focus!
2198
2199            // Accessibility events - treat as hover events (element-specific)
2200            Default => EventFilter::Hover(HoverEventFilter::MouseUp), // Default action = click
2201            Collapse => EventFilter::Hover(HoverEventFilter::MouseUp), // Collapse = click
2202            Expand => EventFilter::Hover(HoverEventFilter::MouseUp),  // Expand = click
2203            Increment => EventFilter::Hover(HoverEventFilter::MouseUp), // Increment = click
2204            Decrement => EventFilter::Hover(HoverEventFilter::MouseUp), // Decrement = click
2205        }
2206    }
2207}
2208
2209// Cross-Platform Event Dispatch System
2210// NOTE: The old dispatch_synthetic_events / CallbackTarget / CallbackToInvoke / EventDispatchResult
2211// pipeline has been removed. Event dispatch now goes through dispatch_events_propagated() in
2212// event_v2.rs which uses propagate_event() for W3C Capture→Target→Bubble propagation.
2213
2214/// Process callback results and potentially generate new synthetic events.
2215///
2216/// This function handles the recursive nature of event processing:
2217/// 1. Process immediate callback results (state changes, images, etc.)
2218/// 2. Check if new synthetic events should be generated
2219/// 3. Recursively process those events (up to max_depth to prevent infinite loops)
2220///
2221/// Returns true if any callbacks resulted in DOM changes requiring re-layout.
2222pub fn should_recurse_callbacks<T: CallbackResultRef>(
2223    callback_results: &[T],
2224    max_depth: usize,
2225    current_depth: usize,
2226) -> bool {
2227    if current_depth >= max_depth {
2228        return false;
2229    }
2230
2231    // Check if any callback result indicates we should continue processing
2232    for result in callback_results {
2233        // If stop_propagation is set, stop processing further events
2234        if result.stop_propagation() {
2235            return false;
2236        }
2237
2238        // Check if DOM was modified (requires re-layout and re-processing)
2239        if result.should_regenerate_dom() {
2240            return current_depth + 1 < max_depth;
2241        }
2242    }
2243
2244    false
2245}
2246
2247/// Trait to abstract over callback result types.
2248/// This allows the core event system to work with results without depending on layout layer.
2249pub trait CallbackResultRef {
2250    fn stop_propagation(&self) -> bool;
2251    fn stop_immediate_propagation(&self) -> bool;
2252    fn prevent_default(&self) -> bool;
2253    fn should_regenerate_dom(&self) -> bool;
2254}
2255
2256// Unified Event Determination System (Phase 3.5+)
2257
2258/// Trait for managers to provide their pending events.
2259///
2260/// Each manager (TextInputManager, ScrollManager, etc.) implements this to
2261/// report what events occurred since the last frame. This enables a unified,
2262/// lazy event determination system.
2263pub trait EventProvider {
2264    /// Get all pending events from this manager.
2265    ///
2266    /// Events should include:
2267    ///
2268    /// - `target`: The DomNodeId that was affected
2269    /// - `event_type`: What happened (Input, Scroll, Focus, etc.)
2270    /// - `source`: EventSource::User for input, EventSource::Programmatic for API calls
2271    /// - `data`: Type-specific event data
2272    ///
2273    /// After calling this, the manager should mark events as "read" so they
2274    /// aren't returned again next frame.
2275    fn get_pending_events(&self, timestamp: Instant) -> Vec<SyntheticEvent>;
2276}
2277
2278/// Deduplicate synthetic events by (target node, event type).
2279///
2280/// Groups by (target.dom, target.node, event_type), keeping the latest timestamp.
2281pub fn deduplicate_synthetic_events(mut events: Vec<SyntheticEvent>) -> Vec<SyntheticEvent> {
2282    if events.len() <= 1 {
2283        return events;
2284    }
2285
2286    events.sort_by_key(|e| (e.target.dom, e.target.node, e.event_type));
2287
2288    // Coalesce consecutive events with same target and event_type
2289    let mut result = Vec::with_capacity(events.len());
2290    let mut iter = events.into_iter();
2291
2292    if let Some(mut prev) = iter.next() {
2293        for curr in iter {
2294            if prev.target == curr.target && prev.event_type == curr.event_type {
2295                // Keep the one with later timestamp
2296                prev = if curr.timestamp > prev.timestamp {
2297                    curr
2298                } else {
2299                    prev
2300                };
2301            } else {
2302                result.push(prev);
2303                prev = curr;
2304            }
2305        }
2306        result.push(prev);
2307    }
2308
2309    result
2310}
2311
2312
2313
2314/// Convert EventType to EventFilters (returns multiple filters for generic + specific events)
2315///
2316/// For mouse button events, returns both generic (MouseUp) AND button-specific (LeftMouseUp/RightMouseUp).
2317/// The button-specific filter is derived from the EventData::Mouse payload.
2318pub fn event_type_to_filters(event_type: EventType, event_data: &EventData) -> Vec<EventFilter> {
2319    use EventFilter as EF;
2320    use EventType as E;
2321    use FocusEventFilter as F;
2322    use HoverEventFilter as H;
2323    use WindowEventFilter as W;
2324
2325    // Helper: get the button-specific MouseDown filter from EventData
2326    let button_specific_down = || -> Option<EventFilter> {
2327        match event_data {
2328            EventData::Mouse(m) => match m.button {
2329                MouseButton::Left => Some(EF::Hover(H::LeftMouseDown)),
2330                MouseButton::Right => Some(EF::Hover(H::RightMouseDown)),
2331                MouseButton::Middle => Some(EF::Hover(H::MiddleMouseDown)),
2332                MouseButton::Other(_) => None, // no specific filter for other buttons
2333            },
2334            _ => Some(EF::Hover(H::LeftMouseDown)), // fallback
2335        }
2336    };
2337
2338    let button_specific_up = || -> Option<EventFilter> {
2339        match event_data {
2340            EventData::Mouse(m) => match m.button {
2341                MouseButton::Left => Some(EF::Hover(H::LeftMouseUp)),
2342                MouseButton::Right => Some(EF::Hover(H::RightMouseUp)),
2343                MouseButton::Middle => Some(EF::Hover(H::MiddleMouseUp)),
2344                MouseButton::Other(_) => None, // no specific filter for other buttons
2345            },
2346            _ => Some(EF::Hover(H::LeftMouseUp)), // fallback
2347        }
2348    };
2349
2350    match event_type {
2351        // Mouse button events - return BOTH generic and button-specific
2352        E::MouseDown => {
2353            let mut v = vec![EF::Hover(H::MouseDown)];
2354            if let Some(f) = button_specific_down() { v.push(f); }
2355            v
2356        }
2357        E::MouseUp => {
2358            let mut v = vec![EF::Hover(H::MouseUp)];
2359            if let Some(f) = button_specific_up() { v.push(f); }
2360            v
2361        }
2362
2363        // Click uses LeftMouseDown (W3C: click is left-button only)
2364        E::Click => vec![EF::Hover(H::LeftMouseDown)],
2365
2366        // Other mouse events
2367        E::MouseOver => vec![EF::Hover(H::MouseOver)],
2368        E::MouseEnter => vec![EF::Hover(H::MouseEnter)],
2369        E::MouseLeave => vec![EF::Hover(H::MouseLeave)],
2370        E::MouseOut => vec![EF::Hover(H::MouseOut)],
2371
2372        E::DoubleClick => vec![EF::Hover(H::DoubleClick), EF::Window(W::DoubleClick)],
2373        E::ContextMenu => vec![EF::Hover(H::RightMouseDown)],
2374
2375        // Keyboard events
2376        E::KeyDown => vec![EF::Focus(F::VirtualKeyDown)],
2377        E::KeyUp => vec![EF::Focus(F::VirtualKeyUp)],
2378        E::KeyPress => vec![EF::Focus(F::TextInput)],
2379
2380        // IME Composition events
2381        E::CompositionStart => vec![EF::Hover(H::CompositionStart), EF::Focus(F::CompositionStart)],
2382        E::CompositionUpdate => vec![EF::Hover(H::CompositionUpdate), EF::Focus(F::CompositionUpdate)],
2383        E::CompositionEnd => vec![EF::Hover(H::CompositionEnd), EF::Focus(F::CompositionEnd)],
2384
2385        // Focus events
2386        E::Focus => vec![EF::Focus(F::FocusReceived)],
2387        E::Blur => vec![EF::Focus(F::FocusLost)],
2388        E::FocusIn => vec![EF::Hover(H::FocusIn), EF::Focus(F::FocusIn)],
2389        E::FocusOut => vec![EF::Hover(H::FocusOut), EF::Focus(F::FocusOut)],
2390
2391        // Input events
2392        E::Input | E::Change => vec![EF::Focus(F::TextInput)],
2393
2394        // Scroll events
2395        E::Scroll | E::ScrollStart | E::ScrollEnd => vec![EF::Hover(H::Scroll)],
2396
2397        // Drag events
2398        E::DragStart => vec![EF::Hover(H::DragStart), EF::Window(W::DragStart)],
2399        E::Drag => vec![EF::Hover(H::Drag), EF::Window(W::Drag)],
2400        E::DragEnd => vec![EF::Hover(H::DragEnd), EF::Window(W::DragEnd)],
2401        E::DragEnter => vec![EF::Hover(H::DragEnter), EF::Window(W::DragEnter)],
2402        E::DragOver => vec![EF::Hover(H::DragOver), EF::Window(W::DragOver)],
2403        E::DragLeave => vec![EF::Hover(H::DragLeave), EF::Window(W::DragLeave)],
2404        E::Drop => vec![EF::Hover(H::Drop), EF::Window(W::Drop)],
2405
2406        // Touch events
2407        E::TouchStart => vec![EF::Hover(H::TouchStart)],
2408        E::TouchMove => vec![EF::Hover(H::TouchMove)],
2409        E::TouchEnd => vec![EF::Hover(H::TouchEnd)],
2410        E::TouchCancel => vec![EF::Hover(H::TouchCancel)],
2411
2412        // Window events
2413        E::WindowResize => vec![EF::Window(W::Resized)],
2414        E::WindowMove => vec![EF::Window(W::Moved)],
2415        E::WindowClose => vec![EF::Window(W::CloseRequested)],
2416        E::WindowFocusIn => vec![EF::Window(W::WindowFocusReceived)],
2417        E::WindowFocusOut => vec![EF::Window(W::WindowFocusLost)],
2418        E::ThemeChange => vec![EF::Window(W::ThemeChanged)],
2419        E::WindowDpiChanged => vec![EF::Window(W::DpiChanged)],
2420        E::WindowMonitorChanged => vec![EF::Window(W::MonitorChanged)],
2421
2422        // Application events
2423        E::MonitorConnected => vec![EF::Application(ApplicationEventFilter::MonitorConnected)],
2424        E::MonitorDisconnected => vec![EF::Application(ApplicationEventFilter::MonitorDisconnected)],
2425
2426        // File events
2427        E::FileHover => vec![EF::Hover(H::HoveredFile)],
2428        E::FileDrop => vec![EF::Hover(H::DroppedFile)],
2429        E::FileHoverCancel => vec![EF::Hover(H::HoveredFileCancelled)],
2430
2431        // Unsupported events
2432        _ => vec![],
2433    }
2434}
2435
2436
2437
2438// Internal System Event Processing
2439
2440/// Result of pre-callback internal event filtering
2441#[derive(Debug, Clone, PartialEq)]
2442pub struct PreCallbackFilterResult {
2443    /// Internal system events to process BEFORE user callbacks
2444    pub internal_events: Vec<PreCallbackSystemEvent>,
2445    /// Regular events that will be passed to user callbacks
2446    pub user_events: Vec<SyntheticEvent>,
2447}
2448
2449/// Mouse button state for drag tracking
2450#[derive(Debug, Clone, Copy, PartialEq, Eq)]
2451pub struct MouseButtonState {
2452    pub left_down: bool,
2453    pub right_down: bool,
2454    pub middle_down: bool,
2455}
2456
2457/// System event to process AFTER user callbacks
2458#[derive(Debug, Clone, PartialEq, Eq)]
2459pub enum PostCallbackSystemEvent {
2460    /// Apply text input to focused contenteditable element
2461    ApplyTextInput,
2462    /// Focus changed during callbacks
2463    FocusChanged,
2464    /// Apply text changeset (separate creation from application)
2465    ApplyTextChangeset,
2466    /// Scroll cursor/selection into view
2467    ScrollIntoView,
2468    /// Start auto-scroll timer for drag-to-scroll
2469    StartAutoScrollTimer,
2470    /// Cancel auto-scroll timer
2471    CancelAutoScrollTimer,
2472}
2473
2474/// Internal system event for pre-callback processing
2475#[derive(Debug, Clone, PartialEq)]
2476pub enum PreCallbackSystemEvent {
2477    /// Single/double/triple click for text selection
2478    TextClick {
2479        target: DomNodeId,
2480        position: LogicalPosition,
2481        click_count: u8,
2482        timestamp: Instant,
2483    },
2484    /// Mouse drag for selection extension
2485    TextDragSelection {
2486        target: DomNodeId,
2487        start_position: LogicalPosition,
2488        current_position: LogicalPosition,
2489        is_dragging: bool,
2490    },
2491    /// Arrow key navigation with optional selection extension
2492    ArrowKeyNavigation {
2493        target: DomNodeId,
2494        direction: ArrowDirection,
2495        extend_selection: bool, // Shift key held
2496        word_jump: bool,        // Ctrl key held
2497    },
2498    /// Keyboard shortcut (Ctrl+C/X/A)
2499    KeyboardShortcut {
2500        target: DomNodeId,
2501        shortcut: KeyboardShortcut,
2502    },
2503    /// Delete currently selected text (Backspace/Delete key)
2504    DeleteSelection {
2505        target: DomNodeId,
2506        forward: bool, // true = Delete key (forward), false = Backspace (backward)
2507    },
2508}
2509
2510/// Arrow key directions
2511#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
2512pub enum ArrowDirection {
2513    Left,
2514    Right,
2515    Up,
2516    Down,
2517}
2518
2519/// Keyboard shortcuts for text editing
2520#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
2521pub enum KeyboardShortcut {
2522    Copy,      // Ctrl+C
2523    Cut,       // Ctrl+X
2524    Paste,     // Ctrl+V
2525    SelectAll, // Ctrl+A
2526    Undo,      // Ctrl+Z
2527    Redo,      // Ctrl+Y or Ctrl+Shift+Z
2528}
2529
2530/// Result of post-callback internal event filtering  
2531#[derive(Debug, Clone, PartialEq)]
2532pub struct PostCallbackFilterResult {
2533    /// System events to process AFTER user callbacks
2534    pub system_events: Vec<PostCallbackSystemEvent>,
2535}
2536
2537/// Pre-callback filter: Extract internal system events from synthetic events.
2538///
2539/// Separates internal system events (text selection, shortcuts) from user-facing events.
2540pub fn pre_callback_filter_internal_events<SM, FM>(
2541    events: &[SyntheticEvent],
2542    hit_test: Option<&FullHitTest>,
2543    keyboard_state: &crate::window::KeyboardState,
2544    mouse_state: &crate::window::MouseState,
2545    selection_manager: &SM,
2546    focus_manager: &FM,
2547) -> PreCallbackFilterResult
2548where
2549    SM: SelectionManagerQuery,
2550    FM: FocusManagerQuery,
2551{
2552    let ctx = FilterContext {
2553        hit_test,
2554        keyboard_state,
2555        mouse_state,
2556        click_count: selection_manager.get_click_count(),
2557        focused_node: focus_manager.get_focused_node_id(),
2558        drag_start_position: selection_manager.get_drag_start_position(),
2559        selection_manager,
2560    };
2561
2562    let (internal_events, user_events) = events.iter().fold(
2563        (Vec::new(), Vec::new()),
2564        |(mut internal, mut user), event| {
2565            match process_event_for_internal(&ctx, event) {
2566                Some(InternalEventAction::AddAndSkip(evt)) => {
2567                    internal.push(evt);
2568                }
2569                Some(InternalEventAction::AddAndPass(evt)) => {
2570                    internal.push(evt);
2571                    user.push(event.clone());
2572                }
2573                None => {
2574                    user.push(event.clone());
2575                }
2576            }
2577            (internal, user)
2578        },
2579    );
2580
2581    PreCallbackFilterResult {
2582        internal_events,
2583        user_events,
2584    }
2585}
2586
2587/// Context for filtering internal events
2588struct FilterContext<'a, SM> {
2589    hit_test: Option<&'a FullHitTest>,
2590    keyboard_state: &'a crate::window::KeyboardState,
2591    mouse_state: &'a crate::window::MouseState,
2592    click_count: u8,
2593    focused_node: Option<DomNodeId>,
2594    drag_start_position: Option<LogicalPosition>,
2595    selection_manager: &'a SM,
2596}
2597
2598/// Process a single event and determine if it generates an internal event
2599fn process_event_for_internal<SM: SelectionManagerQuery>(
2600    ctx: &FilterContext<'_, SM>,
2601    event: &SyntheticEvent,
2602) -> Option<InternalEventAction> {
2603    match event.event_type {
2604        EventType::MouseDown => handle_mouse_down(event, ctx.hit_test, ctx.click_count, ctx.mouse_state),
2605        EventType::MouseOver => handle_mouse_over(
2606            event,
2607            ctx.hit_test,
2608            ctx.mouse_state,
2609            ctx.drag_start_position,
2610        ),
2611        EventType::KeyDown => handle_key_down(
2612            event,
2613            ctx.keyboard_state,
2614            ctx.selection_manager,
2615            ctx.focused_node,
2616        ),
2617        _ => None,
2618    }
2619}
2620
2621/// Action to take after processing an event for internal system events
2622enum InternalEventAction {
2623    /// Add internal event and skip passing to user callbacks
2624    AddAndSkip(PreCallbackSystemEvent),
2625    /// Add internal event but also pass to user callbacks
2626    AddAndPass(PreCallbackSystemEvent),
2627}
2628
2629/// Extract first hovered node from hit test
2630fn get_first_hovered_node(hit_test: Option<&FullHitTest>) -> Option<DomNodeId> {
2631    let ht = hit_test?;
2632    let (dom_id, hit_data) = ht.hovered_nodes.iter().next()?;
2633    let node_id = hit_data.regular_hit_test_nodes.keys().next()?;
2634    Some(DomNodeId {
2635        dom: *dom_id,
2636        node: NodeHierarchyItemId::from_crate_internal(Some(*node_id)),
2637    })
2638}
2639
2640/// Extract mouse position from event data, falling back to mouse_state if not available
2641fn get_mouse_position_with_fallback(
2642    event: &SyntheticEvent,
2643    mouse_state: &crate::window::MouseState,
2644) -> LogicalPosition {
2645    match &event.data {
2646        EventData::Mouse(mouse_data) => mouse_data.position,
2647        _ => {
2648            // Fallback: use current cursor position from mouse_state
2649            // This handles synthetic events from debug API and automation
2650            // where EventData may not contain the mouse position
2651            mouse_state.cursor_position.get_position().unwrap_or(LogicalPosition::zero())
2652        }
2653    }
2654}
2655
2656/// Handle MouseDown event - detect text selection clicks
2657fn handle_mouse_down(
2658    event: &SyntheticEvent,
2659    hit_test: Option<&FullHitTest>,
2660    click_count: u8,
2661    mouse_state: &crate::window::MouseState,
2662) -> Option<InternalEventAction> {
2663    // Note: click_count == 0 means this is a new click sequence (first click)
2664    // The actual click count will be determined in process_mouse_click_for_selection
2665    // We use 1 here as a placeholder for new clicks
2666    let effective_click_count = if click_count == 0 { 1 } else { click_count };
2667    
2668    if effective_click_count > 3 {
2669        return None;
2670    }
2671
2672    let target = get_first_hovered_node(hit_test)?;
2673    let position = get_mouse_position_with_fallback(event, mouse_state);
2674
2675    Some(InternalEventAction::AddAndPass(
2676        PreCallbackSystemEvent::TextClick {
2677            target,
2678            position,
2679            click_count: effective_click_count,
2680            timestamp: event.timestamp.clone(),
2681        },
2682    ))
2683}
2684
2685/// Handle MouseOver event - detect drag selection
2686fn handle_mouse_over(
2687    event: &SyntheticEvent,
2688    hit_test: Option<&FullHitTest>,
2689    mouse_state: &crate::window::MouseState,
2690    drag_start_position: Option<LogicalPosition>,
2691) -> Option<InternalEventAction> {
2692    if !mouse_state.left_down {
2693        return None;
2694    }
2695
2696    let start_position = drag_start_position?;
2697
2698    let target = get_first_hovered_node(hit_test)?;
2699    let current_position = get_mouse_position_with_fallback(event, mouse_state);
2700
2701    Some(InternalEventAction::AddAndPass(
2702        PreCallbackSystemEvent::TextDragSelection {
2703            target,
2704            start_position,
2705            current_position,
2706            is_dragging: true,
2707        },
2708    ))
2709}
2710
2711/// Handle KeyDown event - detect shortcuts, arrow keys, and delete keys
2712fn handle_key_down<SM: SelectionManagerQuery>(
2713    event: &SyntheticEvent,
2714    keyboard_state: &crate::window::KeyboardState,
2715    selection_manager: &SM,
2716    focused_node: Option<DomNodeId>,
2717) -> Option<InternalEventAction> {
2718    use crate::window::VirtualKeyCode;
2719
2720    let target = focused_node?;
2721    let EventData::Keyboard(_) = &event.data else {
2722        return None;
2723    };
2724
2725    let ctrl = keyboard_state.ctrl_down();
2726    let shift = keyboard_state.shift_down();
2727    let vk = keyboard_state.current_virtual_keycode.as_ref()?;
2728
2729    // Check keyboard shortcuts (Ctrl+key)
2730    if ctrl {
2731        let shortcut = match vk {
2732            VirtualKeyCode::C => Some(KeyboardShortcut::Copy),
2733            VirtualKeyCode::X => Some(KeyboardShortcut::Cut),
2734            VirtualKeyCode::V => Some(KeyboardShortcut::Paste),
2735            VirtualKeyCode::A => Some(KeyboardShortcut::SelectAll),
2736            VirtualKeyCode::Z if !shift => Some(KeyboardShortcut::Undo),
2737            VirtualKeyCode::Z if shift => Some(KeyboardShortcut::Redo),
2738            VirtualKeyCode::Y => Some(KeyboardShortcut::Redo),
2739            _ => None,
2740        };
2741        if let Some(shortcut) = shortcut {
2742            return Some(InternalEventAction::AddAndSkip(
2743                PreCallbackSystemEvent::KeyboardShortcut { target, shortcut },
2744            ));
2745        }
2746    }
2747
2748    // Check arrow key navigation
2749    let direction = match vk {
2750        VirtualKeyCode::Left => Some(ArrowDirection::Left),
2751        VirtualKeyCode::Up => Some(ArrowDirection::Up),
2752        VirtualKeyCode::Right => Some(ArrowDirection::Right),
2753        VirtualKeyCode::Down => Some(ArrowDirection::Down),
2754        _ => None,
2755    };
2756    if let Some(direction) = direction {
2757        return Some(InternalEventAction::AddAndSkip(
2758            PreCallbackSystemEvent::ArrowKeyNavigation {
2759                target,
2760                direction,
2761                extend_selection: shift,
2762                word_jump: ctrl,
2763            },
2764        ));
2765    }
2766
2767    // Check delete keys (only when selection exists)
2768    if !selection_manager.has_selection() {
2769        return None;
2770    }
2771
2772    let forward = match vk {
2773        VirtualKeyCode::Back => Some(false),
2774        VirtualKeyCode::Delete => Some(true),
2775        _ => None,
2776    }?;
2777
2778    Some(InternalEventAction::AddAndSkip(
2779        PreCallbackSystemEvent::DeleteSelection { target, forward },
2780    ))
2781}
2782
2783/// Trait for querying selection manager state.
2784///
2785/// This allows `pre_callback_filter_internal_events` to query manager state
2786/// without depending on the concrete `SelectionManager` type from layout crate.
2787pub trait SelectionManagerQuery {
2788    /// Get the current click count (1 = single, 2 = double, 3 = triple)
2789    fn get_click_count(&self) -> u8;
2790
2791    /// Get the drag start position if a drag is in progress
2792    fn get_drag_start_position(&self) -> Option<LogicalPosition>;
2793
2794    /// Check if any selection exists (click selection or drag selection)
2795    fn has_selection(&self) -> bool;
2796}
2797
2798/// Trait for querying focus manager state.
2799///
2800/// This allows `pre_callback_filter_internal_events` to query manager state
2801/// without depending on the concrete `FocusManager` type from layout crate.
2802pub trait FocusManagerQuery {
2803    /// Get the currently focused node ID
2804    fn get_focused_node_id(&self) -> Option<DomNodeId>;
2805}
2806
2807/// Post-callback filter: Analyze applied changes and generate system events
2808pub fn post_callback_filter_internal_events(
2809    prevent_default: bool,
2810    internal_events: &[PreCallbackSystemEvent],
2811    old_focus: Option<DomNodeId>,
2812    new_focus: Option<DomNodeId>,
2813) -> PostCallbackFilterResult {
2814    if prevent_default {
2815        let focus_event = (old_focus != new_focus).then_some(PostCallbackSystemEvent::FocusChanged);
2816        return PostCallbackFilterResult {
2817            system_events: focus_event.into_iter().collect(),
2818        };
2819    }
2820
2821    let event_actions = internal_events
2822        .iter()
2823        .filter_map(internal_event_to_system_event);
2824
2825    let focus_event = (old_focus != new_focus).then_some(PostCallbackSystemEvent::FocusChanged);
2826
2827    let system_events = core::iter::once(PostCallbackSystemEvent::ApplyTextInput)
2828        .chain(event_actions)
2829        .chain(focus_event)
2830        .collect();
2831
2832    PostCallbackFilterResult { system_events }
2833}
2834
2835/// Convert internal event to post-callback system event
2836fn internal_event_to_system_event(
2837    event: &PreCallbackSystemEvent,
2838) -> Option<PostCallbackSystemEvent> {
2839    use PostCallbackSystemEvent::*;
2840    use PreCallbackSystemEvent::*;
2841
2842    match event {
2843        TextClick { .. } | ArrowKeyNavigation { .. } | DeleteSelection { .. } => {
2844            Some(ScrollIntoView)
2845        }
2846        TextDragSelection { is_dragging, .. } => Some(if *is_dragging {
2847            StartAutoScrollTimer
2848        } else {
2849            CancelAutoScrollTimer
2850        }),
2851        KeyboardShortcut { shortcut, .. } => shortcut_to_system_event(*shortcut),
2852    }
2853}
2854
2855/// Convert keyboard shortcut to system event (if any)
2856fn shortcut_to_system_event(shortcut: KeyboardShortcut) -> Option<PostCallbackSystemEvent> {
2857    use KeyboardShortcut::*;
2858    match shortcut {
2859        Cut | Paste | Undo | Redo => Some(PostCallbackSystemEvent::ScrollIntoView),
2860        Copy | SelectAll => None,
2861    }
2862}
2863
2864#[cfg(test)]
2865mod tests {
2866    //! Unit tests for the Phase 3.5 event system
2867    //!
2868    //! Tests cover:
2869    //! - Event type creation
2870    //! - DOM path traversal
2871    //! - Event propagation (capture/target/bubble)
2872    //! - Event filter matching
2873    //! - Lifecycle event detection
2874
2875    use std::collections::BTreeMap;
2876
2877    use crate::{
2878        dom::{DomId, DomNodeId},
2879        events::*,
2880        geom::{LogicalPosition, LogicalRect, LogicalSize},
2881        id::{Node, NodeHierarchy, NodeId},
2882        styled_dom::NodeHierarchyItemId,
2883        task::{Instant, SystemTick},
2884    };
2885
2886    // Helper: Create a test Instant
2887    fn test_instant() -> Instant {
2888        Instant::Tick(SystemTick::new(0))
2889    }
2890
2891    // Helper: Create a simple 3-node tree (root -> child1 -> grandchild)
2892    fn create_test_hierarchy() -> NodeHierarchy {
2893        let nodes = vec![
2894            Node {
2895                parent: None,
2896                previous_sibling: None,
2897                next_sibling: None,
2898                last_child: Some(NodeId::new(1)),
2899            },
2900            Node {
2901                parent: Some(NodeId::new(0)),
2902                previous_sibling: None,
2903                next_sibling: None,
2904                last_child: Some(NodeId::new(2)),
2905            },
2906            Node {
2907                parent: Some(NodeId::new(1)),
2908                previous_sibling: None,
2909                next_sibling: None,
2910                last_child: None,
2911            },
2912        ];
2913        NodeHierarchy::new(nodes)
2914    }
2915
2916    #[test]
2917    fn test_event_source_enum() {
2918        // Test that EventSource variants can be created
2919        let _user = EventSource::User;
2920        let _programmatic = EventSource::Programmatic;
2921        let _synthetic = EventSource::Synthetic;
2922        let _lifecycle = EventSource::Lifecycle;
2923    }
2924
2925    #[test]
2926    fn test_event_phase_enum() {
2927        // Test that EventPhase variants can be created
2928        let _capture = EventPhase::Capture;
2929        let _target = EventPhase::Target;
2930        let _bubble = EventPhase::Bubble;
2931
2932        // Test default
2933        assert_eq!(EventPhase::default(), EventPhase::Bubble);
2934    }
2935
2936    #[test]
2937    fn test_synthetic_event_creation() {
2938        let dom_id = DomId { inner: 1 };
2939        let node_id = NodeHierarchyItemId::from_crate_internal(Some(NodeId::new(0)));
2940        let target = DomNodeId {
2941            dom: dom_id,
2942            node: node_id,
2943        };
2944
2945        let event = SyntheticEvent::new(
2946            EventType::Click,
2947            EventSource::User,
2948            target,
2949            test_instant(),
2950            EventData::None,
2951        );
2952
2953        assert_eq!(event.event_type, EventType::Click);
2954        assert_eq!(event.source, EventSource::User);
2955        assert_eq!(event.phase, EventPhase::Target);
2956        assert_eq!(event.target, target);
2957        assert_eq!(event.current_target, target);
2958        assert!(!event.stopped);
2959        assert!(!event.stopped_immediate);
2960        assert!(!event.prevented_default);
2961    }
2962
2963    #[test]
2964    fn test_stop_propagation() {
2965        let dom_id = DomId { inner: 1 };
2966        let node_id = NodeHierarchyItemId::from_crate_internal(Some(NodeId::new(0)));
2967        let target = DomNodeId {
2968            dom: dom_id,
2969            node: node_id,
2970        };
2971
2972        let mut event = SyntheticEvent::new(
2973            EventType::Click,
2974            EventSource::User,
2975            target,
2976            test_instant(),
2977            EventData::None,
2978        );
2979
2980        assert!(!event.is_propagation_stopped());
2981
2982        event.stop_propagation();
2983
2984        assert!(event.is_propagation_stopped());
2985        assert!(!event.is_immediate_propagation_stopped());
2986    }
2987
2988    #[test]
2989    fn test_stop_immediate_propagation() {
2990        let dom_id = DomId { inner: 1 };
2991        let node_id = NodeHierarchyItemId::from_crate_internal(Some(NodeId::new(0)));
2992        let target = DomNodeId {
2993            dom: dom_id,
2994            node: node_id,
2995        };
2996
2997        let mut event = SyntheticEvent::new(
2998            EventType::Click,
2999            EventSource::User,
3000            target,
3001            test_instant(),
3002            EventData::None,
3003        );
3004
3005        event.stop_immediate_propagation();
3006
3007        assert!(event.is_propagation_stopped());
3008        assert!(event.is_immediate_propagation_stopped());
3009    }
3010
3011    #[test]
3012    fn test_prevent_default() {
3013        let dom_id = DomId { inner: 1 };
3014        let node_id = NodeHierarchyItemId::from_crate_internal(Some(NodeId::new(0)));
3015        let target = DomNodeId {
3016            dom: dom_id,
3017            node: node_id,
3018        };
3019
3020        let mut event = SyntheticEvent::new(
3021            EventType::Click,
3022            EventSource::User,
3023            target,
3024            test_instant(),
3025            EventData::None,
3026        );
3027
3028        assert!(!event.is_default_prevented());
3029
3030        event.prevent_default();
3031
3032        assert!(event.is_default_prevented());
3033    }
3034
3035    #[test]
3036    fn test_get_dom_path_single_node() {
3037        let hierarchy = NodeHierarchy::new(vec![Node {
3038            parent: None,
3039            previous_sibling: None,
3040            next_sibling: None,
3041            last_child: None,
3042        }]);
3043
3044        let target = NodeHierarchyItemId::from_crate_internal(Some(NodeId::new(0)));
3045        let path = get_dom_path(&hierarchy, target);
3046
3047        assert_eq!(path.len(), 1);
3048        assert_eq!(path[0], NodeId::new(0));
3049    }
3050
3051    #[test]
3052    fn test_get_dom_path_three_nodes() {
3053        let hierarchy = create_test_hierarchy();
3054
3055        // Test path to grandchild (node 2)
3056        let target = NodeHierarchyItemId::from_crate_internal(Some(NodeId::new(2)));
3057        let path = get_dom_path(&hierarchy, target);
3058
3059        assert_eq!(path.len(), 3);
3060        assert_eq!(path[0], NodeId::new(0)); // root
3061        assert_eq!(path[1], NodeId::new(1)); // child
3062        assert_eq!(path[2], NodeId::new(2)); // grandchild
3063    }
3064
3065    #[test]
3066    fn test_get_dom_path_middle_node() {
3067        let hierarchy = create_test_hierarchy();
3068
3069        // Test path to middle node (node 1)
3070        let target = NodeHierarchyItemId::from_crate_internal(Some(NodeId::new(1)));
3071        let path = get_dom_path(&hierarchy, target);
3072
3073        assert_eq!(path.len(), 2);
3074        assert_eq!(path[0], NodeId::new(0)); // root
3075        assert_eq!(path[1], NodeId::new(1)); // child
3076    }
3077
3078    #[test]
3079    fn test_propagate_event_empty_callbacks() {
3080        let hierarchy = create_test_hierarchy();
3081        let dom_id = DomId { inner: 1 };
3082        let target_node = NodeHierarchyItemId::from_crate_internal(Some(NodeId::new(2)));
3083        let target = DomNodeId {
3084            dom: dom_id,
3085            node: target_node,
3086        };
3087
3088        let mut event = SyntheticEvent::new(
3089            EventType::Click,
3090            EventSource::User,
3091            target,
3092            test_instant(),
3093            EventData::None,
3094        );
3095
3096        let callbacks: BTreeMap<NodeId, Vec<EventFilter>> = BTreeMap::new();
3097        let result = propagate_event(&mut event, &hierarchy, &callbacks);
3098
3099        // No callbacks, so nothing should be invoked
3100        assert_eq!(result.callbacks_to_invoke.len(), 0);
3101        assert!(!result.default_prevented);
3102    }
3103
3104    #[test]
3105    fn test_mouse_event_data_creation() {
3106        let mouse_data = MouseEventData {
3107            position: LogicalPosition { x: 100.0, y: 200.0 },
3108            button: MouseButton::Left,
3109            buttons: 1,
3110            modifiers: KeyModifiers::new(),
3111        };
3112
3113        assert_eq!(mouse_data.position.x, 100.0);
3114        assert_eq!(mouse_data.position.y, 200.0);
3115        assert_eq!(mouse_data.button, MouseButton::Left);
3116    }
3117
3118    #[test]
3119    fn test_key_modifiers() {
3120        let modifiers = KeyModifiers::new().with_shift().with_ctrl();
3121
3122        assert!(modifiers.shift);
3123        assert!(modifiers.ctrl);
3124        assert!(!modifiers.alt);
3125        assert!(!modifiers.meta);
3126        assert!(!modifiers.is_empty());
3127
3128        let empty = KeyModifiers::new();
3129        assert!(empty.is_empty());
3130    }
3131
3132    #[test]
3133    fn test_lifecycle_event_mount() {
3134        let dom_id = DomId { inner: 1 };
3135        let old_hierarchy = None;
3136        let new_hierarchy = create_test_hierarchy();
3137        let old_layout = None;
3138        let new_layout = {
3139            let mut map = BTreeMap::new();
3140            map.insert(
3141                NodeId::new(0),
3142                LogicalRect {
3143                    origin: LogicalPosition { x: 0.0, y: 0.0 },
3144                    size: LogicalSize {
3145                        width: 100.0,
3146                        height: 100.0,
3147                    },
3148                },
3149            );
3150            map.insert(
3151                NodeId::new(1),
3152                LogicalRect {
3153                    origin: LogicalPosition { x: 10.0, y: 10.0 },
3154                    size: LogicalSize {
3155                        width: 80.0,
3156                        height: 80.0,
3157                    },
3158                },
3159            );
3160            map.insert(
3161                NodeId::new(2),
3162                LogicalRect {
3163                    origin: LogicalPosition { x: 20.0, y: 20.0 },
3164                    size: LogicalSize {
3165                        width: 60.0,
3166                        height: 60.0,
3167                    },
3168                },
3169            );
3170            Some(map)
3171        };
3172
3173        let events = detect_lifecycle_events(
3174            dom_id,
3175            dom_id,
3176            old_hierarchy,
3177            Some(&new_hierarchy),
3178            old_layout.as_ref(),
3179            new_layout.as_ref(),
3180            test_instant(),
3181        );
3182
3183        // All 3 nodes should have Mount events
3184        assert_eq!(events.len(), 3);
3185
3186        for event in &events {
3187            assert_eq!(event.event_type, EventType::Mount);
3188            assert_eq!(event.source, EventSource::Lifecycle);
3189
3190            if let EventData::Lifecycle(data) = &event.data {
3191                assert_eq!(data.reason, LifecycleReason::InitialMount);
3192                assert!(data.previous_bounds.is_none());
3193            } else {
3194                panic!("Expected Lifecycle event data");
3195            }
3196        }
3197    }
3198
3199    #[test]
3200    fn test_lifecycle_event_unmount() {
3201        let dom_id = DomId { inner: 1 };
3202        let old_hierarchy = create_test_hierarchy();
3203        let new_hierarchy = None;
3204        let old_layout = {
3205            let mut map = BTreeMap::new();
3206            map.insert(
3207                NodeId::new(0),
3208                LogicalRect {
3209                    origin: LogicalPosition { x: 0.0, y: 0.0 },
3210                    size: LogicalSize {
3211                        width: 100.0,
3212                        height: 100.0,
3213                    },
3214                },
3215            );
3216            Some(map)
3217        };
3218        let new_layout = None;
3219
3220        let events = detect_lifecycle_events(
3221            dom_id,
3222            dom_id,
3223            Some(&old_hierarchy),
3224            new_hierarchy,
3225            old_layout.as_ref(),
3226            new_layout,
3227            test_instant(),
3228        );
3229
3230        // All 3 nodes should have Unmount events
3231        assert_eq!(events.len(), 3);
3232
3233        for event in &events {
3234            assert_eq!(event.event_type, EventType::Unmount);
3235            assert_eq!(event.source, EventSource::Lifecycle);
3236        }
3237    }
3238
3239    #[test]
3240    fn test_lifecycle_event_resize() {
3241        let dom_id = DomId { inner: 1 };
3242        let hierarchy = create_test_hierarchy();
3243
3244        let old_layout = {
3245            let mut map = BTreeMap::new();
3246            map.insert(
3247                NodeId::new(0),
3248                LogicalRect {
3249                    origin: LogicalPosition { x: 0.0, y: 0.0 },
3250                    size: LogicalSize {
3251                        width: 100.0,
3252                        height: 100.0,
3253                    },
3254                },
3255            );
3256            Some(map)
3257        };
3258
3259        let new_layout = {
3260            let mut map = BTreeMap::new();
3261            map.insert(
3262                NodeId::new(0),
3263                LogicalRect {
3264                    origin: LogicalPosition { x: 0.0, y: 0.0 },
3265                    size: LogicalSize {
3266                        width: 200.0,
3267                        height: 100.0,
3268                    }, // Width changed
3269                },
3270            );
3271            Some(map)
3272        };
3273
3274        let events = detect_lifecycle_events(
3275            dom_id,
3276            dom_id,
3277            Some(&hierarchy),
3278            Some(&hierarchy),
3279            old_layout.as_ref(),
3280            new_layout.as_ref(),
3281            test_instant(),
3282        );
3283
3284        // Should have 1 Resize event
3285        assert_eq!(events.len(), 1);
3286        assert_eq!(events[0].event_type, EventType::Resize);
3287        assert_eq!(events[0].source, EventSource::Lifecycle);
3288
3289        if let EventData::Lifecycle(data) = &events[0].data {
3290            assert_eq!(data.reason, LifecycleReason::Resize);
3291            assert!(data.previous_bounds.is_some());
3292            assert_eq!(data.current_bounds.size.width, 200.0);
3293        } else {
3294            panic!("Expected Lifecycle event data");
3295        }
3296    }
3297
3298    #[test]
3299    fn test_event_filter_hover_match() {
3300        let dom_id = DomId { inner: 1 };
3301        let node_id = NodeHierarchyItemId::from_crate_internal(Some(NodeId::new(0)));
3302        let target = DomNodeId {
3303            dom: dom_id,
3304            node: node_id,
3305        };
3306
3307        let _event = SyntheticEvent::new(
3308            EventType::MouseDown,
3309            EventSource::User,
3310            target,
3311            test_instant(),
3312            EventData::Mouse(MouseEventData {
3313                position: LogicalPosition { x: 0.0, y: 0.0 },
3314                button: MouseButton::Left,
3315                buttons: 1,
3316                modifiers: KeyModifiers::new(),
3317            }),
3318        );
3319
3320        // This is tested internally via matches_hover_filter
3321        // We can't test it directly without making the function public
3322        // but it's tested indirectly through propagate_event
3323    }
3324}