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