|
| 1 | +"""Window event listener component for Reflex.""" |
| 2 | + |
| 3 | +import reflex as rx |
| 4 | +from reflex.components.base.fragment import Fragment |
| 5 | +from reflex.constants.compiler import Hooks |
| 6 | +from reflex.event import key_event, no_args_event_spec |
| 7 | +from reflex.vars.base import Var, VarData |
| 8 | +from reflex.vars.object import ObjectVar |
| 9 | + |
| 10 | + |
| 11 | +def _on_resize_spec() -> tuple[Var[int], Var[int]]: |
| 12 | + """Args spec for the on_resize event trigger. |
| 13 | +
|
| 14 | + Returns: |
| 15 | + A tuple containing window width and height variables. |
| 16 | + """ |
| 17 | + return (Var("window.innerWidth"), Var("window.innerHeight")) |
| 18 | + |
| 19 | + |
| 20 | +def _on_scroll_spec() -> tuple[Var[float], Var[float]]: |
| 21 | + """Args spec for the on_scroll event trigger. |
| 22 | +
|
| 23 | + Returns: |
| 24 | + A tuple containing window scroll X and Y position variables. |
| 25 | + """ |
| 26 | + return (Var("window.scrollX"), Var("window.scrollY")) |
| 27 | + |
| 28 | + |
| 29 | +def _on_visibility_change_spec() -> tuple[Var[bool]]: |
| 30 | + """Args spec for the on_visibility_change event trigger. |
| 31 | +
|
| 32 | + Returns: |
| 33 | + A tuple containing the document hidden state variable. |
| 34 | + """ |
| 35 | + return (Var("document.hidden"),) |
| 36 | + |
| 37 | + |
| 38 | +def _on_storage_spec(e: ObjectVar) -> tuple[Var[str], Var[str], Var[str], Var[str]]: |
| 39 | + """Args spec for the on_storage event trigger. |
| 40 | +
|
| 41 | + Args: |
| 42 | + e: The storage event. |
| 43 | +
|
| 44 | + Returns: |
| 45 | + A tuple containing key, old value, new value, and URL variables. |
| 46 | + """ |
| 47 | + return (e.key.to(str), e.oldValue.to(str), e.newValue.to(str), e.url.to(str)) |
| 48 | + |
| 49 | + |
| 50 | +class WindowEventListener(Fragment): |
| 51 | + """A component that listens for window events.""" |
| 52 | + |
| 53 | + # Event handlers |
| 54 | + on_resize: rx.EventHandler[_on_resize_spec] |
| 55 | + on_scroll: rx.EventHandler[_on_scroll_spec] |
| 56 | + on_focus: rx.EventHandler[no_args_event_spec] |
| 57 | + on_blur: rx.EventHandler[no_args_event_spec] |
| 58 | + on_visibility_change: rx.EventHandler[_on_visibility_change_spec] |
| 59 | + on_before_unload: rx.EventHandler[no_args_event_spec] |
| 60 | + on_key_down: rx.EventHandler[key_event] |
| 61 | + on_popstate: rx.EventHandler[no_args_event_spec] |
| 62 | + on_storage: rx.EventHandler[_on_storage_spec] |
| 63 | + |
| 64 | + def _exclude_props(self) -> list[str]: |
| 65 | + """Exclude event handler props from being passed to Fragment. |
| 66 | +
|
| 67 | + Returns: |
| 68 | + List of prop names to exclude from the Fragment. |
| 69 | + """ |
| 70 | + return [*super()._exclude_props(), *self.event_triggers.keys()] |
| 71 | + |
| 72 | + def add_hooks(self) -> list[str | Var[str]]: |
| 73 | + """Add hooks to register window event listeners. |
| 74 | +
|
| 75 | + Returns: |
| 76 | + The hooks to add to the component. |
| 77 | + """ |
| 78 | + hooks = [] |
| 79 | + |
| 80 | + for prop_name, event_trigger in self.event_triggers.items(): |
| 81 | + # Get JS event name: remove on_ prefix and underscores |
| 82 | + event_name = prop_name.removeprefix("on_").replace("_", "") |
| 83 | + |
| 84 | + hook_expr = f""" |
| 85 | + useEffect(() => {{ |
| 86 | + if (typeof window === 'undefined') return; |
| 87 | +
|
| 88 | + window.addEventListener('{event_name}', {event_trigger}); |
| 89 | + return () => window.removeEventListener('{event_name}', {event_trigger}); |
| 90 | + }}, []); |
| 91 | + """ |
| 92 | + |
| 93 | + hooks.append( |
| 94 | + Var( |
| 95 | + hook_expr, |
| 96 | + _var_type="str", |
| 97 | + _var_data=VarData(position=Hooks.HookPosition.POST_TRIGGER), |
| 98 | + ) |
| 99 | + ) |
| 100 | + |
| 101 | + return hooks |
| 102 | + |
| 103 | + |
| 104 | +window_event_listener = WindowEventListener.create |
0 commit comments