Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions pyi_hashes.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"reflex/__init__.pyi": "c69e4120c505941af8087b9c18df6121",
"reflex/__init__.pyi": "8f9482b205f5a33a59f748006ded637b",
"reflex/components/__init__.pyi": "ac05995852baa81062ba3d18fbc489fb",
"reflex/components/base/__init__.pyi": "16e47bf19e0d62835a605baa3d039c5a",
"reflex/components/base/app_wrap.pyi": "ae600e2cc9d70f2ce613bdd6c1da3b16",
Expand All @@ -11,7 +11,7 @@
"reflex/components/base/meta.pyi": "0445c66fbc32671c795640ef1a4827e9",
"reflex/components/base/script.pyi": "43a0e21f257b10d2c76ed359284a9d80",
"reflex/components/base/strict_mode.pyi": "d169c575d676c73edc6a3f593badfd1f",
"reflex/components/core/__init__.pyi": "6419485660830fe0af9c9c5715a4ed03",
"reflex/components/core/__init__.pyi": "007170b97e58bdf28b2aee381d91c0c7",
"reflex/components/core/auto_scroll.pyi": "c628ed503c7bfcee0dd05cf48d5b763d",
"reflex/components/core/banner.pyi": "407352aa1833b80b21d30647ec7717d8",
"reflex/components/core/client_side_routing.pyi": "c3d38a1de89cfcd76735a1559e99ed05",
Expand All @@ -21,6 +21,7 @@
"reflex/components/core/html.pyi": "faf9bb353ef4784e7f17ac97c93ef697",
"reflex/components/core/sticky.pyi": "cdf17e6cd287e7300acd25669701d117",
"reflex/components/core/upload.pyi": "f9be9b74d97d841b53b963d8704d5809",
"reflex/components/core/window_events.pyi": "04be3d73886d12774498004b712c136e",
"reflex/components/datadisplay/__init__.pyi": "52755871369acbfd3a96b46b9a11d32e",
"reflex/components/datadisplay/code.pyi": "3787ca724cae7b29d57ea03f981b8c22",
"reflex/components/datadisplay/dataeditor.pyi": "23f777b8a46eff2afd95035dd5fc51a7",
Expand Down
1 change: 1 addition & 0 deletions reflex/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,7 @@
"upload",
],
"components.core.auto_scroll": ["auto_scroll"],
"components.core.window_events": ["window_event_listener"],
}

COMPONENTS_BASE_MAPPING: dict = {
Expand Down
1 change: 1 addition & 0 deletions reflex/components/core/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
"selected_files",
],
"auto_scroll": ["auto_scroll"],
"window_events": ["WindowEventListener", "window_event_listener"],
}

__getattr__, __dir__, __all__ = lazy_loader.attach(
Expand Down
104 changes: 104 additions & 0 deletions reflex/components/core/window_events.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
"""Window event listener component for Reflex."""

import reflex as rx
from reflex.components.base.fragment import Fragment
from reflex.constants.compiler import Hooks
from reflex.event import key_event, no_args_event_spec
from reflex.vars.base import Var, VarData
from reflex.vars.object import ObjectVar


def _on_resize_spec() -> tuple[Var[int], Var[int]]:
"""Args spec for the on_resize event trigger.

Returns:
A tuple containing window width and height variables.
"""
return (Var("window.innerWidth"), Var("window.innerHeight"))


def _on_scroll_spec() -> tuple[Var[float], Var[float]]:
"""Args spec for the on_scroll event trigger.

Returns:
A tuple containing window scroll X and Y position variables.
"""
return (Var("window.scrollX"), Var("window.scrollY"))


def _on_visibility_change_spec() -> tuple[Var[bool]]:
"""Args spec for the on_visibility_change event trigger.

Returns:
A tuple containing the document hidden state variable.
"""
return (Var("document.hidden"),)


def _on_storage_spec(e: ObjectVar) -> tuple[Var[str], Var[str], Var[str], Var[str]]:
"""Args spec for the on_storage event trigger.

Args:
e: The storage event.

Returns:
A tuple containing key, old value, new value, and URL variables.
"""
return (e.key.to(str), e.oldValue.to(str), e.newValue.to(str), e.url.to(str))


class WindowEventListener(Fragment):
"""A component that listens for window events."""

# Event handlers
on_resize: rx.EventHandler[_on_resize_spec]
on_scroll: rx.EventHandler[_on_scroll_spec]
on_focus: rx.EventHandler[no_args_event_spec]
on_blur: rx.EventHandler[no_args_event_spec]
on_visibility_change: rx.EventHandler[_on_visibility_change_spec]
on_before_unload: rx.EventHandler[no_args_event_spec]
on_key_down: rx.EventHandler[key_event]
on_popstate: rx.EventHandler[no_args_event_spec]
on_storage: rx.EventHandler[_on_storage_spec]

def _exclude_props(self) -> list[str]:
"""Exclude event handler props from being passed to Fragment.

Returns:
List of prop names to exclude from the Fragment.
"""
return [*super()._exclude_props(), *self.event_triggers.keys()]

def add_hooks(self) -> list[str | Var[str]]:
"""Add hooks to register window event listeners.

Returns:
The hooks to add to the component.
"""
hooks = []

for prop_name, event_trigger in self.event_triggers.items():
# Get JS event name: remove on_ prefix and underscores
event_name = prop_name.removeprefix("on_").replace("_", "")

hook_expr = f"""
useEffect(() => {{
if (typeof window === 'undefined') return;

window.addEventListener('{event_name}', {event_trigger});
return () => window.removeEventListener('{event_name}', {event_trigger});
}}, []);
"""
Comment thread
Lendemor marked this conversation as resolved.

hooks.append(
Var(
hook_expr,
_var_type="str",
_var_data=VarData(position=Hooks.HookPosition.POST_TRIGGER),
)
)

return hooks


window_event_listener = WindowEventListener.create
Loading