浏览代码

Add type hinting to dataeditor events (#4210)

Khaleel Al-Adhami 6 月之前
父节点
当前提交
c103ab5e28
共有 29 个文件被更改,包括 342 次插入71 次删除
  1. 3 1
      reflex/components/base/error_boundary.py
  2. 1 1
      reflex/components/base/error_boundary.pyi
  3. 1 1
      reflex/components/core/clipboard.pyi
  4. 82 6
      reflex/components/datadisplay/dataeditor.py
  5. 66 12
      reflex/components/datadisplay/dataeditor.pyi
  6. 1 1
      reflex/components/moment/moment.pyi
  7. 2 2
      reflex/components/radix/primitives/drawer.pyi
  8. 1 1
      reflex/components/radix/themes/color_mode.pyi
  9. 1 1
      reflex/components/radix/themes/components/alert_dialog.pyi
  10. 3 3
      reflex/components/radix/themes/components/checkbox.pyi
  11. 1 1
      reflex/components/radix/themes/components/context_menu.pyi
  12. 2 2
      reflex/components/radix/themes/components/dialog.pyi
  13. 2 2
      reflex/components/radix/themes/components/dropdown_menu.pyi
  14. 2 2
      reflex/components/radix/themes/components/hover_card.pyi
  15. 1 1
      reflex/components/radix/themes/components/popover.pyi
  16. 1 1
      reflex/components/radix/themes/components/radio_cards.pyi
  17. 1 1
      reflex/components/radix/themes/components/radio_group.pyi
  18. 6 6
      reflex/components/radix/themes/components/select.pyi
  19. 1 1
      reflex/components/radix/themes/components/switch.pyi
  20. 2 2
      reflex/components/radix/themes/components/tabs.pyi
  21. 1 1
      reflex/components/radix/themes/components/tooltip.pyi
  22. 2 2
      reflex/components/react_player/audio.pyi
  23. 2 2
      reflex/components/react_player/react_player.pyi
  24. 2 2
      reflex/components/react_player/video.pyi
  25. 4 4
      reflex/components/suneditor/editor.pyi
  26. 73 9
      reflex/event.py
  27. 1 1
      reflex/experimental/layout.pyi
  28. 76 2
      reflex/utils/pyi_generator.py
  29. 1 0
      tests/integration/test_lifespan.py

+ 3 - 1
reflex/components/base/error_boundary.py

@@ -12,7 +12,9 @@ from reflex.state import FrontendEventExceptionState
 from reflex.vars.base import Var
 
 
-def on_error_spec(error: Var, info: Var[Dict[str, str]]) -> Tuple[Var[str], Var[str]]:
+def on_error_spec(
+    error: Var[Dict[str, str]], info: Var[Dict[str, str]]
+) -> Tuple[Var[str], Var[str]]:
     """The spec for the on_error event handler.
 
     Args:

+ 1 - 1
reflex/components/base/error_boundary.pyi

@@ -11,7 +11,7 @@ from reflex.style import Style
 from reflex.vars.base import Var
 
 def on_error_spec(
-    error: Var, info: Var[Dict[str, str]]
+    error: Var[Dict[str, str]], info: Var[Dict[str, str]]
 ) -> Tuple[Var[str], Var[str]]: ...
 
 class ErrorBoundary(Component):

+ 1 - 1
reflex/components/core/clipboard.pyi

@@ -40,7 +40,7 @@ class Clipboard(Fragment):
         on_mouse_out: Optional[EventType[[]]] = None,
         on_mouse_over: Optional[EventType[[]]] = None,
         on_mouse_up: Optional[EventType[[]]] = None,
-        on_paste: Optional[EventType] = None,
+        on_paste: Optional[EventType[list[tuple[str, str]]]] = None,
         on_scroll: Optional[EventType[[]]] = None,
         on_unmount: Optional[EventType[[]]] = None,
         **props,

+ 82 - 6
reflex/components/datadisplay/dataeditor.py

@@ -5,6 +5,8 @@ from __future__ import annotations
 from enum import Enum
 from typing import Any, Dict, List, Literal, Optional, Tuple, Union
 
+from typing_extensions import TypedDict
+
 from reflex.base import Base
 from reflex.components.component import Component, NoSSRComponent
 from reflex.components.literals import LiteralRowMarker
@@ -120,6 +122,78 @@ def on_edit_spec(pos, data: dict[str, Any]):
     return [pos, data]
 
 
+class Bounds(TypedDict):
+    """The bounds of the group header."""
+
+    x: int
+    y: int
+    width: int
+    height: int
+
+
+class CompatSelection(TypedDict):
+    """The selection."""
+
+    items: list
+
+
+class Rectangle(TypedDict):
+    """The bounds of the group header."""
+
+    x: int
+    y: int
+    width: int
+    height: int
+
+
+class GridSelectionCurrent(TypedDict):
+    """The current selection."""
+
+    cell: list[int]
+    range: Rectangle
+    rangeStack: list[Rectangle]
+
+
+class GridSelection(TypedDict):
+    """The grid selection."""
+
+    current: Optional[GridSelectionCurrent]
+    columns: CompatSelection
+    rows: CompatSelection
+
+
+class GroupHeaderClickedEventArgs(TypedDict):
+    """The arguments for the group header clicked event."""
+
+    kind: str
+    group: str
+    location: list[int]
+    bounds: Bounds
+    isEdge: bool
+    shiftKey: bool
+    ctrlKey: bool
+    metaKey: bool
+    isTouch: bool
+    localEventX: int
+    localEventY: int
+    button: int
+    buttons: int
+    scrollEdge: list[int]
+
+
+class GridCell(TypedDict):
+    """The grid cell."""
+
+    span: Optional[List[int]]
+
+
+class GridColumn(TypedDict):
+    """The grid column."""
+
+    title: str
+    group: Optional[str]
+
+
 class DataEditor(NoSSRComponent):
     """The DataEditor Component."""
 
@@ -238,10 +312,12 @@ class DataEditor(NoSSRComponent):
     on_group_header_clicked: EventHandler[on_edit_spec]
 
     # Fired when a group header is right-clicked.
-    on_group_header_context_menu: EventHandler[lambda grp_idx, data: [grp_idx, data]]
+    on_group_header_context_menu: EventHandler[
+        identity_event(int, GroupHeaderClickedEventArgs)
+    ]
 
     # Fired when a group header is renamed.
-    on_group_header_renamed: EventHandler[lambda idx, val: [idx, val]]
+    on_group_header_renamed: EventHandler[identity_event(str, str)]
 
     # Fired when a header is clicked.
     on_header_clicked: EventHandler[identity_event(Tuple[int, int])]
@@ -250,16 +326,16 @@ class DataEditor(NoSSRComponent):
     on_header_context_menu: EventHandler[identity_event(Tuple[int, int])]
 
     # Fired when a header menu item is clicked.
-    on_header_menu_click: EventHandler[lambda col, pos: [col, pos]]
+    on_header_menu_click: EventHandler[identity_event(int, Rectangle)]
 
     # Fired when an item is hovered.
     on_item_hovered: EventHandler[identity_event(Tuple[int, int])]
 
     # Fired when a selection is deleted.
-    on_delete: EventHandler[lambda selection: [selection]]
+    on_delete: EventHandler[identity_event(GridSelection)]
 
     # Fired when editing is finished.
-    on_finished_editing: EventHandler[lambda new_value, movement: [new_value, movement]]
+    on_finished_editing: EventHandler[identity_event(Union[GridCell, None], list[int])]
 
     # Fired when a row is appended.
     on_row_appended: EventHandler[empty_event]
@@ -268,7 +344,7 @@ class DataEditor(NoSSRComponent):
     on_selection_cleared: EventHandler[empty_event]
 
     # Fired when a column is resized.
-    on_column_resize: EventHandler[lambda col, width: [col, width]]
+    on_column_resize: EventHandler[identity_event(GridColumn, int)]
 
     def add_imports(self) -> ImportDict:
         """Add imports for the component.

+ 66 - 12
reflex/components/datadisplay/dataeditor.pyi

@@ -6,6 +6,8 @@
 from enum import Enum
 from typing import Any, Dict, List, Literal, Optional, Union, overload
 
+from typing_extensions import TypedDict
+
 from reflex.base import Base
 from reflex.components.component import NoSSRComponent
 from reflex.event import EventType
@@ -78,6 +80,54 @@ class DataEditorTheme(Base):
 
 def on_edit_spec(pos, data: dict[str, Any]): ...
 
+class Bounds(TypedDict):
+    x: int
+    y: int
+    width: int
+    height: int
+
+class CompatSelection(TypedDict):
+    items: list
+
+class Rectangle(TypedDict):
+    x: int
+    y: int
+    width: int
+    height: int
+
+class GridSelectionCurrent(TypedDict):
+    cell: list[int]
+    range: Rectangle
+    rangeStack: list[Rectangle]
+
+class GridSelection(TypedDict):
+    current: Optional[GridSelectionCurrent]
+    columns: CompatSelection
+    rows: CompatSelection
+
+class GroupHeaderClickedEventArgs(TypedDict):
+    kind: str
+    group: str
+    location: list[int]
+    bounds: Bounds
+    isEdge: bool
+    shiftKey: bool
+    ctrlKey: bool
+    metaKey: bool
+    isTouch: bool
+    localEventX: int
+    localEventY: int
+    button: int
+    buttons: int
+    scrollEdge: list[int]
+
+class GridCell(TypedDict):
+    span: Optional[List[int]]
+
+class GridColumn(TypedDict):
+    title: str
+    group: Optional[str]
+
 class DataEditor(NoSSRComponent):
     def add_imports(self) -> ImportDict: ...
     def add_hooks(self) -> list[str]: ...
@@ -136,24 +186,28 @@ class DataEditor(NoSSRComponent):
         autofocus: Optional[bool] = None,
         custom_attrs: Optional[Dict[str, Union[Var, str]]] = None,
         on_blur: Optional[EventType[[]]] = None,
-        on_cell_activated: Optional[EventType] = None,
-        on_cell_clicked: Optional[EventType] = None,
-        on_cell_context_menu: Optional[EventType] = None,
+        on_cell_activated: Optional[EventType[tuple[int, int]]] = None,
+        on_cell_clicked: Optional[EventType[tuple[int, int]]] = None,
+        on_cell_context_menu: Optional[EventType[tuple[int, int]]] = None,
         on_cell_edited: Optional[EventType] = None,
         on_click: Optional[EventType[[]]] = None,
-        on_column_resize: Optional[EventType] = None,
+        on_column_resize: Optional[EventType[GridColumn, int]] = None,
         on_context_menu: Optional[EventType[[]]] = None,
-        on_delete: Optional[EventType] = None,
+        on_delete: Optional[EventType[GridSelection]] = None,
         on_double_click: Optional[EventType[[]]] = None,
-        on_finished_editing: Optional[EventType] = None,
+        on_finished_editing: Optional[
+            EventType[Union[GridCell, None], list[int]]
+        ] = None,
         on_focus: Optional[EventType[[]]] = None,
         on_group_header_clicked: Optional[EventType] = None,
-        on_group_header_context_menu: Optional[EventType] = None,
-        on_group_header_renamed: Optional[EventType] = None,
-        on_header_clicked: Optional[EventType] = None,
-        on_header_context_menu: Optional[EventType] = None,
-        on_header_menu_click: Optional[EventType] = None,
-        on_item_hovered: Optional[EventType] = None,
+        on_group_header_context_menu: Optional[
+            EventType[int, GroupHeaderClickedEventArgs]
+        ] = None,
+        on_group_header_renamed: Optional[EventType[str, str]] = None,
+        on_header_clicked: Optional[EventType[tuple[int, int]]] = None,
+        on_header_context_menu: Optional[EventType[tuple[int, int]]] = None,
+        on_header_menu_click: Optional[EventType[int, Rectangle]] = None,
+        on_item_hovered: Optional[EventType[tuple[int, int]]] = None,
         on_mount: Optional[EventType[[]]] = None,
         on_mouse_down: Optional[EventType[[]]] = None,
         on_mouse_enter: Optional[EventType[[]]] = None,

+ 1 - 1
reflex/components/moment/moment.pyi

@@ -58,7 +58,7 @@ class Moment(NoSSRComponent):
         autofocus: Optional[bool] = None,
         custom_attrs: Optional[Dict[str, Union[Var, str]]] = None,
         on_blur: Optional[EventType[[]]] = None,
-        on_change: Optional[EventType] = None,
+        on_change: Optional[EventType[str]] = None,
         on_click: Optional[EventType[[]]] = None,
         on_context_menu: Optional[EventType[[]]] = None,
         on_double_click: Optional[EventType[[]]] = None,

+ 2 - 2
reflex/components/radix/primitives/drawer.pyi

@@ -101,7 +101,7 @@ class DrawerRoot(DrawerComponent):
         on_mouse_out: Optional[EventType[[]]] = None,
         on_mouse_over: Optional[EventType[[]]] = None,
         on_mouse_up: Optional[EventType[[]]] = None,
-        on_open_change: Optional[EventType] = None,
+        on_open_change: Optional[EventType[bool]] = None,
         on_scroll: Optional[EventType[[]]] = None,
         on_unmount: Optional[EventType[[]]] = None,
         **props,
@@ -511,7 +511,7 @@ class Drawer(ComponentNamespace):
         on_mouse_out: Optional[EventType[[]]] = None,
         on_mouse_over: Optional[EventType[[]]] = None,
         on_mouse_up: Optional[EventType[[]]] = None,
-        on_open_change: Optional[EventType] = None,
+        on_open_change: Optional[EventType[bool]] = None,
         on_scroll: Optional[EventType[[]]] = None,
         on_unmount: Optional[EventType[[]]] = None,
         **props,

+ 1 - 1
reflex/components/radix/themes/color_mode.pyi

@@ -383,7 +383,7 @@ class ColorModeSwitch(Switch):
         autofocus: Optional[bool] = None,
         custom_attrs: Optional[Dict[str, Union[Var, str]]] = None,
         on_blur: Optional[EventType[[]]] = None,
-        on_change: Optional[EventType] = None,
+        on_change: Optional[EventType[bool]] = None,
         on_click: Optional[EventType[[]]] = None,
         on_context_menu: Optional[EventType[[]]] = None,
         on_double_click: Optional[EventType[[]]] = None,

+ 1 - 1
reflex/components/radix/themes/components/alert_dialog.pyi

@@ -42,7 +42,7 @@ class AlertDialogRoot(RadixThemesComponent):
         on_mouse_out: Optional[EventType[[]]] = None,
         on_mouse_over: Optional[EventType[[]]] = None,
         on_mouse_up: Optional[EventType[[]]] = None,
-        on_open_change: Optional[EventType] = None,
+        on_open_change: Optional[EventType[bool]] = None,
         on_scroll: Optional[EventType[[]]] = None,
         on_unmount: Optional[EventType[[]]] = None,
         **props,

+ 3 - 3
reflex/components/radix/themes/components/checkbox.pyi

@@ -116,7 +116,7 @@ class Checkbox(RadixThemesComponent):
         autofocus: Optional[bool] = None,
         custom_attrs: Optional[Dict[str, Union[Var, str]]] = None,
         on_blur: Optional[EventType[[]]] = None,
-        on_change: Optional[EventType] = None,
+        on_change: Optional[EventType[bool]] = None,
         on_click: Optional[EventType[[]]] = None,
         on_context_menu: Optional[EventType[[]]] = None,
         on_double_click: Optional[EventType[[]]] = None,
@@ -263,7 +263,7 @@ class HighLevelCheckbox(RadixThemesComponent):
         autofocus: Optional[bool] = None,
         custom_attrs: Optional[Dict[str, Union[Var, str]]] = None,
         on_blur: Optional[EventType[[]]] = None,
-        on_change: Optional[EventType] = None,
+        on_change: Optional[EventType[bool]] = None,
         on_click: Optional[EventType[[]]] = None,
         on_context_menu: Optional[EventType[[]]] = None,
         on_double_click: Optional[EventType[[]]] = None,
@@ -407,7 +407,7 @@ class CheckboxNamespace(ComponentNamespace):
         autofocus: Optional[bool] = None,
         custom_attrs: Optional[Dict[str, Union[Var, str]]] = None,
         on_blur: Optional[EventType[[]]] = None,
-        on_change: Optional[EventType] = None,
+        on_change: Optional[EventType[bool]] = None,
         on_click: Optional[EventType[[]]] = None,
         on_context_menu: Optional[EventType[[]]] = None,
         on_double_click: Optional[EventType[[]]] = None,

+ 1 - 1
reflex/components/radix/themes/components/context_menu.pyi

@@ -39,7 +39,7 @@ class ContextMenuRoot(RadixThemesComponent):
         on_mouse_out: Optional[EventType[[]]] = None,
         on_mouse_over: Optional[EventType[[]]] = None,
         on_mouse_up: Optional[EventType[[]]] = None,
-        on_open_change: Optional[EventType] = None,
+        on_open_change: Optional[EventType[bool]] = None,
         on_scroll: Optional[EventType[[]]] = None,
         on_unmount: Optional[EventType[[]]] = None,
         **props,

+ 2 - 2
reflex/components/radix/themes/components/dialog.pyi

@@ -40,7 +40,7 @@ class DialogRoot(RadixThemesComponent):
         on_mouse_out: Optional[EventType[[]]] = None,
         on_mouse_over: Optional[EventType[[]]] = None,
         on_mouse_up: Optional[EventType[[]]] = None,
-        on_open_change: Optional[EventType] = None,
+        on_open_change: Optional[EventType[bool]] = None,
         on_scroll: Optional[EventType[[]]] = None,
         on_unmount: Optional[EventType[[]]] = None,
         **props,
@@ -382,7 +382,7 @@ class Dialog(ComponentNamespace):
         on_mouse_out: Optional[EventType[[]]] = None,
         on_mouse_over: Optional[EventType[[]]] = None,
         on_mouse_up: Optional[EventType[[]]] = None,
-        on_open_change: Optional[EventType] = None,
+        on_open_change: Optional[EventType[bool]] = None,
         on_scroll: Optional[EventType[[]]] = None,
         on_unmount: Optional[EventType[[]]] = None,
         **props,

+ 2 - 2
reflex/components/radix/themes/components/dropdown_menu.pyi

@@ -49,7 +49,7 @@ class DropdownMenuRoot(RadixThemesComponent):
         on_mouse_out: Optional[EventType[[]]] = None,
         on_mouse_over: Optional[EventType[[]]] = None,
         on_mouse_up: Optional[EventType[[]]] = None,
-        on_open_change: Optional[EventType] = None,
+        on_open_change: Optional[EventType[bool]] = None,
         on_scroll: Optional[EventType[[]]] = None,
         on_unmount: Optional[EventType[[]]] = None,
         **props,
@@ -363,7 +363,7 @@ class DropdownMenuSub(RadixThemesComponent):
         on_mouse_out: Optional[EventType[[]]] = None,
         on_mouse_over: Optional[EventType[[]]] = None,
         on_mouse_up: Optional[EventType[[]]] = None,
-        on_open_change: Optional[EventType] = None,
+        on_open_change: Optional[EventType[bool]] = None,
         on_scroll: Optional[EventType[[]]] = None,
         on_unmount: Optional[EventType[[]]] = None,
         **props,

+ 2 - 2
reflex/components/radix/themes/components/hover_card.pyi

@@ -43,7 +43,7 @@ class HoverCardRoot(RadixThemesComponent):
         on_mouse_out: Optional[EventType[[]]] = None,
         on_mouse_over: Optional[EventType[[]]] = None,
         on_mouse_up: Optional[EventType[[]]] = None,
-        on_open_change: Optional[EventType] = None,
+        on_open_change: Optional[EventType[bool]] = None,
         on_scroll: Optional[EventType[[]]] = None,
         on_unmount: Optional[EventType[[]]] = None,
         **props,
@@ -256,7 +256,7 @@ class HoverCard(ComponentNamespace):
         on_mouse_out: Optional[EventType[[]]] = None,
         on_mouse_over: Optional[EventType[[]]] = None,
         on_mouse_up: Optional[EventType[[]]] = None,
-        on_open_change: Optional[EventType] = None,
+        on_open_change: Optional[EventType[bool]] = None,
         on_scroll: Optional[EventType[[]]] = None,
         on_unmount: Optional[EventType[[]]] = None,
         **props,

+ 1 - 1
reflex/components/radix/themes/components/popover.pyi

@@ -41,7 +41,7 @@ class PopoverRoot(RadixThemesComponent):
         on_mouse_out: Optional[EventType[[]]] = None,
         on_mouse_over: Optional[EventType[[]]] = None,
         on_mouse_up: Optional[EventType[[]]] = None,
-        on_open_change: Optional[EventType] = None,
+        on_open_change: Optional[EventType[bool]] = None,
         on_scroll: Optional[EventType[[]]] = None,
         on_unmount: Optional[EventType[[]]] = None,
         **props,

+ 1 - 1
reflex/components/radix/themes/components/radio_cards.pyi

@@ -177,7 +177,7 @@ class RadioCardsRoot(RadixThemesComponent):
         on_mouse_up: Optional[EventType[[]]] = None,
         on_scroll: Optional[EventType[[]]] = None,
         on_unmount: Optional[EventType[[]]] = None,
-        on_value_change: Optional[EventType] = None,
+        on_value_change: Optional[EventType[str]] = None,
         **props,
     ) -> "RadioCardsRoot":
         """Create a new component instance.

+ 1 - 1
reflex/components/radix/themes/components/radio_group.pyi

@@ -113,7 +113,7 @@ class RadioGroupRoot(RadixThemesComponent):
         autofocus: Optional[bool] = None,
         custom_attrs: Optional[Dict[str, Union[Var, str]]] = None,
         on_blur: Optional[EventType[[]]] = None,
-        on_change: Optional[EventType] = None,
+        on_change: Optional[EventType[str]] = None,
         on_click: Optional[EventType[[]]] = None,
         on_context_menu: Optional[EventType[[]]] = None,
         on_double_click: Optional[EventType[[]]] = None,

+ 6 - 6
reflex/components/radix/themes/components/select.pyi

@@ -44,7 +44,7 @@ class SelectRoot(RadixThemesComponent):
         autofocus: Optional[bool] = None,
         custom_attrs: Optional[Dict[str, Union[Var, str]]] = None,
         on_blur: Optional[EventType[[]]] = None,
-        on_change: Optional[EventType] = None,
+        on_change: Optional[EventType[str]] = None,
         on_click: Optional[EventType[[]]] = None,
         on_context_menu: Optional[EventType[[]]] = None,
         on_double_click: Optional[EventType[[]]] = None,
@@ -57,7 +57,7 @@ class SelectRoot(RadixThemesComponent):
         on_mouse_out: Optional[EventType[[]]] = None,
         on_mouse_over: Optional[EventType[[]]] = None,
         on_mouse_up: Optional[EventType[[]]] = None,
-        on_open_change: Optional[EventType] = None,
+        on_open_change: Optional[EventType[bool]] = None,
         on_scroll: Optional[EventType[[]]] = None,
         on_unmount: Optional[EventType[[]]] = None,
         **props,
@@ -680,7 +680,7 @@ class HighLevelSelect(SelectRoot):
         autofocus: Optional[bool] = None,
         custom_attrs: Optional[Dict[str, Union[Var, str]]] = None,
         on_blur: Optional[EventType[[]]] = None,
-        on_change: Optional[EventType] = None,
+        on_change: Optional[EventType[str]] = None,
         on_click: Optional[EventType[[]]] = None,
         on_context_menu: Optional[EventType[[]]] = None,
         on_double_click: Optional[EventType[[]]] = None,
@@ -693,7 +693,7 @@ class HighLevelSelect(SelectRoot):
         on_mouse_out: Optional[EventType[[]]] = None,
         on_mouse_over: Optional[EventType[[]]] = None,
         on_mouse_up: Optional[EventType[[]]] = None,
-        on_open_change: Optional[EventType] = None,
+        on_open_change: Optional[EventType[bool]] = None,
         on_scroll: Optional[EventType[[]]] = None,
         on_unmount: Optional[EventType[[]]] = None,
         **props,
@@ -854,7 +854,7 @@ class Select(ComponentNamespace):
         autofocus: Optional[bool] = None,
         custom_attrs: Optional[Dict[str, Union[Var, str]]] = None,
         on_blur: Optional[EventType[[]]] = None,
-        on_change: Optional[EventType] = None,
+        on_change: Optional[EventType[str]] = None,
         on_click: Optional[EventType[[]]] = None,
         on_context_menu: Optional[EventType[[]]] = None,
         on_double_click: Optional[EventType[[]]] = None,
@@ -867,7 +867,7 @@ class Select(ComponentNamespace):
         on_mouse_out: Optional[EventType[[]]] = None,
         on_mouse_over: Optional[EventType[[]]] = None,
         on_mouse_up: Optional[EventType[[]]] = None,
-        on_open_change: Optional[EventType] = None,
+        on_open_change: Optional[EventType[bool]] = None,
         on_scroll: Optional[EventType[[]]] = None,
         on_unmount: Optional[EventType[[]]] = None,
         **props,

+ 1 - 1
reflex/components/radix/themes/components/switch.pyi

@@ -119,7 +119,7 @@ class Switch(RadixThemesComponent):
         autofocus: Optional[bool] = None,
         custom_attrs: Optional[Dict[str, Union[Var, str]]] = None,
         on_blur: Optional[EventType[[]]] = None,
-        on_change: Optional[EventType] = None,
+        on_change: Optional[EventType[bool]] = None,
         on_click: Optional[EventType[[]]] = None,
         on_context_menu: Optional[EventType[[]]] = None,
         on_double_click: Optional[EventType[[]]] = None,

+ 2 - 2
reflex/components/radix/themes/components/tabs.pyi

@@ -41,7 +41,7 @@ class TabsRoot(RadixThemesComponent):
         autofocus: Optional[bool] = None,
         custom_attrs: Optional[Dict[str, Union[Var, str]]] = None,
         on_blur: Optional[EventType[[]]] = None,
-        on_change: Optional[EventType] = None,
+        on_change: Optional[EventType[str]] = None,
         on_click: Optional[EventType[[]]] = None,
         on_context_menu: Optional[EventType[[]]] = None,
         on_double_click: Optional[EventType[[]]] = None,
@@ -340,7 +340,7 @@ class Tabs(ComponentNamespace):
         autofocus: Optional[bool] = None,
         custom_attrs: Optional[Dict[str, Union[Var, str]]] = None,
         on_blur: Optional[EventType[[]]] = None,
-        on_change: Optional[EventType] = None,
+        on_change: Optional[EventType[str]] = None,
         on_click: Optional[EventType[[]]] = None,
         on_context_menu: Optional[EventType[[]]] = None,
         on_double_click: Optional[EventType[[]]] = None,

+ 1 - 1
reflex/components/radix/themes/components/tooltip.pyi

@@ -76,7 +76,7 @@ class Tooltip(RadixThemesComponent):
         on_mouse_out: Optional[EventType[[]]] = None,
         on_mouse_over: Optional[EventType[[]]] = None,
         on_mouse_up: Optional[EventType[[]]] = None,
-        on_open_change: Optional[EventType] = None,
+        on_open_change: Optional[EventType[bool]] = None,
         on_pointer_down_outside: Optional[EventType[[]]] = None,
         on_scroll: Optional[EventType[[]]] = None,
         on_unmount: Optional[EventType[[]]] = None,

+ 2 - 2
reflex/components/react_player/audio.pyi

@@ -41,7 +41,7 @@ class Audio(ReactPlayer):
         on_context_menu: Optional[EventType[[]]] = None,
         on_disable_pip: Optional[EventType[[]]] = None,
         on_double_click: Optional[EventType[[]]] = None,
-        on_duration: Optional[EventType] = None,
+        on_duration: Optional[EventType[float]] = None,
         on_enable_pip: Optional[EventType[[]]] = None,
         on_ended: Optional[EventType[[]]] = None,
         on_error: Optional[EventType[[]]] = None,
@@ -61,7 +61,7 @@ class Audio(ReactPlayer):
         on_progress: Optional[EventType] = None,
         on_ready: Optional[EventType[[]]] = None,
         on_scroll: Optional[EventType[[]]] = None,
-        on_seek: Optional[EventType] = None,
+        on_seek: Optional[EventType[float]] = None,
         on_start: Optional[EventType[[]]] = None,
         on_unmount: Optional[EventType[[]]] = None,
         **props,

+ 2 - 2
reflex/components/react_player/react_player.pyi

@@ -39,7 +39,7 @@ class ReactPlayer(NoSSRComponent):
         on_context_menu: Optional[EventType[[]]] = None,
         on_disable_pip: Optional[EventType[[]]] = None,
         on_double_click: Optional[EventType[[]]] = None,
-        on_duration: Optional[EventType] = None,
+        on_duration: Optional[EventType[float]] = None,
         on_enable_pip: Optional[EventType[[]]] = None,
         on_ended: Optional[EventType[[]]] = None,
         on_error: Optional[EventType[[]]] = None,
@@ -59,7 +59,7 @@ class ReactPlayer(NoSSRComponent):
         on_progress: Optional[EventType] = None,
         on_ready: Optional[EventType[[]]] = None,
         on_scroll: Optional[EventType[[]]] = None,
-        on_seek: Optional[EventType] = None,
+        on_seek: Optional[EventType[float]] = None,
         on_start: Optional[EventType[[]]] = None,
         on_unmount: Optional[EventType[[]]] = None,
         **props,

+ 2 - 2
reflex/components/react_player/video.pyi

@@ -41,7 +41,7 @@ class Video(ReactPlayer):
         on_context_menu: Optional[EventType[[]]] = None,
         on_disable_pip: Optional[EventType[[]]] = None,
         on_double_click: Optional[EventType[[]]] = None,
-        on_duration: Optional[EventType] = None,
+        on_duration: Optional[EventType[float]] = None,
         on_enable_pip: Optional[EventType[[]]] = None,
         on_ended: Optional[EventType[[]]] = None,
         on_error: Optional[EventType[[]]] = None,
@@ -61,7 +61,7 @@ class Video(ReactPlayer):
         on_progress: Optional[EventType] = None,
         on_ready: Optional[EventType[[]]] = None,
         on_scroll: Optional[EventType[[]]] = None,
-        on_seek: Optional[EventType] = None,
+        on_seek: Optional[EventType[float]] = None,
         on_start: Optional[EventType[[]]] = None,
         on_unmount: Optional[EventType[[]]] = None,
         **props,

+ 4 - 4
reflex/components/suneditor/editor.pyi

@@ -128,7 +128,7 @@ class Editor(NoSSRComponent):
         autofocus: Optional[bool] = None,
         custom_attrs: Optional[Dict[str, Union[Var, str]]] = None,
         on_blur: Optional[EventType[str]] = None,
-        on_change: Optional[EventType] = None,
+        on_change: Optional[EventType[str]] = None,
         on_click: Optional[EventType[[]]] = None,
         on_context_menu: Optional[EventType[[]]] = None,
         on_copy: Optional[EventType[[]]] = None,
@@ -136,7 +136,7 @@ class Editor(NoSSRComponent):
         on_double_click: Optional[EventType[[]]] = None,
         on_focus: Optional[EventType[[]]] = None,
         on_input: Optional[EventType[[]]] = None,
-        on_load: Optional[EventType] = None,
+        on_load: Optional[EventType[bool]] = None,
         on_mount: Optional[EventType[[]]] = None,
         on_mouse_down: Optional[EventType[[]]] = None,
         on_mouse_enter: Optional[EventType[[]]] = None,
@@ -148,8 +148,8 @@ class Editor(NoSSRComponent):
         on_paste: Optional[EventType[str, bool]] = None,
         on_scroll: Optional[EventType[[]]] = None,
         on_unmount: Optional[EventType[[]]] = None,
-        toggle_code_view: Optional[EventType] = None,
-        toggle_full_screen: Optional[EventType] = None,
+        toggle_code_view: Optional[EventType[bool]] = None,
+        toggle_full_screen: Optional[EventType[bool]] = None,
         **props,
     ) -> "Editor":
         """Create an instance of Editor. No children allowed.

+ 73 - 9
reflex/event.py

@@ -24,7 +24,7 @@ from typing import (
     overload,
 )
 
-from typing_extensions import ParamSpec, get_args, get_origin
+from typing_extensions import ParamSpec, Protocol, get_args, get_origin
 
 from reflex import constants
 from reflex.utils import console, format
@@ -465,33 +465,97 @@ prevent_default = EventChain(events=[], args_spec=empty_event).prevent_default
 
 
 T = TypeVar("T")
+U = TypeVar("U")
 
 
-def identity_event(event_type: Type[T]) -> Callable[[Var[T]], Tuple[Var[T]]]:
+# def identity_event(event_type: Type[T]) -> Callable[[Var[T]], Tuple[Var[T]]]:
+#     """A helper function that returns the input event as output.
+
+#     Args:
+#         event_type: The type of the event.
+
+#     Returns:
+#         A function that returns the input event as output.
+#     """
+
+#     def inner(ev: Var[T]) -> Tuple[Var[T]]:
+#         return (ev,)
+
+#     inner.__signature__ = inspect.signature(inner).replace(  # type: ignore
+#         parameters=[
+#             inspect.Parameter(
+#                 "ev",
+#                 kind=inspect.Parameter.POSITIONAL_OR_KEYWORD,
+#                 annotation=Var[event_type],
+#             )
+#         ],
+#         return_annotation=Tuple[Var[event_type]],
+#     )
+#     inner.__annotations__["ev"] = Var[event_type]
+#     inner.__annotations__["return"] = Tuple[Var[event_type]]
+
+#     return inner
+
+
+class IdentityEventReturn(Generic[T], Protocol):
+    """Protocol for an identity event return."""
+
+    def __call__(self, *values: Var[T]) -> Tuple[Var[T], ...]:
+        """Return the input values.
+
+        Args:
+            *values: The values to return.
+
+        Returns:
+            The input values.
+        """
+        return values
+
+
+@overload
+def identity_event(event_type: Type[T], /) -> Callable[[Var[T]], Tuple[Var[T]]]: ...  # type: ignore
+
+
+@overload
+def identity_event(
+    event_type_1: Type[T], event_type2: Type[U], /
+) -> Callable[[Var[T], Var[U]], Tuple[Var[T], Var[U]]]: ...
+
+
+@overload
+def identity_event(*event_types: Type[T]) -> IdentityEventReturn[T]: ...
+
+
+def identity_event(*event_types: Type[T]) -> IdentityEventReturn[T]:  # type: ignore
     """A helper function that returns the input event as output.
 
     Args:
-        event_type: The type of the event.
+        *event_types: The types of the events.
 
     Returns:
         A function that returns the input event as output.
     """
 
-    def inner(ev: Var[T]) -> Tuple[Var[T]]:
-        return (ev,)
+    def inner(*values: Var[T]) -> Tuple[Var[T], ...]:
+        return values
+
+    inner_type = tuple(Var[event_type] for event_type in event_types)
+    return_annotation = Tuple[inner_type]  # type: ignore
 
     inner.__signature__ = inspect.signature(inner).replace(  # type: ignore
         parameters=[
             inspect.Parameter(
-                "ev",
+                f"ev_{i}",
                 kind=inspect.Parameter.POSITIONAL_OR_KEYWORD,
                 annotation=Var[event_type],
             )
+            for i, event_type in enumerate(event_types)
         ],
-        return_annotation=Tuple[Var[event_type]],
+        return_annotation=return_annotation,
     )
-    inner.__annotations__["ev"] = Var[event_type]
-    inner.__annotations__["return"] = Tuple[Var[event_type]]
+    for i, event_type in enumerate(event_types):
+        inner.__annotations__[f"ev_{i}"] = Var[event_type]
+    inner.__annotations__["return"] = return_annotation
 
     return inner
 

+ 1 - 1
reflex/experimental/layout.pyi

@@ -129,7 +129,7 @@ class DrawerSidebar(DrawerRoot):
         on_mouse_out: Optional[EventType[[]]] = None,
         on_mouse_over: Optional[EventType[[]]] = None,
         on_mouse_up: Optional[EventType[[]]] = None,
-        on_open_change: Optional[EventType] = None,
+        on_open_change: Optional[EventType[bool]] = None,
         on_scroll: Optional[EventType[[]]] = None,
         on_unmount: Optional[EventType[[]]] = None,
         **props,

+ 76 - 2
reflex/utils/pyi_generator.py

@@ -16,7 +16,7 @@ from itertools import chain
 from multiprocessing import Pool, cpu_count
 from pathlib import Path
 from types import ModuleType, SimpleNamespace
-from typing import Any, Callable, Iterable, Type, get_args
+from typing import Any, Callable, Iterable, Type, get_args, get_origin
 
 from reflex.components.component import Component
 from reflex.utils import types as rx_types
@@ -372,6 +372,53 @@ def _extract_class_props_as_ast_nodes(
     return kwargs
 
 
+def type_to_ast(typ) -> ast.AST:
+    """Converts any type annotation into its AST representation.
+    Handles nested generic types, unions, etc.
+
+    Args:
+        typ: The type annotation to convert.
+
+    Returns:
+        The AST representation of the type annotation.
+    """
+    if typ is type(None):
+        return ast.Name(id="None")
+
+    origin = get_origin(typ)
+
+    # Handle plain types (int, str, custom classes, etc.)
+    if origin is None:
+        if hasattr(typ, "__name__"):
+            return ast.Name(id=typ.__name__)
+        elif hasattr(typ, "_name"):
+            return ast.Name(id=typ._name)
+        return ast.Name(id=str(typ))
+
+    # Get the base type name (List, Dict, Optional, etc.)
+    base_name = origin._name if hasattr(origin, "_name") else origin.__name__
+
+    # Get type arguments
+    args = get_args(typ)
+
+    # Handle empty type arguments
+    if not args:
+        return ast.Name(id=base_name)
+
+    # Convert all type arguments recursively
+    arg_nodes = [type_to_ast(arg) for arg in args]
+
+    # Special case for single-argument types (like List[T] or Optional[T])
+    if len(arg_nodes) == 1:
+        slice_value = arg_nodes[0]
+    else:
+        slice_value = ast.Tuple(elts=arg_nodes, ctx=ast.Load())
+
+    return ast.Subscript(
+        value=ast.Name(id=base_name), slice=ast.Index(value=slice_value), ctx=ast.Load()
+    )
+
+
 def _get_parent_imports(func):
     _imports = {"reflex.vars": ["Var"]}
     for type_hint in inspect.get_annotations(func).values():
@@ -430,13 +477,40 @@ def _generate_component_create_functiondef(
     def figure_out_return_type(annotation: Any):
         if inspect.isclass(annotation) and issubclass(annotation, inspect._empty):
             return ast.Name(id="Optional[EventType]")
+
+        if not isinstance(annotation, str) and get_origin(annotation) is tuple:
+            arguments = get_args(annotation)
+
+            arguments_without_var = [
+                get_args(argument)[0] if get_origin(argument) == Var else argument
+                for argument in arguments
+            ]
+
+            # Convert each argument type to its AST representation
+            type_args = [type_to_ast(arg) for arg in arguments_without_var]
+
+            # Join the type arguments with commas for EventType
+            args_str = ", ".join(ast.unparse(arg) for arg in type_args)
+
+            # Create EventType using the joined string
+            event_type = ast.Name(id=f"EventType[{args_str}]")
+
+            # Wrap in Optional
+            optional_type = ast.Subscript(
+                value=ast.Name(id="Optional"),
+                slice=ast.Index(value=event_type),
+                ctx=ast.Load(),
+            )
+
+            return ast.Name(id=ast.unparse(optional_type))
+
         if isinstance(annotation, str) and annotation.startswith("Tuple["):
             inside_of_tuple = annotation.removeprefix("Tuple[").removesuffix("]")
 
             if inside_of_tuple == "()":
                 return ast.Name(id="Optional[EventType[[]]]")
 
-            arguments: list[str] = [""]
+            arguments = [""]
 
             bracket_count = 0
 

+ 1 - 0
tests/integration/test_lifespan.py

@@ -51,6 +51,7 @@ def LifespanApp():
         def context_global(self) -> int:
             return lifespan_context_global
 
+        @rx.event
         def tick(self, date):
             pass