فهرست منبع

Add handler type alias and use it in event handlers (#3811)

* Add Handler type alias

* Replace Callable with Handler in elements

* Fix imports and move Handler to events module

* fix confusion between select and change event handler

* use relative import statements

* minor cleanup

* fix mypy issue

* use Handler for generic events

---------

Co-authored-by: Falko Schindler <falko@zauberzeug.com>
Marcus Lim 7 ماه پیش
والد
کامیت
b7b6956f26
49فایلهای تغییر یافته به همراه234 افزوده شده و 176 حذف شده
  1. 3 16
      nicegui/element.py
  2. 4 4
      nicegui/elements/button.py
  3. 4 4
      nicegui/elements/button_dropdown.py
  4. 3 2
      nicegui/elements/carousel.py
  5. 3 2
      nicegui/elements/checkbox.py
  6. 6 6
      nicegui/elements/chip.py
  7. 3 2
      nicegui/elements/choice_element.py
  8. 3 3
      nicegui/elements/codemirror.py
  9. 3 2
      nicegui/elements/color_input.py
  10. 4 4
      nicegui/elements/color_picker.py
  11. 3 2
      nicegui/elements/dark_mode.py
  12. 3 2
      nicegui/elements/date.py
  13. 4 4
      nicegui/elements/echart.py
  14. 3 2
      nicegui/elements/editor.py
  15. 3 2
      nicegui/elements/expansion.py
  16. 2 1
      nicegui/elements/input.py
  17. 4 4
      nicegui/elements/interactive_image.py
  18. 4 4
      nicegui/elements/item.py
  19. 8 8
      nicegui/elements/joystick.py
  20. 12 6
      nicegui/elements/json_editor.py
  21. 4 3
      nicegui/elements/keyboard.py
  22. 3 2
      nicegui/elements/knob.py
  23. 3 2
      nicegui/elements/menu.py
  24. 4 4
      nicegui/elements/mixins/selectable_element.py
  25. 4 4
      nicegui/elements/mixins/value_element.py
  26. 4 4
      nicegui/elements/notification.py
  27. 2 2
      nicegui/elements/number.py
  28. 5 4
      nicegui/elements/pagination.py
  29. 3 3
      nicegui/elements/radio.py
  30. 3 2
      nicegui/elements/range.py
  31. 7 6
      nicegui/elements/scene.py
  32. 11 4
      nicegui/elements/scene_view.py
  33. 5 5
      nicegui/elements/scroll_area.py
  34. 2 2
      nicegui/elements/select.py
  35. 3 2
      nicegui/elements/slider.py
  36. 3 2
      nicegui/elements/splitter.py
  37. 3 2
      nicegui/elements/stepper.py
  38. 3 2
      nicegui/elements/switch.py
  39. 13 7
      nicegui/elements/table.py
  40. 4 3
      nicegui/elements/tabs.py
  41. 3 2
      nicegui/elements/textarea.py
  42. 3 2
      nicegui/elements/time.py
  43. 3 3
      nicegui/elements/toggle.py
  44. 8 8
      nicegui/elements/tree.py
  45. 8 8
      nicegui/elements/upload.py
  46. 24 3
      nicegui/events.py
  47. 3 2
      nicegui/functions/on.py
  48. 8 0
      tests/test_radio_element.py
  49. 3 3
      website/documentation/content/storage_documentation.py

+ 3 - 16
nicegui/element.py

@@ -4,20 +4,7 @@ import inspect
 import re
 import re
 from copy import copy
 from copy import copy
 from pathlib import Path
 from pathlib import Path
-from typing import (
-    TYPE_CHECKING,
-    Any,
-    Callable,
-    ClassVar,
-    Dict,
-    Iterator,
-    List,
-    Optional,
-    Sequence,
-    Union,
-    cast,
-    overload,
-)
+from typing import TYPE_CHECKING, Any, ClassVar, Dict, Iterator, List, Optional, Sequence, Union, cast, overload
 
 
 from typing_extensions import Self
 from typing_extensions import Self
 
 
@@ -334,7 +321,7 @@ class Element(Visibility):
     @overload
     @overload
     def on(self,
     def on(self,
            type: str,  # pylint: disable=redefined-builtin
            type: str,  # pylint: disable=redefined-builtin
-           handler: Optional[Callable[..., Any]] = None,
+           handler: Optional[events.Handler[events.GenericEventArguments]] = None,
            args: Union[None, Sequence[str], Sequence[Optional[Sequence[str]]]] = None,
            args: Union[None, Sequence[str], Sequence[Optional[Sequence[str]]]] = None,
            *,
            *,
            throttle: float = 0.0,
            throttle: float = 0.0,
@@ -345,7 +332,7 @@ class Element(Visibility):
 
 
     def on(self,
     def on(self,
            type: str,  # pylint: disable=redefined-builtin
            type: str,  # pylint: disable=redefined-builtin
-           handler: Optional[Callable[..., Any]] = None,
+           handler: Optional[events.Handler[events.GenericEventArguments]] = None,
            args: Union[None, Sequence[str], Sequence[Optional[Sequence[str]]]] = None,
            args: Union[None, Sequence[str], Sequence[Optional[Sequence[str]]]] = None,
            *,
            *,
            throttle: float = 0.0,
            throttle: float = 0.0,

+ 4 - 4
nicegui/elements/button.py

@@ -1,9 +1,9 @@
 import asyncio
 import asyncio
-from typing import Any, Callable, Optional
+from typing import Optional
 
 
 from typing_extensions import Self
 from typing_extensions import Self
 
 
-from ..events import ClickEventArguments, handle_event
+from ..events import ClickEventArguments, Handler, handle_event
 from .mixins.color_elements import BackgroundColorElement
 from .mixins.color_elements import BackgroundColorElement
 from .mixins.disableable_element import DisableableElement
 from .mixins.disableable_element import DisableableElement
 from .mixins.icon_element import IconElement
 from .mixins.icon_element import IconElement
@@ -14,7 +14,7 @@ class Button(IconElement, TextElement, DisableableElement, BackgroundColorElemen
 
 
     def __init__(self,
     def __init__(self,
                  text: str = '', *,
                  text: str = '', *,
-                 on_click: Optional[Callable[..., Any]] = None,
+                 on_click: Optional[Handler[ClickEventArguments]] = None,
                  color: Optional[str] = 'primary',
                  color: Optional[str] = 'primary',
                  icon: Optional[str] = None,
                  icon: Optional[str] = None,
                  ) -> None:
                  ) -> None:
@@ -37,7 +37,7 @@ class Button(IconElement, TextElement, DisableableElement, BackgroundColorElemen
         if on_click:
         if on_click:
             self.on_click(on_click)
             self.on_click(on_click)
 
 
-    def on_click(self, callback: Callable[..., Any]) -> Self:
+    def on_click(self, callback: Handler[ClickEventArguments]) -> Self:
         """Add a callback to be invoked when the button is clicked."""
         """Add a callback to be invoked when the button is clicked."""
         self.on('click', lambda _: handle_event(callback, ClickEventArguments(sender=self, client=self.client)), [])
         self.on('click', lambda _: handle_event(callback, ClickEventArguments(sender=self, client=self.client)), [])
         return self
         return self

+ 4 - 4
nicegui/elements/button_dropdown.py

@@ -1,6 +1,6 @@
-from typing import Any, Callable, Optional
+from typing import Optional
 
 
-from ..events import ClickEventArguments, handle_event
+from ..events import ClickEventArguments, Handler, ValueChangeEventArguments, handle_event
 from .mixins.color_elements import BackgroundColorElement
 from .mixins.color_elements import BackgroundColorElement
 from .mixins.disableable_element import DisableableElement
 from .mixins.disableable_element import DisableableElement
 from .mixins.icon_element import IconElement
 from .mixins.icon_element import IconElement
@@ -13,8 +13,8 @@ class DropdownButton(IconElement, TextElement, DisableableElement, BackgroundCol
     def __init__(self,
     def __init__(self,
                  text: str = '', *,
                  text: str = '', *,
                  value: bool = False,
                  value: bool = False,
-                 on_value_change: Optional[Callable[..., Any]] = None,
-                 on_click: Optional[Callable[..., Any]] = None,
+                 on_value_change: Optional[Handler[ValueChangeEventArguments]] = None,
+                 on_click: Optional[Handler[ClickEventArguments]] = None,
                  color: Optional[str] = 'primary',
                  color: Optional[str] = 'primary',
                  icon: Optional[str] = None,
                  icon: Optional[str] = None,
                  auto_close: Optional[bool] = False,
                  auto_close: Optional[bool] = False,

+ 3 - 2
nicegui/elements/carousel.py

@@ -1,8 +1,9 @@
 from __future__ import annotations
 from __future__ import annotations
 
 
-from typing import Any, Callable, Optional, Union, cast
+from typing import Any, Optional, Union, cast
 
 
 from ..context import context
 from ..context import context
+from ..events import Handler, ValueChangeEventArguments
 from .mixins.disableable_element import DisableableElement
 from .mixins.disableable_element import DisableableElement
 from .mixins.value_element import ValueElement
 from .mixins.value_element import ValueElement
 
 
@@ -11,7 +12,7 @@ class Carousel(ValueElement):
 
 
     def __init__(self, *,
     def __init__(self, *,
                  value: Union[str, CarouselSlide, None] = None,
                  value: Union[str, CarouselSlide, None] = None,
-                 on_value_change: Optional[Callable[..., Any]] = None,
+                 on_value_change: Optional[Handler[ValueChangeEventArguments]] = None,
                  animated: bool = False,
                  animated: bool = False,
                  arrows: bool = False,
                  arrows: bool = False,
                  navigation: bool = False,
                  navigation: bool = False,

+ 3 - 2
nicegui/elements/checkbox.py

@@ -1,5 +1,6 @@
-from typing import Any, Callable, Optional
+from typing import Optional
 
 
+from ..events import Handler, ValueChangeEventArguments
 from .mixins.disableable_element import DisableableElement
 from .mixins.disableable_element import DisableableElement
 from .mixins.text_element import TextElement
 from .mixins.text_element import TextElement
 from .mixins.value_element import ValueElement
 from .mixins.value_element import ValueElement
@@ -7,7 +8,7 @@ from .mixins.value_element import ValueElement
 
 
 class Checkbox(TextElement, ValueElement, DisableableElement):
 class Checkbox(TextElement, ValueElement, DisableableElement):
 
 
-    def __init__(self, text: str = '', *, value: bool = False, on_change: Optional[Callable[..., Any]] = None) -> None:
+    def __init__(self, text: str = '', *, value: bool = False, on_change: Optional[Handler[ValueChangeEventArguments]] = None) -> None:
         """Checkbox
         """Checkbox
 
 
         This element is based on Quasar's `QCheckbox <https://quasar.dev/vue-components/checkbox>`_ component.
         This element is based on Quasar's `QCheckbox <https://quasar.dev/vue-components/checkbox>`_ component.

+ 6 - 6
nicegui/elements/chip.py

@@ -1,8 +1,8 @@
-from typing import Any, Callable, Optional
+from typing import Optional
 
 
 from typing_extensions import Self
 from typing_extensions import Self
 
 
-from ..events import ClickEventArguments, handle_event
+from ..events import ClickEventArguments, Handler, ValueChangeEventArguments, handle_event
 from .mixins.color_elements import BackgroundColorElement, TextColorElement
 from .mixins.color_elements import BackgroundColorElement, TextColorElement
 from .mixins.disableable_element import DisableableElement
 from .mixins.disableable_element import DisableableElement
 from .mixins.icon_element import IconElement
 from .mixins.icon_element import IconElement
@@ -20,12 +20,12 @@ class Chip(IconElement, ValueElement, TextElement, BackgroundColorElement, TextC
                  icon: Optional[str] = None,
                  icon: Optional[str] = None,
                  color: Optional[str] = 'primary',
                  color: Optional[str] = 'primary',
                  text_color: Optional[str] = None,
                  text_color: Optional[str] = None,
-                 on_click: Optional[Callable[..., Any]] = None,
+                 on_click: Optional[Handler[ClickEventArguments]] = None,
                  selectable: bool = False,
                  selectable: bool = False,
                  selected: bool = False,
                  selected: bool = False,
-                 on_selection_change: Optional[Callable[..., Any]] = None,
+                 on_selection_change: Optional[Handler[ValueChangeEventArguments]] = None,
                  removable: bool = False,
                  removable: bool = False,
-                 on_value_change: Optional[Callable[..., Any]] = None,
+                 on_value_change: Optional[Handler[ValueChangeEventArguments]] = None,
                  ) -> None:
                  ) -> None:
         """Chip
         """Chip
 
 
@@ -52,7 +52,7 @@ class Chip(IconElement, ValueElement, TextElement, BackgroundColorElement, TextC
         if on_click:
         if on_click:
             self.on_click(on_click)
             self.on_click(on_click)
 
 
-    def on_click(self, callback: Callable[..., Any]) -> Self:
+    def on_click(self, callback: Handler[ClickEventArguments]) -> Self:
         """Add a callback to be invoked when the chip is clicked."""
         """Add a callback to be invoked when the chip is clicked."""
         self._props['clickable'] = True
         self._props['clickable'] = True
         self.update()
         self.update()

+ 3 - 2
nicegui/elements/choice_element.py

@@ -1,5 +1,6 @@
-from typing import Any, Callable, Dict, List, Optional, Union
+from typing import Any, Dict, List, Optional, Union
 
 
+from ..events import Handler, ValueChangeEventArguments
 from .mixins.value_element import ValueElement
 from .mixins.value_element import ValueElement
 
 
 
 
@@ -9,7 +10,7 @@ class ChoiceElement(ValueElement):
                  tag: Optional[str] = None,
                  tag: Optional[str] = None,
                  options: Union[List, Dict],
                  options: Union[List, Dict],
                  value: Any,
                  value: Any,
-                 on_change: Optional[Callable[..., Any]] = None,
+                 on_change: Optional[Handler[ValueChangeEventArguments]] = None,
                  ) -> None:
                  ) -> None:
         self.options = options
         self.options = options
         self._values: List[str] = []
         self._values: List[str] = []

+ 3 - 3
nicegui/elements/codemirror.py

@@ -1,9 +1,9 @@
 from pathlib import Path
 from pathlib import Path
-from typing import Any, Callable, List, Literal, Optional, get_args
+from typing import Callable, List, Literal, Optional, get_args
 
 
 from nicegui.elements.mixins.disableable_element import DisableableElement
 from nicegui.elements.mixins.disableable_element import DisableableElement
 from nicegui.elements.mixins.value_element import ValueElement
 from nicegui.elements.mixins.value_element import ValueElement
-from nicegui.events import GenericEventArguments
+from nicegui.events import GenericEventArguments, Handler, ValueChangeEventArguments
 
 
 SUPPORTED_LANGUAGES = Literal[
 SUPPORTED_LANGUAGES = Literal[
     'Angular Template',
     'Angular Template',
@@ -253,7 +253,7 @@ class CodeMirror(ValueElement, DisableableElement, component='codemirror.js'):
         self,
         self,
         value: str = '',
         value: str = '',
         *,
         *,
-        on_change: Optional[Callable[..., Any]] = None,
+        on_change: Optional[Handler[ValueChangeEventArguments]] = None,
         language: Optional[SUPPORTED_LANGUAGES] = None,
         language: Optional[SUPPORTED_LANGUAGES] = None,
         theme: SUPPORTED_THEMES = 'basicLight',
         theme: SUPPORTED_THEMES = 'basicLight',
         indent: str = ' ' * 4,
         indent: str = ' ' * 4,

+ 3 - 2
nicegui/elements/color_input.py

@@ -1,5 +1,6 @@
-from typing import Any, Callable, Optional
+from typing import Any, Optional
 
 
+from ..events import Handler, ValueChangeEventArguments
 from .button import Button as button
 from .button import Button as button
 from .color_picker import ColorPicker as color_picker
 from .color_picker import ColorPicker as color_picker
 from .mixins.disableable_element import DisableableElement
 from .mixins.disableable_element import DisableableElement
@@ -13,7 +14,7 @@ class ColorInput(ValueElement, DisableableElement):
                  label: Optional[str] = None, *,
                  label: Optional[str] = None, *,
                  placeholder: Optional[str] = None,
                  placeholder: Optional[str] = None,
                  value: str = '',
                  value: str = '',
-                 on_change: Optional[Callable[..., Any]] = None,
+                 on_change: Optional[Handler[ValueChangeEventArguments]] = None,
                  preview: bool = False,
                  preview: bool = False,
                  ) -> None:
                  ) -> None:
         """Color Input
         """Color Input

+ 4 - 4
nicegui/elements/color_picker.py

@@ -1,16 +1,16 @@
-from typing import Any, Callable, Optional
+from typing import Optional
 
 
 from typing_extensions import Self
 from typing_extensions import Self
 
 
 from ..element import Element
 from ..element import Element
-from ..events import ColorPickEventArguments, GenericEventArguments, handle_event
+from ..events import ColorPickEventArguments, GenericEventArguments, Handler, handle_event
 from .menu import Menu
 from .menu import Menu
 
 
 
 
 class ColorPicker(Menu):
 class ColorPicker(Menu):
 
 
     def __init__(self, *,
     def __init__(self, *,
-                 on_pick: Optional[Callable[..., Any]] = None,
+                 on_pick: Optional[Handler[ColorPickEventArguments]] = None,
                  value: bool = False,
                  value: bool = False,
                  ) -> None:
                  ) -> None:
         """Color Picker
         """Color Picker
@@ -36,7 +36,7 @@ class ColorPicker(Menu):
         """
         """
         self.q_color.props(f'model-value="{color}"')
         self.q_color.props(f'model-value="{color}"')
 
 
-    def on_pick(self, callback: Callable[..., Any]) -> Self:
+    def on_pick(self, callback: Handler[ColorPickEventArguments]) -> Self:
         """Add a callback to be invoked when a color is picked."""
         """Add a callback to be invoked when a color is picked."""
         self._pick_handlers.append(callback)
         self._pick_handlers.append(callback)
         return self
         return self

+ 3 - 2
nicegui/elements/dark_mode.py

@@ -1,12 +1,13 @@
-from typing import Any, Callable, Optional
+from typing import Optional
 
 
+from ..events import Handler, ValueChangeEventArguments
 from .mixins.value_element import ValueElement
 from .mixins.value_element import ValueElement
 
 
 
 
 class DarkMode(ValueElement, component='dark_mode.js'):
 class DarkMode(ValueElement, component='dark_mode.js'):
     VALUE_PROP = 'value'
     VALUE_PROP = 'value'
 
 
-    def __init__(self, value: Optional[bool] = False, *, on_change: Optional[Callable[..., Any]] = None) -> None:
+    def __init__(self, value: Optional[bool] = False, *, on_change: Optional[Handler[ValueChangeEventArguments]] = None) -> None:
         """Dark mode
         """Dark mode
 
 
         You can use this element to enable, disable or toggle dark mode on the page.
         You can use this element to enable, disable or toggle dark mode on the page.

+ 3 - 2
nicegui/elements/date.py

@@ -1,5 +1,6 @@
-from typing import Any, Callable, Dict, List, Optional, Union
+from typing import Dict, List, Optional, Union
 
 
+from ..events import Handler, ValueChangeEventArguments
 from .mixins.disableable_element import DisableableElement
 from .mixins.disableable_element import DisableableElement
 from .mixins.value_element import ValueElement
 from .mixins.value_element import ValueElement
 
 
@@ -12,7 +13,7 @@ class Date(ValueElement, DisableableElement):
                  ] = None,
                  ] = None,
                  *,
                  *,
                  mask: str = 'YYYY-MM-DD',
                  mask: str = 'YYYY-MM-DD',
-                 on_change: Optional[Callable[..., Any]] = None) -> None:
+                 on_change: Optional[Handler[ValueChangeEventArguments]] = None) -> None:
         """Date Input
         """Date Input
 
 
         This element is based on Quasar's `QDate <https://quasar.dev/vue-components/date>`_ component.
         This element is based on Quasar's `QDate <https://quasar.dev/vue-components/date>`_ component.

+ 4 - 4
nicegui/elements/echart.py

@@ -1,11 +1,11 @@
-from typing import Any, Callable, Dict, Optional
+from typing import Callable, Dict, Optional
 
 
 from typing_extensions import Self
 from typing_extensions import Self
 
 
 from .. import optional_features
 from .. import optional_features
 from ..awaitable_response import AwaitableResponse
 from ..awaitable_response import AwaitableResponse
 from ..element import Element
 from ..element import Element
-from ..events import EChartPointClickEventArguments, GenericEventArguments, handle_event
+from ..events import EChartPointClickEventArguments, GenericEventArguments, Handler, handle_event
 
 
 try:
 try:
     from pyecharts.charts.base import default, json
     from pyecharts.charts.base import default, json
@@ -19,7 +19,7 @@ except ImportError:
 
 
 class EChart(Element, component='echart.js', dependencies=['lib/echarts/echarts.min.js', 'lib/echarts-gl/echarts-gl.min.js']):
 class EChart(Element, component='echart.js', dependencies=['lib/echarts/echarts.min.js', 'lib/echarts-gl/echarts-gl.min.js']):
 
 
-    def __init__(self, options: Dict, on_point_click: Optional[Callable] = None, *, enable_3d: bool = False) -> None:
+    def __init__(self, options: Dict, on_point_click: Optional[Handler[EChartPointClickEventArguments]] = None, *, enable_3d: bool = False) -> None:
         """Apache EChart
         """Apache EChart
 
 
         An element to create a chart using `ECharts <https://echarts.apache.org/>`_.
         An element to create a chart using `ECharts <https://echarts.apache.org/>`_.
@@ -38,7 +38,7 @@ class EChart(Element, component='echart.js', dependencies=['lib/echarts/echarts.
         if on_point_click:
         if on_point_click:
             self.on_point_click(on_point_click)
             self.on_point_click(on_point_click)
 
 
-    def on_point_click(self, callback: Callable[..., Any]) -> Self:
+    def on_point_click(self, callback: Handler[EChartPointClickEventArguments]) -> Self:
         """Add a callback to be invoked when a point is clicked."""
         """Add a callback to be invoked when a point is clicked."""
         def handle_point_click(e: GenericEventArguments) -> None:
         def handle_point_click(e: GenericEventArguments) -> None:
             handle_event(callback, EChartPointClickEventArguments(
             handle_event(callback, EChartPointClickEventArguments(

+ 3 - 2
nicegui/elements/editor.py

@@ -1,5 +1,6 @@
-from typing import Any, Callable, Optional
+from typing import Any, Optional
 
 
+from ..events import Handler, ValueChangeEventArguments
 from .mixins.disableable_element import DisableableElement
 from .mixins.disableable_element import DisableableElement
 from .mixins.value_element import ValueElement
 from .mixins.value_element import ValueElement
 
 
@@ -12,7 +13,7 @@ class Editor(ValueElement, DisableableElement, component='editor.js'):
                  *,
                  *,
                  placeholder: Optional[str] = None,
                  placeholder: Optional[str] = None,
                  value: str = '',
                  value: str = '',
-                 on_change: Optional[Callable[..., Any]] = None,
+                 on_change: Optional[Handler[ValueChangeEventArguments]] = None,
                  ) -> None:
                  ) -> None:
         """Editor
         """Editor
 
 

+ 3 - 2
nicegui/elements/expansion.py

@@ -1,5 +1,6 @@
-from typing import Any, Callable, Optional
+from typing import Optional
 
 
+from ..events import Handler, ValueChangeEventArguments
 from .mixins.disableable_element import DisableableElement
 from .mixins.disableable_element import DisableableElement
 from .mixins.icon_element import IconElement
 from .mixins.icon_element import IconElement
 from .mixins.text_element import TextElement
 from .mixins.text_element import TextElement
@@ -14,7 +15,7 @@ class Expansion(IconElement, TextElement, ValueElement, DisableableElement):
                  icon: Optional[str] = None,
                  icon: Optional[str] = None,
                  group: Optional[str] = None,
                  group: Optional[str] = None,
                  value: bool = False,
                  value: bool = False,
-                 on_value_change: Optional[Callable[..., Any]] = None
+                 on_value_change: Optional[Handler[ValueChangeEventArguments]] = None
                  ) -> None:
                  ) -> None:
         """Expansion Element
         """Expansion Element
 
 

+ 2 - 1
nicegui/elements/input.py

@@ -1,5 +1,6 @@
 from typing import Any, Callable, Dict, List, Optional, Union
 from typing import Any, Callable, Dict, List, Optional, Union
 
 
+from ..events import Handler, ValueChangeEventArguments
 from .icon import Icon
 from .icon import Icon
 from .mixins.disableable_element import DisableableElement
 from .mixins.disableable_element import DisableableElement
 from .mixins.validation_element import ValidationElement
 from .mixins.validation_element import ValidationElement
@@ -15,7 +16,7 @@ class Input(ValidationElement, DisableableElement, component='input.js'):
                  value: str = '',
                  value: str = '',
                  password: bool = False,
                  password: bool = False,
                  password_toggle_button: bool = False,
                  password_toggle_button: bool = False,
-                 on_change: Optional[Callable[..., Any]] = None,
+                 on_change: Optional[Handler[ValueChangeEventArguments]] = None,
                  autocomplete: Optional[List[str]] = None,
                  autocomplete: Optional[List[str]] = None,
                  validation: Optional[Union[Callable[..., Optional[str]], Dict[str, Callable[..., bool]]]] = None,
                  validation: Optional[Union[Callable[..., Optional[str]], Dict[str, Callable[..., bool]]]] = None,
                  ) -> None:
                  ) -> None:

+ 4 - 4
nicegui/elements/interactive_image.py

@@ -2,12 +2,12 @@ from __future__ import annotations
 
 
 import time
 import time
 from pathlib import Path
 from pathlib import Path
-from typing import Any, Callable, List, Optional, Tuple, Union, cast
+from typing import List, Optional, Tuple, Union, cast
 
 
 from typing_extensions import Self
 from typing_extensions import Self
 
 
 from .. import optional_features
 from .. import optional_features
-from ..events import GenericEventArguments, MouseEventArguments, handle_event
+from ..events import GenericEventArguments, Handler, MouseEventArguments, handle_event
 from .image import pil_to_base64
 from .image import pil_to_base64
 from .mixins.content_element import ContentElement
 from .mixins.content_element import ContentElement
 from .mixins.source_element import SourceElement
 from .mixins.source_element import SourceElement
@@ -27,7 +27,7 @@ class InteractiveImage(SourceElement, ContentElement, component='interactive_ima
                  source: Union[str, Path, 'PIL_Image'] = '', *,  # noqa: UP037
                  source: Union[str, Path, 'PIL_Image'] = '', *,  # noqa: UP037
                  content: str = '',
                  content: str = '',
                  size: Optional[Tuple[float, float]] = None,
                  size: Optional[Tuple[float, float]] = None,
-                 on_mouse: Optional[Callable[..., Any]] = None,
+                 on_mouse: Optional[Handler[MouseEventArguments]] = None,
                  events: List[str] = ['click'],  # noqa: B006
                  events: List[str] = ['click'],  # noqa: B006
                  cross: Union[bool, str] = False,
                  cross: Union[bool, str] = False,
                  ) -> None:
                  ) -> None:
@@ -67,7 +67,7 @@ class InteractiveImage(SourceElement, ContentElement, component='interactive_ima
     def set_source(self, source: Union[str, Path, 'PIL_Image']) -> None:  # noqa: UP037
     def set_source(self, source: Union[str, Path, 'PIL_Image']) -> None:  # noqa: UP037
         return super().set_source(source)
         return super().set_source(source)
 
 
-    def on_mouse(self, on_mouse: Callable[..., Any]) -> Self:
+    def on_mouse(self, on_mouse: Handler[MouseEventArguments]) -> Self:
         """Add a callback to be invoked when a mouse event occurs."""
         """Add a callback to be invoked when a mouse event occurs."""
         def handle_mouse(e: GenericEventArguments) -> None:
         def handle_mouse(e: GenericEventArguments) -> None:
             args = cast(dict, e.args)
             args = cast(dict, e.args)

+ 4 - 4
nicegui/elements/item.py

@@ -1,15 +1,15 @@
-from typing import Any, Callable, Optional
+from typing import Optional
 
 
 from typing_extensions import Self
 from typing_extensions import Self
 
 
-from ..events import ClickEventArguments, handle_event
+from ..events import ClickEventArguments, Handler, handle_event
 from .mixins.disableable_element import DisableableElement
 from .mixins.disableable_element import DisableableElement
 from .mixins.text_element import TextElement
 from .mixins.text_element import TextElement
 
 
 
 
 class Item(DisableableElement):
 class Item(DisableableElement):
 
 
-    def __init__(self, text: str = '', *, on_click: Optional[Callable[..., Any]] = None) -> None:
+    def __init__(self, text: str = '', *, on_click: Optional[Handler[ClickEventArguments]] = None) -> None:
         """List Item
         """List Item
 
 
         Creates a clickable list item based on Quasar's
         Creates a clickable list item based on Quasar's
@@ -30,7 +30,7 @@ class Item(DisableableElement):
             with self:
             with self:
                 ItemSection(text=text)
                 ItemSection(text=text)
 
 
-    def on_click(self, callback: Callable[..., Any]) -> Self:
+    def on_click(self, callback: Handler[ClickEventArguments]) -> Self:
         """Add a callback to be invoked when the List Item is clicked."""
         """Add a callback to be invoked when the List Item is clicked."""
         self._props['clickable'] = True  # idempotent
         self._props['clickable'] = True  # idempotent
         self.on('click', lambda _: handle_event(callback, ClickEventArguments(sender=self, client=self.client)))
         self.on('click', lambda _: handle_event(callback, ClickEventArguments(sender=self, client=self.client)))

+ 8 - 8
nicegui/elements/joystick.py

@@ -1,17 +1,17 @@
-from typing import Any, Callable, Optional
+from typing import Any, Optional
 
 
 from typing_extensions import Self
 from typing_extensions import Self
 
 
 from ..element import Element
 from ..element import Element
-from ..events import GenericEventArguments, JoystickEventArguments, handle_event
+from ..events import GenericEventArguments, Handler, JoystickEventArguments, handle_event
 
 
 
 
 class Joystick(Element, component='joystick.vue', dependencies=['lib/nipplejs/nipplejs.js']):
 class Joystick(Element, component='joystick.vue', dependencies=['lib/nipplejs/nipplejs.js']):
 
 
     def __init__(self, *,
     def __init__(self, *,
-                 on_start: Optional[Callable[..., Any]] = None,
-                 on_move: Optional[Callable[..., Any]] = None,
-                 on_end: Optional[Callable[..., Any]] = None,
+                 on_start: Optional[Handler[JoystickEventArguments]] = None,
+                 on_move: Optional[Handler[JoystickEventArguments]] = None,
+                 on_end: Optional[Handler[JoystickEventArguments]] = None,
                  throttle: float = 0.05,
                  throttle: float = 0.05,
                  ** options: Any) -> None:
                  ** options: Any) -> None:
         """Joystick
         """Joystick
@@ -61,17 +61,17 @@ class Joystick(Element, component='joystick.vue', dependencies=['lib/nipplejs/ni
         self.on('move', handle_move, ['data'], throttle=throttle)
         self.on('move', handle_move, ['data'], throttle=throttle)
         self.on('end', handle_end, [])
         self.on('end', handle_end, [])
 
 
-    def on_start(self, callback: Callable[..., Any]) -> Self:
+    def on_start(self, callback: Handler[JoystickEventArguments]) -> Self:
         """Add a callback to be invoked when the user touches the joystick."""
         """Add a callback to be invoked when the user touches the joystick."""
         self._start_handlers.append(callback)
         self._start_handlers.append(callback)
         return self
         return self
 
 
-    def on_move(self, callback: Callable[..., Any]) -> Self:
+    def on_move(self, callback: Handler[JoystickEventArguments]) -> Self:
         """Add a callback to be invoked when the user moves the joystick."""
         """Add a callback to be invoked when the user moves the joystick."""
         self._move_handlers.append(callback)
         self._move_handlers.append(callback)
         return self
         return self
 
 
-    def on_end(self, callback: Callable[..., Any]) -> Self:
+    def on_end(self, callback: Handler[JoystickEventArguments]) -> Self:
         """Add a callback to be invoked when the user releases the joystick."""
         """Add a callback to be invoked when the user releases the joystick."""
         self._end_handlers.append(callback)
         self._end_handlers.append(callback)
         return self
         return self

+ 12 - 6
nicegui/elements/json_editor.py

@@ -1,18 +1,24 @@
-from typing import Any, Callable, Dict, Optional
+from typing import Dict, Optional
 
 
 from typing_extensions import Self
 from typing_extensions import Self
 
 
 from ..awaitable_response import AwaitableResponse
 from ..awaitable_response import AwaitableResponse
 from ..element import Element
 from ..element import Element
-from ..events import GenericEventArguments, JsonEditorChangeEventArguments, JsonEditorSelectEventArguments, handle_event
+from ..events import (
+    GenericEventArguments,
+    Handler,
+    JsonEditorChangeEventArguments,
+    JsonEditorSelectEventArguments,
+    handle_event,
+)
 
 
 
 
 class JsonEditor(Element, component='json_editor.js', dependencies=['lib/vanilla-jsoneditor/standalone.js']):
 class JsonEditor(Element, component='json_editor.js', dependencies=['lib/vanilla-jsoneditor/standalone.js']):
 
 
     def __init__(self,
     def __init__(self,
                  properties: Dict, *,
                  properties: Dict, *,
-                 on_select: Optional[Callable] = None,
-                 on_change: Optional[Callable] = None,
+                 on_select: Optional[Handler[JsonEditorSelectEventArguments]] = None,
+                 on_change: Optional[Handler[JsonEditorChangeEventArguments]] = None,
                  ) -> None:
                  ) -> None:
         """JSONEditor
         """JSONEditor
 
 
@@ -33,14 +39,14 @@ class JsonEditor(Element, component='json_editor.js', dependencies=['lib/vanilla
         if on_change:
         if on_change:
             self.on_change(on_change)
             self.on_change(on_change)
 
 
-    def on_change(self, callback: Callable[..., Any]) -> Self:
+    def on_change(self, callback: Handler[JsonEditorChangeEventArguments]) -> Self:
         """Add a callback to be invoked when the content changes."""
         """Add a callback to be invoked when the content changes."""
         def handle_on_change(e: GenericEventArguments) -> None:
         def handle_on_change(e: GenericEventArguments) -> None:
             handle_event(callback, JsonEditorChangeEventArguments(sender=self, client=self.client, **e.args))
             handle_event(callback, JsonEditorChangeEventArguments(sender=self, client=self.client, **e.args))
         self.on('change', handle_on_change, ['content', 'errors'])
         self.on('change', handle_on_change, ['content', 'errors'])
         return self
         return self
 
 
-    def on_select(self, callback: Callable[..., Any]) -> Self:
+    def on_select(self, callback: Handler[JsonEditorSelectEventArguments]) -> Self:
         """Add a callback to be invoked when some of the content has been selected."""
         """Add a callback to be invoked when some of the content has been selected."""
         def handle_on_select(e: GenericEventArguments) -> None:
         def handle_on_select(e: GenericEventArguments) -> None:
             handle_event(callback, JsonEditorSelectEventArguments(sender=self, client=self.client, **e.args))
             handle_event(callback, JsonEditorSelectEventArguments(sender=self, client=self.client, **e.args))

+ 4 - 3
nicegui/elements/keyboard.py

@@ -1,4 +1,4 @@
-from typing import Any, Callable, List, Literal, Optional
+from typing import List, Literal, Optional
 
 
 from typing_extensions import Self
 from typing_extensions import Self
 
 
@@ -6,6 +6,7 @@ from ..binding import BindableProperty
 from ..element import Element
 from ..element import Element
 from ..events import (
 from ..events import (
     GenericEventArguments,
     GenericEventArguments,
+    Handler,
     KeyboardAction,
     KeyboardAction,
     KeyboardKey,
     KeyboardKey,
     KeyboardModifiers,
     KeyboardModifiers,
@@ -18,7 +19,7 @@ class Keyboard(Element, component='keyboard.js'):
     active = BindableProperty()
     active = BindableProperty()
 
 
     def __init__(self,
     def __init__(self,
-                 on_key: Optional[Callable[..., Any]] = None, *,
+                 on_key: Optional[Handler[KeyEventArguments]] = None, *,
                  active: bool = True,
                  active: bool = True,
                  repeating: bool = True,
                  repeating: bool = True,
                  ignore: List[Literal['input', 'select', 'button', 'textarea']] =
                  ignore: List[Literal['input', 'select', 'button', 'textarea']] =
@@ -97,7 +98,7 @@ class Keyboard(Element, component='keyboard.js'):
         for handler in self._key_handlers:
         for handler in self._key_handlers:
             handle_event(handler, arguments)
             handle_event(handler, arguments)
 
 
-    def on_key(self, handler: Callable[..., Any]) -> Self:
+    def on_key(self, handler: Handler[KeyEventArguments]) -> Self:
         """Add a callback to be invoked when keyboard events occur."""
         """Add a callback to be invoked when keyboard events occur."""
         self._key_handlers.append(handler)
         self._key_handlers.append(handler)
         return self
         return self

+ 3 - 2
nicegui/elements/knob.py

@@ -1,5 +1,6 @@
-from typing import Any, Callable, Optional
+from typing import Optional
 
 
+from ..events import Handler, ValueChangeEventArguments
 from .label import Label
 from .label import Label
 from .mixins.color_elements import TextColorElement
 from .mixins.color_elements import TextColorElement
 from .mixins.disableable_element import DisableableElement
 from .mixins.disableable_element import DisableableElement
@@ -19,7 +20,7 @@ class Knob(ValueElement, DisableableElement, TextColorElement):
                  track_color: Optional[str] = None,
                  track_color: Optional[str] = None,
                  size: Optional[str] = None,
                  size: Optional[str] = None,
                  show_value: bool = False,
                  show_value: bool = False,
-                 on_change: Optional[Callable[..., Any]] = None,
+                 on_change: Optional[Handler[ValueChangeEventArguments]] = None,
                  ) -> None:
                  ) -> None:
         """Knob
         """Knob
 
 

+ 3 - 2
nicegui/elements/menu.py

@@ -1,6 +1,7 @@
-from typing import Any, Callable, Optional, Union
+from typing import Optional, Union
 
 
 from ..element import Element
 from ..element import Element
+from ..events import ClickEventArguments, Handler
 from .context_menu import ContextMenu
 from .context_menu import ContextMenu
 from .item import Item
 from .item import Item
 from .mixins.value_element import ValueElement
 from .mixins.value_element import ValueElement
@@ -43,7 +44,7 @@ class MenuItem(Item):
 
 
     def __init__(self,
     def __init__(self,
                  text: str = '',
                  text: str = '',
-                 on_click: Optional[Callable[..., Any]] = None, *,
+                 on_click: Optional[Handler[ClickEventArguments]] = None, *,
                  auto_close: bool = True,
                  auto_close: bool = True,
                  ) -> None:
                  ) -> None:
         """Menu Item
         """Menu Item

+ 4 - 4
nicegui/elements/mixins/selectable_element.py

@@ -4,7 +4,7 @@ from typing_extensions import Self
 
 
 from ...binding import BindableProperty, bind, bind_from, bind_to
 from ...binding import BindableProperty, bind, bind_from, bind_to
 from ...element import Element
 from ...element import Element
-from ...events import ValueChangeEventArguments, handle_event
+from ...events import Handler, ValueChangeEventArguments, handle_event
 
 
 
 
 class SelectableElement(Element):
 class SelectableElement(Element):
@@ -14,7 +14,7 @@ class SelectableElement(Element):
     def __init__(self, *,
     def __init__(self, *,
                  selectable: bool,
                  selectable: bool,
                  selected: bool,
                  selected: bool,
-                 on_selection_change: Optional[Callable[..., Any]],
+                 on_selection_change: Optional[Handler[ValueChangeEventArguments]] = None,
                  **kwargs: Any) -> None:
                  **kwargs: Any) -> None:
         super().__init__(**kwargs)
         super().__init__(**kwargs)
         if not selectable:
         if not selectable:
@@ -27,11 +27,11 @@ class SelectableElement(Element):
         self.set_selected(selected)
         self.set_selected(selected)
         self.on('update:selected', lambda e: self.set_selected(e.args))
         self.on('update:selected', lambda e: self.set_selected(e.args))
 
 
-        self._selection_change_handlers: List[Callable[..., Any]] = []
+        self._selection_change_handlers: List[Handler[ValueChangeEventArguments]] = []
         if on_selection_change:
         if on_selection_change:
             self.on_selection_change(on_selection_change)
             self.on_selection_change(on_selection_change)
 
 
-    def on_selection_change(self, callback: Callable[..., Any]) -> Self:
+    def on_selection_change(self, callback: Handler[ValueChangeEventArguments]) -> Self:
         """Add a callback to be invoked when the selection state changes."""
         """Add a callback to be invoked when the selection state changes."""
         self._selection_change_handlers.append(callback)
         self._selection_change_handlers.append(callback)
         return self
         return self

+ 4 - 4
nicegui/elements/mixins/value_element.py

@@ -4,7 +4,7 @@ from typing_extensions import Self
 
 
 from ...binding import BindableProperty, bind, bind_from, bind_to
 from ...binding import BindableProperty, bind, bind_from, bind_to
 from ...element import Element
 from ...element import Element
-from ...events import GenericEventArguments, ValueChangeEventArguments, handle_event
+from ...events import GenericEventArguments, Handler, ValueChangeEventArguments, handle_event
 
 
 
 
 class ValueElement(Element):
 class ValueElement(Element):
@@ -24,7 +24,7 @@ class ValueElement(Element):
 
 
     def __init__(self, *,
     def __init__(self, *,
                  value: Any,
                  value: Any,
-                 on_value_change: Optional[Callable[..., Any]],
+                 on_value_change: Optional[Handler[ValueChangeEventArguments]] = None,
                  throttle: float = 0,
                  throttle: float = 0,
                  **kwargs: Any,
                  **kwargs: Any,
                  ) -> None:
                  ) -> None:
@@ -33,7 +33,7 @@ class ValueElement(Element):
         self.set_value(value)
         self.set_value(value)
         self._props[self.VALUE_PROP] = self._value_to_model_value(value)
         self._props[self.VALUE_PROP] = self._value_to_model_value(value)
         self._props['loopback'] = self.LOOPBACK
         self._props['loopback'] = self.LOOPBACK
-        self._change_handlers: List[Callable[..., Any]] = [on_value_change] if on_value_change else []
+        self._change_handlers: List[Handler[ValueChangeEventArguments]] = [on_value_change] if on_value_change else []
 
 
         def handle_change(e: GenericEventArguments) -> None:
         def handle_change(e: GenericEventArguments) -> None:
             self._send_update_on_value_change = self.LOOPBACK is True
             self._send_update_on_value_change = self.LOOPBACK is True
@@ -41,7 +41,7 @@ class ValueElement(Element):
             self._send_update_on_value_change = True
             self._send_update_on_value_change = True
         self.on(f'update:{self.VALUE_PROP}', handle_change, [None], throttle=throttle)
         self.on(f'update:{self.VALUE_PROP}', handle_change, [None], throttle=throttle)
 
 
-    def on_value_change(self, callback: Callable[..., Any]) -> Self:
+    def on_value_change(self, callback: Handler[ValueChangeEventArguments]) -> Self:
         """Add a callback to be invoked when the value changes."""
         """Add a callback to be invoked when the value changes."""
         self._change_handlers.append(callback)
         self._change_handlers.append(callback)
         return self
         return self

+ 4 - 4
nicegui/elements/notification.py

@@ -1,11 +1,11 @@
 import asyncio
 import asyncio
-from typing import Any, Callable, Dict, Literal, Optional, Union
+from typing import Any, Dict, Literal, Optional, Union
 
 
 from typing_extensions import Self
 from typing_extensions import Self
 
 
 from ..context import context
 from ..context import context
 from ..element import Element
 from ..element import Element
-from ..events import UiEventArguments, handle_event
+from ..events import Handler, UiEventArguments, handle_event
 
 
 NotificationPosition = Literal[
 NotificationPosition = Literal[
     'top-left',
     'top-left',
@@ -40,7 +40,7 @@ class Notification(Element, component='notification.js'):
                  icon: Optional[str] = None,
                  icon: Optional[str] = None,
                  spinner: bool = False,
                  spinner: bool = False,
                  timeout: Optional[float] = 5.0,
                  timeout: Optional[float] = 5.0,
-                 on_dismiss: Optional[Callable] = None,
+                 on_dismiss: Optional[Handler[UiEventArguments]] = None,
                  options: Optional[Dict] = None,
                  options: Optional[Dict] = None,
                  **kwargs: Any,
                  **kwargs: Any,
                  ) -> None:
                  ) -> None:
@@ -188,7 +188,7 @@ class Notification(Element, component='notification.js'):
         self._props['options']['closeBtn'] = value
         self._props['options']['closeBtn'] = value
         self.update()
         self.update()
 
 
-    def on_dismiss(self, callback: Callable[..., Any]) -> Self:
+    def on_dismiss(self, callback: Handler[UiEventArguments]) -> Self:
         """Add a callback to be invoked when the notification is dismissed."""
         """Add a callback to be invoked when the notification is dismissed."""
         self.on('dismiss', lambda _: handle_event(callback, UiEventArguments(sender=self, client=self.client)), [])
         self.on('dismiss', lambda _: handle_event(callback, UiEventArguments(sender=self, client=self.client)), [])
         return self
         return self

+ 2 - 2
nicegui/elements/number.py

@@ -1,6 +1,6 @@
 from typing import Any, Callable, Dict, Optional, Union
 from typing import Any, Callable, Dict, Optional, Union
 
 
-from ..events import GenericEventArguments
+from ..events import GenericEventArguments, Handler, ValueChangeEventArguments
 from .mixins.disableable_element import DisableableElement
 from .mixins.disableable_element import DisableableElement
 from .mixins.validation_element import ValidationElement
 from .mixins.validation_element import ValidationElement
 
 
@@ -19,7 +19,7 @@ class Number(ValidationElement, DisableableElement):
                  prefix: Optional[str] = None,
                  prefix: Optional[str] = None,
                  suffix: Optional[str] = None,
                  suffix: Optional[str] = None,
                  format: Optional[str] = None,  # pylint: disable=redefined-builtin
                  format: Optional[str] = None,  # pylint: disable=redefined-builtin
-                 on_change: Optional[Callable[..., Any]] = None,
+                 on_change: Optional[Handler[ValueChangeEventArguments]] = None,
                  validation: Optional[Union[Callable[..., Optional[str]], Dict[str, Callable[..., bool]]]] = None,
                  validation: Optional[Union[Callable[..., Optional[str]], Dict[str, Callable[..., bool]]]] = None,
                  ) -> None:
                  ) -> None:
         """Number Input
         """Number Input

+ 5 - 4
nicegui/elements/pagination.py

@@ -1,7 +1,8 @@
-from typing import Any, Callable, Optional
+from typing import Optional
 
 
-from nicegui.elements.mixins.disableable_element import DisableableElement
-from nicegui.elements.mixins.value_element import ValueElement
+from ..events import Handler, ValueChangeEventArguments
+from .mixins.disableable_element import DisableableElement
+from .mixins.value_element import ValueElement
 
 
 
 
 class Pagination(ValueElement, DisableableElement):
 class Pagination(ValueElement, DisableableElement):
@@ -10,7 +11,7 @@ class Pagination(ValueElement, DisableableElement):
                  min: int, max: int, *,  # pylint: disable=redefined-builtin
                  min: int, max: int, *,  # pylint: disable=redefined-builtin
                  direction_links: bool = False,
                  direction_links: bool = False,
                  value: Optional[int] = ...,  # type: ignore
                  value: Optional[int] = ...,  # type: ignore
-                 on_change: Optional[Callable[..., Any]] = None) -> None:
+                 on_change: Optional[Handler[ValueChangeEventArguments]] = None) -> None:
         """Pagination
         """Pagination
 
 
         A pagination element wrapping Quasar's `QPagination <https://quasar.dev/vue-components/pagination>`_ component.
         A pagination element wrapping Quasar's `QPagination <https://quasar.dev/vue-components/pagination>`_ component.

+ 3 - 3
nicegui/elements/radio.py

@@ -1,6 +1,6 @@
-from typing import Any, Callable, Dict, List, Optional, Union
+from typing import Any, Dict, List, Optional, Union
 
 
-from ..events import GenericEventArguments
+from ..events import GenericEventArguments, Handler, ValueChangeEventArguments
 from .choice_element import ChoiceElement
 from .choice_element import ChoiceElement
 from .mixins.disableable_element import DisableableElement
 from .mixins.disableable_element import DisableableElement
 
 
@@ -10,7 +10,7 @@ class Radio(ChoiceElement, DisableableElement):
     def __init__(self,
     def __init__(self,
                  options: Union[List, Dict], *,
                  options: Union[List, Dict], *,
                  value: Any = None,
                  value: Any = None,
-                 on_change: Optional[Callable[..., Any]] = None,
+                 on_change: Optional[Handler[ValueChangeEventArguments]] = None,
                  ) -> None:
                  ) -> None:
         """Radio Selection
         """Radio Selection
 
 

+ 3 - 2
nicegui/elements/range.py

@@ -1,5 +1,6 @@
-from typing import Any, Callable, Dict, Optional
+from typing import Dict, Optional
 
 
+from ..events import Handler, ValueChangeEventArguments
 from .mixins.disableable_element import DisableableElement
 from .mixins.disableable_element import DisableableElement
 from .mixins.value_element import ValueElement
 from .mixins.value_element import ValueElement
 
 
@@ -11,7 +12,7 @@ class Range(ValueElement, DisableableElement):
                  max: float,  # pylint: disable=redefined-builtin
                  max: float,  # pylint: disable=redefined-builtin
                  step: float = 1.0,
                  step: float = 1.0,
                  value: Optional[Dict[str, int]] = None,
                  value: Optional[Dict[str, int]] = None,
-                 on_change: Optional[Callable[..., Any]] = None,
+                 on_change: Optional[Handler[ValueChangeEventArguments]] = None,
                  ) -> None:
                  ) -> None:
         """Range
         """Range
 
 

+ 7 - 6
nicegui/elements/scene.py

@@ -9,6 +9,7 @@ from ..dataclasses import KWONLY_SLOTS
 from ..element import Element
 from ..element import Element
 from ..events import (
 from ..events import (
     GenericEventArguments,
     GenericEventArguments,
+    Handler,
     SceneClickEventArguments,
     SceneClickEventArguments,
     SceneClickHit,
     SceneClickHit,
     SceneDragEventArguments,
     SceneDragEventArguments,
@@ -74,10 +75,10 @@ class Scene(Element,
                  height: int = 300,
                  height: int = 300,
                  grid: Union[bool, Tuple[int, int]] = True,
                  grid: Union[bool, Tuple[int, int]] = True,
                  camera: Optional[SceneCamera] = None,
                  camera: Optional[SceneCamera] = None,
-                 on_click: Optional[Callable[..., Any]] = None,
+                 on_click: Optional[Handler[SceneClickEventArguments]] = None,
                  click_events: List[str] = ['click', 'dblclick'],  # noqa: B006
                  click_events: List[str] = ['click', 'dblclick'],  # noqa: B006
-                 on_drag_start: Optional[Callable[..., Any]] = None,
-                 on_drag_end: Optional[Callable[..., Any]] = None,
+                 on_drag_start: Optional[Handler[SceneDragEventArguments]] = None,
+                 on_drag_end: Optional[Handler[SceneDragEventArguments]] = None,
                  drag_constraints: str = '',
                  drag_constraints: str = '',
                  background_color: str = '#eee',
                  background_color: str = '#eee',
                  ) -> None:
                  ) -> None:
@@ -120,17 +121,17 @@ class Scene(Element,
         self._props['drag_constraints'] = drag_constraints
         self._props['drag_constraints'] = drag_constraints
         self._classes.append('nicegui-scene')
         self._classes.append('nicegui-scene')
 
 
-    def on_click(self, callback: Callable[..., Any]) -> Self:
+    def on_click(self, callback: Handler[SceneClickEventArguments]) -> Self:
         """Add a callback to be invoked when a 3D object is clicked."""
         """Add a callback to be invoked when a 3D object is clicked."""
         self._click_handlers.append(callback)
         self._click_handlers.append(callback)
         return self
         return self
 
 
-    def on_drag_start(self, callback: Callable[..., Any]) -> Self:
+    def on_drag_start(self, callback: Handler[SceneDragEventArguments]) -> Self:
         """Add a callback to be invoked when a 3D object is dragged."""
         """Add a callback to be invoked when a 3D object is dragged."""
         self._drag_start_handlers.append(callback)
         self._drag_start_handlers.append(callback)
         return self
         return self
 
 
-    def on_drag_end(self, callback: Callable[..., Any]) -> Self:
+    def on_drag_end(self, callback: Handler[SceneDragEventArguments]) -> Self:
         """Add a callback to be invoked when a 3D object is dropped."""
         """Add a callback to be invoked when a 3D object is dropped."""
         self._drag_end_handlers.append(callback)
         self._drag_end_handlers.append(callback)
         return self
         return self

+ 11 - 4
nicegui/elements/scene_view.py

@@ -1,10 +1,17 @@
 import asyncio
 import asyncio
-from typing import Any, Callable, Optional
+from typing import Optional
 
 
 from typing_extensions import Self
 from typing_extensions import Self
 
 
 from ..element import Element
 from ..element import Element
-from ..events import GenericEventArguments, SceneClickEventArguments, SceneClickHit, handle_event
+from ..events import (
+    ClickEventArguments,
+    GenericEventArguments,
+    Handler,
+    SceneClickEventArguments,
+    SceneClickHit,
+    handle_event,
+)
 from .scene import Scene, SceneCamera
 from .scene import Scene, SceneCamera
 
 
 
 
@@ -20,7 +27,7 @@ class SceneView(Element,
                  width: int = 400,
                  width: int = 400,
                  height: int = 300,
                  height: int = 300,
                  camera: Optional[SceneCamera] = None,
                  camera: Optional[SceneCamera] = None,
-                 on_click: Optional[Callable[..., Any]] = None,
+                 on_click: Optional[Handler[ClickEventArguments]] = None,
                  ) -> None:
                  ) -> None:
         """Scene View
         """Scene View
 
 
@@ -48,7 +55,7 @@ class SceneView(Element,
         self.on('click3d', self._handle_click)
         self.on('click3d', self._handle_click)
         self._classes.append('nicegui-scene-view')
         self._classes.append('nicegui-scene-view')
 
 
-    def on_click(self, callback: Callable[..., Any]) -> Self:
+    def on_click(self, callback: Handler[ClickEventArguments]) -> Self:
         """Add a callback to be invoked when a 3D object is clicked."""
         """Add a callback to be invoked when a 3D object is clicked."""
         self._click_handlers.append(callback)
         self._click_handlers.append(callback)
         return self
         return self

+ 5 - 5
nicegui/elements/scroll_area.py

@@ -1,14 +1,14 @@
-from typing import Any, Callable, Literal, Optional
+from typing import Literal, Optional
 
 
 from typing_extensions import Self
 from typing_extensions import Self
 
 
 from ..element import Element
 from ..element import Element
-from ..events import GenericEventArguments, ScrollEventArguments, handle_event
+from ..events import GenericEventArguments, Handler, ScrollEventArguments, handle_event
 
 
 
 
 class ScrollArea(Element):
 class ScrollArea(Element):
 
 
-    def __init__(self, *, on_scroll: Optional[Callable[..., Any]] = None) -> None:
+    def __init__(self, *, on_scroll: Optional[Handler[ScrollEventArguments]] = None) -> None:
         """Scroll Area
         """Scroll Area
 
 
         A way of customizing the scrollbars by encapsulating your content.
         A way of customizing the scrollbars by encapsulating your content.
@@ -22,7 +22,7 @@ class ScrollArea(Element):
         if on_scroll:
         if on_scroll:
             self.on_scroll(on_scroll)
             self.on_scroll(on_scroll)
 
 
-    def on_scroll(self, callback: Callable[..., Any]) -> Self:
+    def on_scroll(self, callback: Handler[ScrollEventArguments]) -> Self:
         """Add a callback to be invoked when the scroll position changes."""
         """Add a callback to be invoked when the scroll position changes."""
         self.on('scroll', lambda e: self._handle_scroll(callback, e), args=[
         self.on('scroll', lambda e: self._handle_scroll(callback, e), args=[
             'verticalPosition',
             'verticalPosition',
@@ -36,7 +36,7 @@ class ScrollArea(Element):
         ])
         ])
         return self
         return self
 
 
-    def _handle_scroll(self, handler: Optional[Callable[..., Any]], e: GenericEventArguments) -> None:
+    def _handle_scroll(self, handler: Optional[Handler[ScrollEventArguments]], e: GenericEventArguments) -> None:
         handle_event(handler, ScrollEventArguments(
         handle_event(handler, ScrollEventArguments(
             sender=self,
             sender=self,
             client=self.client,
             client=self.client,

+ 2 - 2
nicegui/elements/select.py

@@ -2,7 +2,7 @@ from collections.abc import Generator, Iterable
 from copy import deepcopy
 from copy import deepcopy
 from typing import Any, Callable, Dict, Iterator, List, Literal, Optional, Union
 from typing import Any, Callable, Dict, Iterator, List, Literal, Optional, Union
 
 
-from ..events import GenericEventArguments
+from ..events import GenericEventArguments, Handler, ValueChangeEventArguments
 from .choice_element import ChoiceElement
 from .choice_element import ChoiceElement
 from .mixins.disableable_element import DisableableElement
 from .mixins.disableable_element import DisableableElement
 from .mixins.validation_element import ValidationElement
 from .mixins.validation_element import ValidationElement
@@ -14,7 +14,7 @@ class Select(ValidationElement, ChoiceElement, DisableableElement, component='se
                  options: Union[List, Dict], *,
                  options: Union[List, Dict], *,
                  label: Optional[str] = None,
                  label: Optional[str] = None,
                  value: Any = None,
                  value: Any = None,
-                 on_change: Optional[Callable[..., Any]] = None,
+                 on_change: Optional[Handler[ValueChangeEventArguments]] = None,
                  with_input: bool = False,
                  with_input: bool = False,
                  new_value_mode: Optional[Literal['add', 'add-unique', 'toggle']] = None,
                  new_value_mode: Optional[Literal['add', 'add-unique', 'toggle']] = None,
                  multiple: bool = False,
                  multiple: bool = False,

+ 3 - 2
nicegui/elements/slider.py

@@ -1,5 +1,6 @@
-from typing import Any, Callable, Optional
+from typing import Optional
 
 
+from ..events import Handler, ValueChangeEventArguments
 from .mixins.disableable_element import DisableableElement
 from .mixins.disableable_element import DisableableElement
 from .mixins.value_element import ValueElement
 from .mixins.value_element import ValueElement
 
 
@@ -11,7 +12,7 @@ class Slider(ValueElement, DisableableElement):
                  max: float,  # pylint: disable=redefined-builtin
                  max: float,  # pylint: disable=redefined-builtin
                  step: float = 1.0,
                  step: float = 1.0,
                  value: Optional[float] = None,
                  value: Optional[float] = None,
-                 on_change: Optional[Callable[..., Any]] = None,
+                 on_change: Optional[Handler[ValueChangeEventArguments]] = None,
                  ) -> None:
                  ) -> None:
         """Slider
         """Slider
 
 

+ 3 - 2
nicegui/elements/splitter.py

@@ -1,5 +1,6 @@
-from typing import Any, Callable, Optional, Tuple
+from typing import Optional, Tuple
 
 
+from ..events import Handler, ValueChangeEventArguments
 from .mixins.disableable_element import DisableableElement
 from .mixins.disableable_element import DisableableElement
 from .mixins.value_element import ValueElement
 from .mixins.value_element import ValueElement
 
 
@@ -11,7 +12,7 @@ class Splitter(ValueElement, DisableableElement):
                  reverse: Optional[bool] = False,
                  reverse: Optional[bool] = False,
                  limits: Optional[Tuple[float, float]] = (0, 100),
                  limits: Optional[Tuple[float, float]] = (0, 100),
                  value: Optional[float] = 50,
                  value: Optional[float] = 50,
-                 on_change: Optional[Callable[..., Any]] = None,
+                 on_change: Optional[Handler[ValueChangeEventArguments]] = None,
                  ) -> None:
                  ) -> None:
         """Splitter
         """Splitter
 
 

+ 3 - 2
nicegui/elements/stepper.py

@@ -1,9 +1,10 @@
 from __future__ import annotations
 from __future__ import annotations
 
 
-from typing import Any, Callable, Optional, Union, cast
+from typing import Any, Optional, Union, cast
 
 
 from ..context import context
 from ..context import context
 from ..element import Element
 from ..element import Element
+from ..events import Handler, ValueChangeEventArguments
 from .mixins.disableable_element import DisableableElement
 from .mixins.disableable_element import DisableableElement
 from .mixins.icon_element import IconElement
 from .mixins.icon_element import IconElement
 from .mixins.value_element import ValueElement
 from .mixins.value_element import ValueElement
@@ -13,7 +14,7 @@ class Stepper(ValueElement):
 
 
     def __init__(self, *,
     def __init__(self, *,
                  value: Union[str, Step, None] = None,
                  value: Union[str, Step, None] = None,
-                 on_value_change: Optional[Callable[..., Any]] = None,
+                 on_value_change: Optional[Handler[ValueChangeEventArguments]] = None,
                  keep_alive: bool = True,
                  keep_alive: bool = True,
                  ) -> None:
                  ) -> None:
         """Stepper
         """Stepper

+ 3 - 2
nicegui/elements/switch.py

@@ -1,5 +1,6 @@
-from typing import Any, Callable, Optional
+from typing import Optional
 
 
+from ..events import Handler, ValueChangeEventArguments
 from .mixins.disableable_element import DisableableElement
 from .mixins.disableable_element import DisableableElement
 from .mixins.text_element import TextElement
 from .mixins.text_element import TextElement
 from .mixins.value_element import ValueElement
 from .mixins.value_element import ValueElement
@@ -7,7 +8,7 @@ from .mixins.value_element import ValueElement
 
 
 class Switch(TextElement, ValueElement, DisableableElement):
 class Switch(TextElement, ValueElement, DisableableElement):
 
 
-    def __init__(self, text: str = '', *, value: bool = False, on_change: Optional[Callable[..., Any]] = None) -> None:
+    def __init__(self, text: str = '', *, value: bool = False, on_change: Optional[Handler[ValueChangeEventArguments]] = None) -> None:
         """Switch
         """Switch
 
 
         This element is based on Quasar's `QToggle <https://quasar.dev/vue-components/toggle>`_ component.
         This element is based on Quasar's `QToggle <https://quasar.dev/vue-components/toggle>`_ component.

+ 13 - 7
nicegui/elements/table.py

@@ -1,11 +1,17 @@
 import importlib.util
 import importlib.util
-from typing import TYPE_CHECKING, Any, Callable, Dict, List, Literal, Optional, Tuple, Union
+from typing import TYPE_CHECKING, Any, Dict, List, Literal, Optional, Tuple, Union
 
 
 from typing_extensions import Self
 from typing_extensions import Self
 
 
 from .. import optional_features
 from .. import optional_features
 from ..element import Element
 from ..element import Element
-from ..events import GenericEventArguments, TableSelectionEventArguments, ValueChangeEventArguments, handle_event
+from ..events import (
+    GenericEventArguments,
+    Handler,
+    TableSelectionEventArguments,
+    ValueChangeEventArguments,
+    handle_event,
+)
 from ..helpers import warn_once
 from ..helpers import warn_once
 from .mixins.filter_element import FilterElement
 from .mixins.filter_element import FilterElement
 
 
@@ -26,8 +32,8 @@ class Table(FilterElement, component='table.js'):
                  title: Optional[str] = None,
                  title: Optional[str] = None,
                  selection: Optional[Literal['single', 'multiple']] = None,
                  selection: Optional[Literal['single', 'multiple']] = None,
                  pagination: Optional[Union[int, dict]] = None,
                  pagination: Optional[Union[int, dict]] = None,
-                 on_select: Optional[Callable[..., Any]] = None,
-                 on_pagination_change: Optional[Callable[..., Any]] = None,
+                 on_select: Optional[Handler[TableSelectionEventArguments]] = None,
+                 on_pagination_change: Optional[Handler[ValueChangeEventArguments]] = None,
                  ) -> None:
                  ) -> None:
         """Table
         """Table
 
 
@@ -86,12 +92,12 @@ class Table(FilterElement, component='table.js'):
                 handle_event(handler, arguments)
                 handle_event(handler, arguments)
         self.on('update:pagination', handle_pagination_change)
         self.on('update:pagination', handle_pagination_change)
 
 
-    def on_select(self, callback: Callable[..., Any]) -> Self:
+    def on_select(self, callback: Handler[TableSelectionEventArguments]) -> Self:
         """Add a callback to be invoked when the selection changes."""
         """Add a callback to be invoked when the selection changes."""
         self._selection_handlers.append(callback)
         self._selection_handlers.append(callback)
         return self
         return self
 
 
-    def on_pagination_change(self, callback: Callable[..., Any]) -> Self:
+    def on_pagination_change(self, callback: Handler[ValueChangeEventArguments]) -> Self:
         """Add a callback to be invoked when the pagination changes."""
         """Add a callback to be invoked when the pagination changes."""
         self._pagination_change_handlers.append(callback)
         self._pagination_change_handlers.append(callback)
         return self
         return self
@@ -108,7 +114,7 @@ class Table(FilterElement, component='table.js'):
                     title: Optional[str] = None,
                     title: Optional[str] = None,
                     selection: Optional[Literal['single', 'multiple']] = None,
                     selection: Optional[Literal['single', 'multiple']] = None,
                     pagination: Optional[Union[int, dict]] = None,
                     pagination: Optional[Union[int, dict]] = None,
-                    on_select: Optional[Callable[..., Any]] = None) -> Self:
+                    on_select: Optional[Handler[TableSelectionEventArguments]] = None) -> Self:
         """Create a table from a Pandas DataFrame.
         """Create a table from a Pandas DataFrame.
 
 
         Note:
         Note:

+ 4 - 3
nicegui/elements/tabs.py

@@ -1,8 +1,9 @@
 from __future__ import annotations
 from __future__ import annotations
 
 
-from typing import Any, Callable, Optional, Union
+from typing import Any, Optional, Union
 
 
 from ..context import context
 from ..context import context
+from ..events import Handler, ValueChangeEventArguments
 from .mixins.disableable_element import DisableableElement
 from .mixins.disableable_element import DisableableElement
 from .mixins.icon_element import IconElement
 from .mixins.icon_element import IconElement
 from .mixins.value_element import ValueElement
 from .mixins.value_element import ValueElement
@@ -12,7 +13,7 @@ class Tabs(ValueElement):
 
 
     def __init__(self, *,
     def __init__(self, *,
                  value: Union[Tab, TabPanel, None] = None,
                  value: Union[Tab, TabPanel, None] = None,
-                 on_change: Optional[Callable[..., Any]] = None,
+                 on_change: Optional[Handler[ValueChangeEventArguments]] = None,
                  ) -> None:
                  ) -> None:
         """Tabs
         """Tabs
 
 
@@ -51,7 +52,7 @@ class TabPanels(ValueElement):
     def __init__(self,
     def __init__(self,
                  tabs: Optional[Tabs] = None, *,
                  tabs: Optional[Tabs] = None, *,
                  value: Union[Tab, TabPanel, str, None] = None,
                  value: Union[Tab, TabPanel, str, None] = None,
-                 on_change: Optional[Callable[..., Any]] = None,
+                 on_change: Optional[Handler[ValueChangeEventArguments]] = None,
                  animated: bool = True,
                  animated: bool = True,
                  keep_alive: bool = True,
                  keep_alive: bool = True,
                  ) -> None:
                  ) -> None:

+ 3 - 2
nicegui/elements/textarea.py

@@ -1,5 +1,6 @@
-from typing import Any, Callable, Dict, Optional, Union
+from typing import Callable, Dict, Optional, Union
 
 
+from ..events import Handler, ValueChangeEventArguments
 from .input import Input
 from .input import Input
 
 
 
 
@@ -9,7 +10,7 @@ class Textarea(Input, component='input.js'):
                  label: Optional[str] = None, *,
                  label: Optional[str] = None, *,
                  placeholder: Optional[str] = None,
                  placeholder: Optional[str] = None,
                  value: str = '',
                  value: str = '',
-                 on_change: Optional[Callable[..., Any]] = None,
+                 on_change: Optional[Handler[ValueChangeEventArguments]] = None,
                  validation: Optional[Union[Callable[..., Optional[str]], Dict[str, Callable[..., bool]]]] = None,
                  validation: Optional[Union[Callable[..., Optional[str]], Dict[str, Callable[..., bool]]]] = None,
                  ) -> None:
                  ) -> None:
         """Textarea
         """Textarea

+ 3 - 2
nicegui/elements/time.py

@@ -1,5 +1,6 @@
-from typing import Any, Callable, Optional
+from typing import Optional
 
 
+from ..events import Handler, ValueChangeEventArguments
 from .mixins.disableable_element import DisableableElement
 from .mixins.disableable_element import DisableableElement
 from .mixins.value_element import ValueElement
 from .mixins.value_element import ValueElement
 
 
@@ -9,7 +10,7 @@ class Time(ValueElement, DisableableElement):
     def __init__(self,
     def __init__(self,
                  value: Optional[str] = None, *,
                  value: Optional[str] = None, *,
                  mask: str = 'HH:mm',
                  mask: str = 'HH:mm',
-                 on_change: Optional[Callable[..., Any]] = None,
+                 on_change: Optional[Handler[ValueChangeEventArguments]] = None,
                  ) -> None:
                  ) -> None:
         """Time Input
         """Time Input
 
 

+ 3 - 3
nicegui/elements/toggle.py

@@ -1,6 +1,6 @@
-from typing import Any, Callable, Dict, List, Optional, Union
+from typing import Any, Dict, List, Optional, Union
 
 
-from ..events import GenericEventArguments
+from ..events import GenericEventArguments, Handler, ValueChangeEventArguments
 from .choice_element import ChoiceElement
 from .choice_element import ChoiceElement
 from .mixins.disableable_element import DisableableElement
 from .mixins.disableable_element import DisableableElement
 
 
@@ -10,7 +10,7 @@ class Toggle(ChoiceElement, DisableableElement):
     def __init__(self,
     def __init__(self,
                  options: Union[List, Dict], *,
                  options: Union[List, Dict], *,
                  value: Any = None,
                  value: Any = None,
-                 on_change: Optional[Callable[..., Any]] = None,
+                 on_change: Optional[Handler[ValueChangeEventArguments]] = None,
                  clearable: bool = False,
                  clearable: bool = False,
                  ) -> None:
                  ) -> None:
         """Toggle
         """Toggle

+ 8 - 8
nicegui/elements/tree.py

@@ -1,8 +1,8 @@
-from typing import Any, Callable, Dict, Iterator, List, Literal, Optional, Set
+from typing import Any, Dict, Iterator, List, Literal, Optional, Set
 
 
 from typing_extensions import Self
 from typing_extensions import Self
 
 
-from ..events import GenericEventArguments, ValueChangeEventArguments, handle_event
+from ..events import GenericEventArguments, Handler, ValueChangeEventArguments, handle_event
 from .mixins.filter_element import FilterElement
 from .mixins.filter_element import FilterElement
 
 
 
 
@@ -13,9 +13,9 @@ class Tree(FilterElement):
                  node_key: str = 'id',
                  node_key: str = 'id',
                  label_key: str = 'label',
                  label_key: str = 'label',
                  children_key: str = 'children',
                  children_key: str = 'children',
-                 on_select: Optional[Callable[..., Any]] = None,
-                 on_expand: Optional[Callable[..., Any]] = None,
-                 on_tick: Optional[Callable[..., Any]] = None,
+                 on_select: Optional[Handler[ValueChangeEventArguments]] = None,
+                 on_expand: Optional[Handler[ValueChangeEventArguments]] = None,
+                 on_tick: Optional[Handler[ValueChangeEventArguments]] = None,
                  tick_strategy: Optional[Literal['leaf', 'leaf-filtered', 'strict']] = None,
                  tick_strategy: Optional[Literal['leaf', 'leaf-filtered', 'strict']] = None,
                  ) -> None:
                  ) -> None:
         """Tree
         """Tree
@@ -77,7 +77,7 @@ class Tree(FilterElement):
                 handle_event(handler, ValueChangeEventArguments(sender=self, client=self.client, value=e.args))
                 handle_event(handler, ValueChangeEventArguments(sender=self, client=self.client, value=e.args))
         self.on('update:ticked', handle_ticked)
         self.on('update:ticked', handle_ticked)
 
 
-    def on_select(self, callback: Callable[..., Any]) -> Self:
+    def on_select(self, callback: Handler[ValueChangeEventArguments]) -> Self:
         """Add a callback to be invoked when the selection changes."""
         """Add a callback to be invoked when the selection changes."""
         self._select_handlers.append(callback)
         self._select_handlers.append(callback)
         return self
         return self
@@ -96,12 +96,12 @@ class Tree(FilterElement):
         """Remove node selection."""
         """Remove node selection."""
         return self.select(None)
         return self.select(None)
 
 
-    def on_expand(self, callback: Callable[..., Any]) -> Self:
+    def on_expand(self, callback: Handler[ValueChangeEventArguments]) -> Self:
         """Add a callback to be invoked when the expansion changes."""
         """Add a callback to be invoked when the expansion changes."""
         self._expand_handlers.append(callback)
         self._expand_handlers.append(callback)
         return self
         return self
 
 
-    def on_tick(self, callback: Callable[..., Any]) -> Self:
+    def on_tick(self, callback: Handler[ValueChangeEventArguments]) -> Self:
         """Add a callback to be invoked when a node is ticked or unticked."""
         """Add a callback to be invoked when a node is ticked or unticked."""
         self._tick_handlers.append(callback)
         self._tick_handlers.append(callback)
         return self
         return self

+ 8 - 8
nicegui/elements/upload.py

@@ -1,10 +1,10 @@
-from typing import Any, Callable, Dict, List, Optional, cast
+from typing import Dict, List, Optional, cast
 
 
 from fastapi import Request
 from fastapi import Request
 from starlette.datastructures import UploadFile
 from starlette.datastructures import UploadFile
 from typing_extensions import Self
 from typing_extensions import Self
 
 
-from ..events import MultiUploadEventArguments, UiEventArguments, UploadEventArguments, handle_event
+from ..events import Handler, MultiUploadEventArguments, UiEventArguments, UploadEventArguments, handle_event
 from ..nicegui import app
 from ..nicegui import app
 from .mixins.disableable_element import DisableableElement
 from .mixins.disableable_element import DisableableElement
 
 
@@ -16,9 +16,9 @@ class Upload(DisableableElement, component='upload.js'):
                  max_file_size: Optional[int] = None,
                  max_file_size: Optional[int] = None,
                  max_total_size: Optional[int] = None,
                  max_total_size: Optional[int] = None,
                  max_files: Optional[int] = None,
                  max_files: Optional[int] = None,
-                 on_upload: Optional[Callable[..., Any]] = None,
-                 on_multi_upload: Optional[Callable[..., Any]] = None,
-                 on_rejected: Optional[Callable[..., Any]] = None,
+                 on_upload: Optional[Handler[UploadEventArguments]] = None,
+                 on_multi_upload: Optional[Handler[MultiUploadEventArguments]] = None,
+                 on_rejected: Optional[Handler[UiEventArguments]] = None,
                  label: str = '',
                  label: str = '',
                  auto_upload: bool = False,
                  auto_upload: bool = False,
                  ) -> None:
                  ) -> None:
@@ -91,17 +91,17 @@ class Upload(DisableableElement, component='upload.js'):
         for multi_upload_handler in self._multi_upload_handlers:
         for multi_upload_handler in self._multi_upload_handlers:
             handle_event(multi_upload_handler, multi_upload_args)
             handle_event(multi_upload_handler, multi_upload_args)
 
 
-    def on_upload(self, callback: Callable[..., Any]) -> Self:
+    def on_upload(self, callback: Handler[UploadEventArguments]) -> Self:
         """Add a callback to be invoked when a file is uploaded."""
         """Add a callback to be invoked when a file is uploaded."""
         self._upload_handlers.append(callback)
         self._upload_handlers.append(callback)
         return self
         return self
 
 
-    def on_multi_upload(self, callback: Callable[..., Any]) -> Self:
+    def on_multi_upload(self, callback: Handler[MultiUploadEventArguments]) -> Self:
         """Add a callback to be invoked when multiple files have been uploaded."""
         """Add a callback to be invoked when multiple files have been uploaded."""
         self._multi_upload_handlers.append(callback)
         self._multi_upload_handlers.append(callback)
         return self
         return self
 
 
-    def on_rejected(self, callback: Callable[..., Any]) -> Self:
+    def on_rejected(self, callback: Handler[UiEventArguments]) -> Self:
         """Add a callback to be invoked when a file is rejected."""
         """Add a callback to be invoked when a file is rejected."""
         self.on('rejected', lambda: handle_event(callback, UiEventArguments(sender=self, client=self.client)), args=[])
         self.on('rejected', lambda: handle_event(callback, UiEventArguments(sender=self, client=self.client)), args=[])
         return self
         return self

+ 24 - 3
nicegui/events.py

@@ -3,7 +3,21 @@ from __future__ import annotations
 from contextlib import nullcontext
 from contextlib import nullcontext
 from dataclasses import dataclass
 from dataclasses import dataclass
 from inspect import Parameter, signature
 from inspect import Parameter, signature
-from typing import TYPE_CHECKING, Any, Awaitable, BinaryIO, Callable, Dict, Iterator, List, Literal, Optional, Union
+from typing import (
+    TYPE_CHECKING,
+    Any,
+    Awaitable,
+    BinaryIO,
+    Callable,
+    Dict,
+    Iterator,
+    List,
+    Literal,
+    Optional,
+    TypeVar,
+    Union,
+    cast,
+)
 
 
 from . import background_tasks, core
 from . import background_tasks, core
 from .awaitable_response import AwaitableResponse
 from .awaitable_response import AwaitableResponse
@@ -382,7 +396,11 @@ class JsonEditorChangeEventArguments(UiEventArguments):
     errors: Dict
     errors: Dict
 
 
 
 
-def handle_event(handler: Optional[Callable[..., Any]], arguments: EventArguments) -> None:
+EventT = TypeVar('EventT', bound=EventArguments)
+Handler = Union[Callable[[EventT], Any], Callable[[], Any]]
+
+
+def handle_event(handler: Optional[Handler[EventT]], arguments: EventT) -> None:
     """Call the given event handler.
     """Call the given event handler.
 
 
     The handler is called within the context of the parent slot of the sender.
     The handler is called within the context of the parent slot of the sender.
@@ -408,7 +426,10 @@ def handle_event(handler: Optional[Callable[..., Any]], arguments: EventArgument
             parent_slot = nullcontext()
             parent_slot = nullcontext()
 
 
         with parent_slot:
         with parent_slot:
-            result = handler(arguments) if expects_arguments else handler()
+            if expects_arguments:
+                result = cast(Callable[[EventT], Any], handler)(arguments)
+            else:
+                result = cast(Callable[[], Any], handler)()
         if isinstance(result, Awaitable) and not isinstance(result, AwaitableResponse):
         if isinstance(result, Awaitable) and not isinstance(result, AwaitableResponse):
             # NOTE: await an awaitable result even if the handler is not a coroutine (like a lambda statement)
             # NOTE: await an awaitable result even if the handler is not a coroutine (like a lambda statement)
             async def wait_for_result():
             async def wait_for_result():

+ 3 - 2
nicegui/functions/on.py

@@ -1,10 +1,11 @@
-from typing import Any, Callable, Optional, Sequence, Union
+from typing import Optional, Sequence, Union
 
 
 from ..context import context
 from ..context import context
+from ..events import GenericEventArguments, Handler
 
 
 
 
 def on(type: str,  # pylint: disable=redefined-builtin
 def on(type: str,  # pylint: disable=redefined-builtin
-       handler: Optional[Callable[..., Any]] = None,
+       handler: Optional[Handler[GenericEventArguments]] = None,
        args: Union[None, Sequence[str], Sequence[Optional[Sequence[str]]]] = None, *,
        args: Union[None, Sequence[str], Sequence[Optional[Sequence[str]]]] = None, *,
        throttle: float = 0.0,
        throttle: float = 0.0,
        leading_events: bool = True,
        leading_events: bool = True,

+ 8 - 0
tests/test_radio_element.py

@@ -1,6 +1,7 @@
 from nicegui import events, ui
 from nicegui import events, ui
 from nicegui.testing import Screen
 from nicegui.testing import Screen
 
 
+
 def test_radio_click(screen: Screen):
 def test_radio_click(screen: Screen):
     r = ui.radio(['A', 'B', 'C'])
     r = ui.radio(['A', 'B', 'C'])
 
 
@@ -10,6 +11,7 @@ def test_radio_click(screen: Screen):
     screen.click('B')
     screen.click('B')
     assert r.value == 'B'
     assert r.value == 'B'
 
 
+
 def test_radio_click_already_selected(screen: Screen):
 def test_radio_click_already_selected(screen: Screen):
     r = ui.radio(['A', 'B', 'C'], value='B')
     r = ui.radio(['A', 'B', 'C'], value='B')
 
 
@@ -17,6 +19,7 @@ def test_radio_click_already_selected(screen: Screen):
     screen.click('B')
     screen.click('B')
     assert r.value == 'B'
     assert r.value == 'B'
 
 
+
 def test_radio_set_value(screen: Screen):
 def test_radio_set_value(screen: Screen):
     r = ui.radio(['A', 'B', 'C'])
     r = ui.radio(['A', 'B', 'C'])
 
 
@@ -24,6 +27,7 @@ def test_radio_set_value(screen: Screen):
     r.set_value('B')
     r.set_value('B')
     assert r.value == 'B'
     assert r.value == 'B'
 
 
+
 def test_radio_set_options(screen: Screen):
 def test_radio_set_options(screen: Screen):
     r = ui.radio(['A', 'B', 'C'])
     r = ui.radio(['A', 'B', 'C'])
 
 
@@ -31,6 +35,7 @@ def test_radio_set_options(screen: Screen):
     r.set_options(['D', 'E', 'F'])
     r.set_options(['D', 'E', 'F'])
     assert r.options == ['D', 'E', 'F']
     assert r.options == ['D', 'E', 'F']
 
 
+
 def test_radio_set_options_value_still_valid(screen: Screen):
 def test_radio_set_options_value_still_valid(screen: Screen):
     r = ui.radio(['A', 'B', 'C'], value='C')
     r = ui.radio(['A', 'B', 'C'], value='C')
 
 
@@ -38,6 +43,7 @@ def test_radio_set_options_value_still_valid(screen: Screen):
     r.set_options(['C', 'D', 'E'])
     r.set_options(['C', 'D', 'E'])
     assert r.value == 'C'
     assert r.value == 'C'
 
 
+
 def test_radio_set_options_value_none(screen: Screen):
 def test_radio_set_options_value_none(screen: Screen):
     r = ui.radio(['A', 'B', 'C'], value='C')
     r = ui.radio(['A', 'B', 'C'], value='C')
 
 
@@ -45,6 +51,7 @@ def test_radio_set_options_value_none(screen: Screen):
     r.set_options(['D', 'E', 'F'])
     r.set_options(['D', 'E', 'F'])
     assert r.value is None
     assert r.value is None
 
 
+
 def test_radio_set_options_value(screen: Screen):
 def test_radio_set_options_value(screen: Screen):
     r = ui.radio(['A', 'B', 'C'])
     r = ui.radio(['A', 'B', 'C'])
 
 
@@ -52,6 +59,7 @@ def test_radio_set_options_value(screen: Screen):
     r.set_options(['D', 'E', 'F'], value='E')
     r.set_options(['D', 'E', 'F'], value='E')
     assert r.value == 'E'
     assert r.value == 'E'
 
 
+
 def test_radio_set_options_value_callback(screen: Screen):
 def test_radio_set_options_value_callback(screen: Screen):
     """Fix for https://github.com/zauberzeug/nicegui/issues/3733.
     """Fix for https://github.com/zauberzeug/nicegui/issues/3733.
 
 

+ 3 - 3
website/documentation/content/storage_documentation.py

@@ -32,7 +32,7 @@ doc.title('Storage')
         Stored server-side, each dictionary is associated with a unique identifier held in a browser session cookie.
         Stored server-side, each dictionary is associated with a unique identifier held in a browser session cookie.
         Unique to each user, this storage is accessible across all their browser tabs.
         Unique to each user, this storage is accessible across all their browser tabs.
         `app.storage.browser['id']` is used to identify the user.
         `app.storage.browser['id']` is used to identify the user.
-        This storage is only available within [page builder functions](/documentation/page) 
+        This storage is only available within [page builder functions](/documentation/page)
         and requires the `storage_secret` parameter in`ui.run()` to sign the browser session cookie.
         and requires the `storage_secret` parameter in`ui.run()` to sign the browser session cookie.
     - `app.storage.general`:
     - `app.storage.general`:
         Also stored server-side, this dictionary provides a shared storage space accessible to all users.
         Also stored server-side, this dictionary provides a shared storage space accessible to all users.
@@ -40,11 +40,11 @@ doc.title('Storage')
         Unlike the previous types, this dictionary is stored directly as the browser session cookie, shared among all browser tabs for the same user.
         Unlike the previous types, this dictionary is stored directly as the browser session cookie, shared among all browser tabs for the same user.
         However, `app.storage.user` is generally preferred due to its advantages in reducing data payload, enhancing security, and offering larger storage capacity.
         However, `app.storage.user` is generally preferred due to its advantages in reducing data payload, enhancing security, and offering larger storage capacity.
         By default, NiceGUI holds a unique identifier for the browser session in `app.storage.browser['id']`.
         By default, NiceGUI holds a unique identifier for the browser session in `app.storage.browser['id']`.
-        This storage is only available within [page builder functions](/documentation/page) 
+        This storage is only available within [page builder functions](/documentation/page)
         and requires the `storage_secret` parameter in`ui.run()` to sign the browser session cookie.
         and requires the `storage_secret` parameter in`ui.run()` to sign the browser session cookie.
 
 
     The following table will help you to choose storage.
     The following table will help you to choose storage.
- 
+
     | Storage type                | `tab`  | `client` | `user` | `general` | `browser` |
     | Storage type                | `tab`  | `client` | `user` | `general` | `browser` |
     |-----------------------------|--------|----------|--------|-----------|-----------|
     |-----------------------------|--------|----------|--------|-----------|-----------|
     | Location                    | Server | Server   | Server | Server    | Browser   |
     | Location                    | Server | Server   | Server | Server    | Browser   |