diff --git a/reflex/.templates/web/utils/state.js b/reflex/.templates/web/utils/state.js index 0fe0db8c1..24092f235 100644 --- a/reflex/.templates/web/utils/state.js +++ b/reflex/.templates/web/utils/state.js @@ -743,6 +743,7 @@ export const useEventLoop = ( addEvents([ Event(`${exception_state_name}.handle_frontend_exception`, { stack: error.stack, + component_stack: "", }), ]); return false; @@ -754,6 +755,7 @@ export const useEventLoop = ( addEvents([ Event(`${exception_state_name}.handle_frontend_exception`, { stack: event.reason.stack, + component_stack: "", }), ]); return false; diff --git a/reflex/components/base/error_boundary.py b/reflex/components/base/error_boundary.py index 66a9c43c8..83becc034 100644 --- a/reflex/components/base/error_boundary.py +++ b/reflex/components/base/error_boundary.py @@ -2,16 +2,32 @@ from __future__ import annotations -from typing import List +from typing import Dict, List, Tuple from reflex.compiler.compiler import _compile_component from reflex.components.component import Component from reflex.components.el import div, p -from reflex.constants import Hooks, Imports -from reflex.event import EventChain, EventHandler -from reflex.utils.imports import ImportVar +from reflex.event import EventHandler +from reflex.state import FrontendEventExceptionState from reflex.vars.base import Var -from reflex.vars.function import FunctionVar + + +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: + error: The error message. + info: Additional information about the error. + + Returns: + The arguments for the event handler. + """ + return ( + error.stack, + info.componentStack, + ) class ErrorBoundary(Component): @@ -21,31 +37,13 @@ class ErrorBoundary(Component): tag = "ErrorBoundary" # Fired when the boundary catches an error. - on_error: EventHandler[lambda error, info: [error, info]] = Var( # type: ignore - "logFrontendError" - ).to(FunctionVar, EventChain) + on_error: EventHandler[on_error_spec] # Rendered instead of the children when an error is caught. Fallback_component: Var[Component] = Var(_js_expr="Fallback")._replace( _var_type=Component ) - def add_imports(self) -> dict[str, list[ImportVar]]: - """Add imports for the component. - - Returns: - The imports to add. - """ - return Imports.EVENTS - - def add_hooks(self) -> List[str | Var]: - """Add hooks for the component. - - Returns: - The hooks to add. - """ - return [Hooks.EVENTS, Hooks.FRONTEND_ERRORS] - def add_custom_code(self) -> List[str]: """Add custom Javascript code into the page that contains this component. @@ -75,5 +73,20 @@ class ErrorBoundary(Component): """ ] + @classmethod + def create(cls, *children, **props): + """Create an ErrorBoundary component. + + Args: + *children: The children of the component. + **props: The props of the component. + + Returns: + The ErrorBoundary component. + """ + if "on_error" not in props: + props["on_error"] = FrontendEventExceptionState.handle_frontend_exception + return super().create(*children, **props) + error_boundary = ErrorBoundary.create diff --git a/reflex/components/base/error_boundary.pyi b/reflex/components/base/error_boundary.pyi index aaf5584e4..c84742851 100644 --- a/reflex/components/base/error_boundary.pyi +++ b/reflex/components/base/error_boundary.pyi @@ -3,17 +3,18 @@ # ------------------- DO NOT EDIT ---------------------- # This file was generated by `reflex/utils/pyi_generator.py`! # ------------------------------------------------------ -from typing import Any, Dict, List, Optional, Union, overload +from typing import Any, Dict, List, Optional, Tuple, Union, overload from reflex.components.component import Component from reflex.event import EventType from reflex.style import Style -from reflex.utils.imports import ImportVar from reflex.vars.base import Var +def on_error_spec( + error: Var[Dict[str, str]], info: Var[Dict[str, str]] +) -> Tuple[Var[str], Var[str]]: ... + class ErrorBoundary(Component): - def add_imports(self) -> dict[str, list[ImportVar]]: ... - def add_hooks(self) -> List[str | Var]: ... def add_custom_code(self) -> List[str]: ... @overload @classmethod @@ -31,7 +32,7 @@ class ErrorBoundary(Component): on_click: Optional[EventType[[]]] = None, on_context_menu: Optional[EventType[[]]] = None, on_double_click: Optional[EventType[[]]] = None, - on_error: Optional[EventType] = None, + on_error: Optional[EventType[str, str]] = None, on_focus: Optional[EventType[[]]] = None, on_mount: Optional[EventType[[]]] = None, on_mouse_down: Optional[EventType[[]]] = None, @@ -45,7 +46,7 @@ class ErrorBoundary(Component): on_unmount: Optional[EventType[[]]] = None, **props, ) -> "ErrorBoundary": - """Create the component. + """Create an ErrorBoundary component. Args: *children: The children of the component. @@ -59,7 +60,7 @@ class ErrorBoundary(Component): **props: The props of the component. Returns: - The component. + The ErrorBoundary component. """ ... diff --git a/reflex/components/core/clipboard.pyi b/reflex/components/core/clipboard.pyi index e2f6afc8d..489a9bcc5 100644 --- a/reflex/components/core/clipboard.pyi +++ b/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, diff --git a/reflex/components/datadisplay/dataeditor.py b/reflex/components/datadisplay/dataeditor.py index 01255dd14..a192c7a45 100644 --- a/reflex/components/datadisplay/dataeditor.py +++ b/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. diff --git a/reflex/components/datadisplay/dataeditor.pyi b/reflex/components/datadisplay/dataeditor.pyi index 1b8fed287..aadd9666e 100644 --- a/reflex/components/datadisplay/dataeditor.pyi +++ b/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, diff --git a/reflex/components/moment/moment.pyi b/reflex/components/moment/moment.pyi index 4f58fda7d..ccffbb8d1 100644 --- a/reflex/components/moment/moment.pyi +++ b/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, diff --git a/reflex/components/radix/primitives/drawer.pyi b/reflex/components/radix/primitives/drawer.pyi index 9c5463e56..c4493ee9b 100644 --- a/reflex/components/radix/primitives/drawer.pyi +++ b/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, diff --git a/reflex/components/radix/themes/color_mode.pyi b/reflex/components/radix/themes/color_mode.pyi index 43703dc37..d856b0bbd 100644 --- a/reflex/components/radix/themes/color_mode.pyi +++ b/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, diff --git a/reflex/components/radix/themes/components/alert_dialog.pyi b/reflex/components/radix/themes/components/alert_dialog.pyi index d63fcae53..771def7d9 100644 --- a/reflex/components/radix/themes/components/alert_dialog.pyi +++ b/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, diff --git a/reflex/components/radix/themes/components/checkbox.pyi b/reflex/components/radix/themes/components/checkbox.pyi index fad4f5210..90a9220ef 100644 --- a/reflex/components/radix/themes/components/checkbox.pyi +++ b/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, diff --git a/reflex/components/radix/themes/components/context_menu.pyi b/reflex/components/radix/themes/components/context_menu.pyi index fbefa88de..56d8200e0 100644 --- a/reflex/components/radix/themes/components/context_menu.pyi +++ b/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, diff --git a/reflex/components/radix/themes/components/dialog.pyi b/reflex/components/radix/themes/components/dialog.pyi index e3f17d7e8..0713461e9 100644 --- a/reflex/components/radix/themes/components/dialog.pyi +++ b/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, diff --git a/reflex/components/radix/themes/components/dropdown_menu.pyi b/reflex/components/radix/themes/components/dropdown_menu.pyi index dba619e7d..8de273be9 100644 --- a/reflex/components/radix/themes/components/dropdown_menu.pyi +++ b/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, diff --git a/reflex/components/radix/themes/components/hover_card.pyi b/reflex/components/radix/themes/components/hover_card.pyi index 8924ef1a8..fa169c852 100644 --- a/reflex/components/radix/themes/components/hover_card.pyi +++ b/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, diff --git a/reflex/components/radix/themes/components/popover.pyi b/reflex/components/radix/themes/components/popover.pyi index 984a139d0..218392517 100644 --- a/reflex/components/radix/themes/components/popover.pyi +++ b/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, diff --git a/reflex/components/radix/themes/components/radio_cards.pyi b/reflex/components/radix/themes/components/radio_cards.pyi index d73447622..d2f6a1425 100644 --- a/reflex/components/radix/themes/components/radio_cards.pyi +++ b/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. diff --git a/reflex/components/radix/themes/components/radio_group.pyi b/reflex/components/radix/themes/components/radio_group.pyi index c984fa1f2..f928421c5 100644 --- a/reflex/components/radix/themes/components/radio_group.pyi +++ b/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, diff --git a/reflex/components/radix/themes/components/select.pyi b/reflex/components/radix/themes/components/select.pyi index c43d58ada..e0e184482 100644 --- a/reflex/components/radix/themes/components/select.pyi +++ b/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, diff --git a/reflex/components/radix/themes/components/switch.pyi b/reflex/components/radix/themes/components/switch.pyi index ba9c2595e..f8871872a 100644 --- a/reflex/components/radix/themes/components/switch.pyi +++ b/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, diff --git a/reflex/components/radix/themes/components/tabs.pyi b/reflex/components/radix/themes/components/tabs.pyi index 7b67bad6e..4272bf2a3 100644 --- a/reflex/components/radix/themes/components/tabs.pyi +++ b/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, diff --git a/reflex/components/radix/themes/components/tooltip.pyi b/reflex/components/radix/themes/components/tooltip.pyi index ad7c4402f..ac2a36368 100644 --- a/reflex/components/radix/themes/components/tooltip.pyi +++ b/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, diff --git a/reflex/components/react_player/audio.pyi b/reflex/components/react_player/audio.pyi index d1f29f508..2556c8e83 100644 --- a/reflex/components/react_player/audio.pyi +++ b/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, diff --git a/reflex/components/react_player/video.pyi b/reflex/components/react_player/video.pyi index a50ccf71f..d46e2617d 100644 --- a/reflex/components/react_player/video.pyi +++ b/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, diff --git a/reflex/components/suneditor/editor.py b/reflex/components/suneditor/editor.py index 6f5b6e4b4..3bca8a3f6 100644 --- a/reflex/components/suneditor/editor.py +++ b/reflex/components/suneditor/editor.py @@ -3,11 +3,11 @@ from __future__ import annotations import enum -from typing import Dict, List, Literal, Optional, Union +from typing import Dict, List, Literal, Optional, Tuple, Union from reflex.base import Base from reflex.components.component import Component, NoSSRComponent -from reflex.event import EventHandler +from reflex.event import EventHandler, empty_event, identity_event from reflex.utils.format import to_camel_case from reflex.utils.imports import ImportDict, ImportVar from reflex.vars.base import Var @@ -68,6 +68,35 @@ class EditorOptions(Base): button_list: Optional[List[Union[List[str], str]]] +def on_blur_spec(e: Var, content: Var[str]) -> Tuple[Var[str]]: + """A helper function to specify the on_blur event handler. + + Args: + e: The event. + content: The content of the editor. + + Returns: + A tuple containing the content of the editor. + """ + return (content,) + + +def on_paste_spec( + e: Var, clean_data: Var[str], max_char_count: Var[bool] +) -> Tuple[Var[str], Var[bool]]: + """A helper function to specify the on_paste event handler. + + Args: + e: The event. + clean_data: The clean data. + max_char_count: The maximum character count. + + Returns: + A tuple containing the clean data and the maximum character count. + """ + return (clean_data, max_char_count) + + class Editor(NoSSRComponent): """A Rich Text Editor component based on SunEditor. Not every JS prop is listed here (some are not easily usable from python), @@ -178,36 +207,31 @@ class Editor(NoSSRComponent): disable_toolbar: Var[bool] # Fired when the editor content changes. - on_change: EventHandler[lambda content: [content]] + on_change: EventHandler[identity_event(str)] # Fired when the something is inputted in the editor. - on_input: EventHandler[lambda e: [e]] + on_input: EventHandler[empty_event] # Fired when the editor loses focus. - on_blur: EventHandler[lambda e, content: [content]] + on_blur: EventHandler[on_blur_spec] # Fired when the editor is loaded. - on_load: EventHandler[lambda reload: [reload]] - - # Fired when the editor is resized. - on_resize_editor: EventHandler[lambda height, prev_height: [height, prev_height]] + on_load: EventHandler[identity_event(bool)] # Fired when the editor content is copied. - on_copy: EventHandler[lambda e, clipboard_data: [clipboard_data]] + on_copy: EventHandler[empty_event] # Fired when the editor content is cut. - on_cut: EventHandler[lambda e, clipboard_data: [clipboard_data]] + on_cut: EventHandler[empty_event] # Fired when the editor content is pasted. - on_paste: EventHandler[ - lambda e, clean_data, max_char_count: [clean_data, max_char_count] - ] + on_paste: EventHandler[on_paste_spec] # Fired when the code view is toggled. - toggle_code_view: EventHandler[lambda is_code_view: [is_code_view]] + toggle_code_view: EventHandler[identity_event(bool)] # Fired when the full screen mode is toggled. - toggle_full_screen: EventHandler[lambda is_full_screen: [is_full_screen]] + toggle_full_screen: EventHandler[identity_event(bool)] def add_imports(self) -> ImportDict: """Add imports for the Editor component. diff --git a/reflex/components/suneditor/editor.pyi b/reflex/components/suneditor/editor.pyi index f7149a02c..73dd38fdc 100644 --- a/reflex/components/suneditor/editor.pyi +++ b/reflex/components/suneditor/editor.pyi @@ -4,7 +4,7 @@ # This file was generated by `reflex/utils/pyi_generator.py`! # ------------------------------------------------------ import enum -from typing import Any, Dict, List, Literal, Optional, Union, overload +from typing import Any, Dict, List, Literal, Optional, Tuple, Union, overload from reflex.base import Base from reflex.components.component import NoSSRComponent @@ -44,6 +44,11 @@ class EditorOptions(Base): rtl: Optional[bool] button_list: Optional[List[Union[List[str], str]]] +def on_blur_spec(e: Var, content: Var[str]) -> Tuple[Var[str]]: ... +def on_paste_spec( + e: Var, clean_data: Var[str], max_char_count: Var[bool] +) -> Tuple[Var[str], Var[bool]]: ... + class Editor(NoSSRComponent): def add_imports(self) -> ImportDict: ... @overload @@ -122,16 +127,16 @@ class Editor(NoSSRComponent): class_name: Optional[Any] = None, autofocus: Optional[bool] = None, custom_attrs: Optional[Dict[str, Union[Var, str]]] = None, - on_blur: Optional[EventType] = None, - on_change: Optional[EventType] = None, + on_blur: Optional[EventType[str]] = None, + on_change: Optional[EventType[str]] = None, on_click: Optional[EventType[[]]] = None, on_context_menu: Optional[EventType[[]]] = None, - on_copy: Optional[EventType] = None, - on_cut: Optional[EventType] = None, + on_copy: Optional[EventType[[]]] = None, + on_cut: Optional[EventType[[]]] = None, on_double_click: Optional[EventType[[]]] = None, on_focus: Optional[EventType[[]]] = None, - on_input: Optional[EventType] = None, - on_load: Optional[EventType] = None, + on_input: 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, @@ -140,12 +145,11 @@ class Editor(NoSSRComponent): on_mouse_out: Optional[EventType[[]]] = None, on_mouse_over: Optional[EventType[[]]] = None, on_mouse_up: Optional[EventType[[]]] = None, - on_paste: Optional[EventType] = None, - on_resize_editor: Optional[EventType] = None, + 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. diff --git a/reflex/constants/compiler.py b/reflex/constants/compiler.py index 557a92092..318a93783 100644 --- a/reflex/constants/compiler.py +++ b/reflex/constants/compiler.py @@ -132,16 +132,6 @@ class Hooks(SimpleNamespace): } })""" - FRONTEND_ERRORS = f""" - const logFrontendError = (error, info) => {{ - if (process.env.NODE_ENV === "production") {{ - addEvents([Event("{CompileVars.FRONTEND_EXCEPTION_STATE_FULL}.handle_frontend_exception", {{ - stack: error.stack, - }})]) - }} - }} - """ - class MemoizationDisposition(enum.Enum): """The conditions under which a component should be memoized.""" diff --git a/reflex/event.py b/reflex/event.py index 76a465739..cfe40ef9a 100644 --- a/reflex/event.py +++ b/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 diff --git a/reflex/experimental/layout.pyi b/reflex/experimental/layout.pyi index e4c82b351..dcdac5b5d 100644 --- a/reflex/experimental/layout.pyi +++ b/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, diff --git a/reflex/state.py b/reflex/state.py index 0634ad5b7..3422d1ba7 100644 --- a/reflex/state.py +++ b/reflex/state.py @@ -39,6 +39,7 @@ from typing import ( from sqlalchemy.orm import DeclarativeBase from typing_extensions import Self +from reflex import event from reflex.config import get_config from reflex.istate.data import RouterData from reflex.vars.base import ( @@ -2094,7 +2095,8 @@ class State(BaseState): class FrontendEventExceptionState(State): """Substate for handling frontend exceptions.""" - def handle_frontend_exception(self, stack: str) -> None: + @event + def handle_frontend_exception(self, stack: str, component_stack: str) -> None: """Handle frontend exceptions. If a frontend exception handler is provided, it will be called. @@ -2102,6 +2104,7 @@ class FrontendEventExceptionState(State): Args: stack: The stack trace of the exception. + component_stack: The stack trace of the component where the exception occurred. """ app_instance = getattr(prerequisites.get_app(), constants.CompileVars.APP) diff --git a/tests/integration/test_lifespan.py b/tests/integration/test_lifespan.py index f8bcf2397..22c399c07 100644 --- a/tests/integration/test_lifespan.py +++ b/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