ソースを参照

Merge commit 'ad60420b95943027882edb1f428b2d16744b563e' into changeable_native_window

Rodja Trappe 2 年 前
コミット
4e67ddbcae
55 ファイル変更445 行追加141 行削除
  1. 1 1
      .github/workflows/test.yml
  2. 109 0
      examples/simpy/async_realtime_environment.py
  3. 49 0
      examples/simpy/main.py
  4. 2 0
      examples/simpy/requirements.txt
  5. 65 0
      examples/todo_list/main.py
  6. 1 0
      main.py
  7. 5 5
      nicegui/binding.py
  8. 5 5
      nicegui/client.py
  9. 12 8
      nicegui/element.py
  10. 2 2
      nicegui/elements/button.py
  11. 2 2
      nicegui/elements/checkbox.py
  12. 5 1
      nicegui/elements/choice_element.py
  13. 7 3
      nicegui/elements/color_input.py
  14. 2 2
      nicegui/elements/color_picker.py
  15. 2 2
      nicegui/elements/date.py
  16. 2 2
      nicegui/elements/input.py
  17. 7 3
      nicegui/elements/interactive_image.py
  18. 3 3
      nicegui/elements/joystick.py
  19. 2 2
      nicegui/elements/keyboard.py
  20. 9 3
      nicegui/elements/line_plot.py
  21. 2 2
      nicegui/elements/link.py
  22. 6 2
      nicegui/elements/menu.py
  23. 8 5
      nicegui/elements/mixins/content_element.py
  24. 8 5
      nicegui/elements/mixins/disableable_element.py
  25. 8 5
      nicegui/elements/mixins/filter_element.py
  26. 8 5
      nicegui/elements/mixins/source_element.py
  27. 8 5
      nicegui/elements/mixins/text_element.py
  28. 13 5
      nicegui/elements/mixins/value_element.py
  29. 8 6
      nicegui/elements/mixins/visibility.py
  30. 3 2
      nicegui/elements/number.py
  31. 2 1
      nicegui/elements/pyplot.py
  32. 5 1
      nicegui/elements/radio.py
  33. 2 1
      nicegui/elements/scene.py
  34. 3 2
      nicegui/elements/select.py
  35. 3 2
      nicegui/elements/slider.py
  36. 3 2
      nicegui/elements/splitter.py
  37. 2 2
      nicegui/elements/switch.py
  38. 2 2
      nicegui/elements/table.py
  39. 3 2
      nicegui/elements/tabs.py
  40. 4 3
      nicegui/elements/textarea.py
  41. 4 4
      nicegui/elements/time.py
  42. 5 1
      nicegui/elements/toggle.py
  43. 4 3
      nicegui/elements/tree.py
  44. 3 3
      nicegui/elements/upload.py
  45. 1 1
      nicegui/events.py
  46. 1 1
      nicegui/functions/notify.py
  47. 2 2
      nicegui/functions/open.py
  48. 3 3
      nicegui/functions/refreshable.py
  49. 10 4
      nicegui/functions/timer.py
  50. 7 7
      nicegui/globals.py
  51. 1 1
      nicegui/helpers.py
  52. 3 3
      nicegui/page.py
  53. 2 2
      nicegui/run.py
  54. 4 1
      nicegui/slot.py
  55. 2 1
      nicegui/templates/index.html

+ 1 - 1
.github/workflows/test.yml

@@ -25,7 +25,7 @@ jobs:
           poetry config virtualenvs.create false
           poetry install
           # install packages to run the examples
-          pip install opencv-python opencv-contrib-python-headless httpx replicate langchain openai
+          pip install opencv-python opencv-contrib-python-headless httpx replicate langchain openai simpy
           # try fix issue with importlib_resources
           pip install importlib-resources
       - name: test startup

+ 109 - 0
examples/simpy/async_realtime_environment.py

@@ -0,0 +1,109 @@
+import asyncio
+from time import monotonic
+from typing import Any, Optional, Union
+
+from numpy import Infinity
+from simpy.core import EmptySchedule, Environment, Infinity, SimTime, StopSimulation
+from simpy.events import URGENT, Event
+from simpy.rt import RealtimeEnvironment
+
+
+class AsyncRealtimeEnvironment(RealtimeEnvironment):
+    """A real-time simulation environment that uses asyncio.
+
+    The methods step and run are a 1-1 copy of the original methods from simpy.rt.RealtimeEnvironment,
+    except that they are async and await asyncio.sleep instead of time.sleep.
+    """
+
+    async def step(self) -> None:
+        """Process the next event after enough real-time has passed for the
+        event to happen.
+
+        The delay is scaled according to the real-time :attr:`factor`. With
+        :attr:`strict` mode enabled, a :exc:`RuntimeError` will be raised, if
+        the event is processed too slowly.
+
+        """
+        evt_time = self.peek()
+
+        if evt_time is Infinity:
+            raise EmptySchedule()
+
+        real_time = self.real_start + (evt_time - self.env_start) * self.factor
+
+        if self.strict and monotonic() - real_time > self.factor:
+            # Events scheduled for time *t* may take just up to *t+1*
+            # for their computation, before an error is raised.
+            delta = monotonic() - real_time
+            raise RuntimeError(
+                f'Simulation too slow for real time ({delta:.3f}s).'
+            )
+
+        # Sleep in a loop to fix inaccuracies of windows (see
+        # http://stackoverflow.com/a/15967564 for details) and to ignore
+        # interrupts.
+        while True:
+            delta = real_time - monotonic()
+            if delta <= 0:
+                break
+            await asyncio.sleep(delta)
+
+        Environment.step(self)
+
+    async def run(
+        self, until: Optional[Union[SimTime, Event]] = None
+    ) -> Optional[Any]:
+        """Executes :meth:`step()` until the given criterion *until* is met.
+
+        - If it is ``None`` (which is the default), this method will return
+          when there are no further events to be processed.
+
+        - If it is an :class:`~simpy.events.Event`, the method will continue
+          stepping until this event has been triggered and will return its
+          value.  Raises a :exc:`RuntimeError` if there are no further events
+          to be processed and the *until* event was not triggered.
+
+        - If it is a number, the method will continue stepping
+          until the environment's time reaches *until*.
+
+        """
+        if until is not None:
+            if not isinstance(until, Event):
+                # Assume that *until* is a number if it is not None and
+                # not an event.  Create a Timeout(until) in this case.
+                at: SimTime
+                if isinstance(until, int):
+                    at = until
+                else:
+                    at = float(until)
+
+                if at <= self.now:
+                    raise ValueError(
+                        f'until(={at}) must be > the current simulation time.'
+                    )
+
+                # Schedule the event before all regular timeouts.
+                until = Event(self)
+                until._ok = True
+                until._value = None
+                self.schedule(until, URGENT, at - self.now)
+
+            elif until.callbacks is None:
+                # Until event has already been processed.
+                return until.value
+
+            until.callbacks.append(StopSimulation.callback)
+
+        try:
+            while True:
+                await self.step()
+        except StopSimulation as exc:
+            return exc.args[0]  # == until.value
+        except EmptySchedule:
+            if until is not None:
+                assert not until.triggered
+                raise RuntimeError(
+                    f'No scheduled events left but "until" event was not '
+                    f'triggered: {until}'
+                )
+        return None

+ 49 - 0
examples/simpy/main.py

@@ -0,0 +1,49 @@
+#!/usr/bin/env python3
+import asyncio
+import datetime
+
+from async_realtime_environment import AsyncRealtimeEnvironment
+
+from nicegui import ui
+
+start_time = datetime.datetime.now()
+
+
+def clock(env):
+    while True:
+        simulation_time = start_time + datetime.timedelta(seconds=env.now)
+        clock_label.text = simulation_time.strftime('%H:%M:%S')
+        yield env.timeout(1)
+
+
+def traffic_light(env):
+    while True:
+        light.classes('bg-green-500', remove='bg-red-500')
+        yield env.timeout(30)
+        light.classes('bg-yellow-500', remove='bg-green-500')
+        yield env.timeout(5)
+        light.classes('bg-red-500', remove='bg-yellow-500')
+        yield env.timeout(20)
+
+
+async def run_simpy():
+    env = AsyncRealtimeEnvironment(factor=0.1)  # fast forward simulation with 1/10th of realtime
+    env.process(traffic_light(env))
+    env.process(clock(env))
+    try:
+        await env.run(until=300)  # run until 300 seconds of simulation time have passed
+    except asyncio.CancelledError:
+        return
+    ui.notify('Simulation completed')
+    content.classes('opacity-0')  # fade out the content
+
+# define the UI
+with ui.column().classes('absolute-center items-center transition-opacity duration-500') as content:
+    ui.label('SimPy Traffic Light Demo').classes('text-2xl mb-6')
+    light = ui.element('div').classes('w-10 h-10 rounded-full shadow-lg transition')
+    clock_label = ui.label()
+
+# start the simpy simulation as an async task in the background as soon as the UI is ready
+ui.timer(0, run_simpy, once=True)
+
+ui.run()

+ 2 - 0
examples/simpy/requirements.txt

@@ -0,0 +1,2 @@
+nicegui>=1.2
+simpy

+ 65 - 0
examples/todo_list/main.py

@@ -0,0 +1,65 @@
+#!/usr/bin/env python3
+from dataclasses import dataclass
+from typing import List
+
+from nicegui import ui
+
+
+@dataclass
+class TodoItem:
+    name: str
+    done: bool = False
+
+
+items: List[TodoItem] = [
+    TodoItem('Buy milk', done=True),
+    TodoItem('Clean the house'),
+    TodoItem('Call mom'),
+]
+
+
+def add(name: str) -> None:
+    items.append(TodoItem(name))
+    add_input.value = None
+    render_list.refresh()
+
+
+def remove(item: TodoItem) -> None:
+    items.remove(item)
+    render_list.refresh()
+
+
+def toggle(item: TodoItem) -> None:
+    item.done = not item.done
+    render_list.refresh()
+
+
+def rename(item: TodoItem, name: str) -> None:
+    item.name = name
+    render_list.refresh()
+
+
+@ui.refreshable
+def render_list():
+    if not items:
+        ui.label('List is empty.')
+        return
+    ui.linear_progress(sum(item.done for item in items) / len(items), show_value=False)
+    with ui.row().classes('justify-center w-full'):
+        ui.label(f'Completed: {sum(item.done for item in items)}')
+        ui.label(f'Remaining: {sum(not item.done for item in items)}')
+    for item in items:
+        with ui.row().classes('items-center'):
+            ui.checkbox(value=item.done, on_change=lambda _, item=item: toggle(item))
+            input = ui.input(value=item.name).classes('flex-grow')
+            input.on('keydown.enter', lambda _, item=item, input=input: rename(item, input.value))
+            ui.button(on_click=lambda _, item=item: remove(item)).props('flat fab-mini icon=delete color=grey')
+
+
+with ui.card().classes('w-80 items-stretch'):
+    ui.label('Todo list').classes('text-semibold text-2xl')
+    render_list()
+    add_input = ui.input('New item').classes('mx-12')
+    add_input.on('keydown.enter', lambda: add(add_input.value))
+
+ui.run()

+ 1 - 0
main.py

@@ -270,6 +270,7 @@ async def index_page(client: Client) -> None:
             example_link('Local File Picker', 'demonstrates a dialog for selecting files locally on the server')
             example_link('Search as you type', 'using public API of thecocktaildb.com to search for cocktails')
             example_link('Menu and Tabs', 'uses Quasar to create foldable menu and tabs inside a header bar')
+            example_link('Todo list', 'shows a simple todo list with checkboxes and text input')
             example_link('Trello Cards', 'shows Trello-like cards that can be dragged and dropped into columns')
             example_link('Slots', 'shows how to use scoped slots to customize Quasar elements')
             example_link('Table and slots', 'shows how to use component slots in a table')

+ 5 - 5
nicegui/binding.py

@@ -8,7 +8,7 @@ from . import globals
 
 bindings: DefaultDict[Tuple[int, str], List] = defaultdict(list)
 bindable_properties: Dict[Tuple[int, str], Any] = {}
-active_links: List[Tuple[Any, str, Any, str, Callable]] = []
+active_links: List[Tuple[Any, str, Any, str, Callable[[Any], Any]]] = []
 
 
 def get_attribute(obj: Union[object, Dict], name: str) -> Any:
@@ -54,14 +54,14 @@ def propagate(source_obj: Any, source_name: str, visited: Optional[Set[Tuple[int
             propagate(target_obj, target_name, visited)
 
 
-def bind_to(self_obj: Any, self_name: str, other_obj: Any, other_name: str, forward: Callable) -> None:
+def bind_to(self_obj: Any, self_name: str, other_obj: Any, other_name: str, forward: Callable[[Any], Any]) -> None:
     bindings[(id(self_obj), self_name)].append((self_obj, other_obj, other_name, forward))
     if (id(self_obj), self_name) not in bindable_properties:
         active_links.append((self_obj, self_name, other_obj, other_name, forward))
     propagate(self_obj, self_name)
 
 
-def bind_from(self_obj: Any, self_name: str, other_obj: Any, other_name: str, backward: Callable) -> None:
+def bind_from(self_obj: Any, self_name: str, other_obj: Any, other_name: str, backward: Callable[[Any], Any]) -> None:
     bindings[(id(other_obj), other_name)].append((other_obj, self_obj, self_name, backward))
     if (id(other_obj), other_name) not in bindable_properties:
         active_links.append((other_obj, other_name, self_obj, self_name, backward))
@@ -69,14 +69,14 @@ def bind_from(self_obj: Any, self_name: str, other_obj: Any, other_name: str, ba
 
 
 def bind(self_obj: Any, self_name: str, other_obj: Any, other_name: str, *,
-         forward: Callable = lambda x: x, backward: Callable = lambda x: x) -> None:
+         forward: Callable[[Any], Any] = lambda x: x, backward: Callable[[Any], Any] = lambda x: x) -> None:
     bind_from(self_obj, self_name, other_obj, other_name, backward=backward)
     bind_to(self_obj, self_name, other_obj, other_name, forward=forward)
 
 
 class BindableProperty:
 
-    def __init__(self, on_change: Optional[Callable] = None) -> None:
+    def __init__(self, on_change: Optional[Callable[..., Any]] = None) -> None:
         self.on_change = on_change
 
     def __set_name__(self, _, name: str) -> None:

+ 5 - 5
nicegui/client.py

@@ -47,8 +47,8 @@ class Client:
 
         self.page = page
 
-        self.connect_handlers: List[Union[Callable, Awaitable]] = []
-        self.disconnect_handlers: List[Union[Callable, Awaitable]] = []
+        self.connect_handlers: List[Union[Callable[..., Any], Awaitable]] = []
+        self.disconnect_handlers: List[Union[Callable[..., Any], Awaitable]] = []
 
     @property
     def ip(self) -> Optional[str]:
@@ -130,7 +130,7 @@ class Client:
             await asyncio.sleep(check_interval)
         return self.waiting_javascript_commands.pop(request_id)
 
-    def open(self, target: Union[Callable, str]) -> None:
+    def open(self, target: Union[Callable[..., Any], str]) -> None:
         """Open a new page in the client."""
         path = target if isinstance(target, str) else globals.page_routes[target]
         outbox.enqueue_message('open', path, self.id)
@@ -139,10 +139,10 @@ class Client:
         """Download a file from the given URL."""
         outbox.enqueue_message('download', {'url': url, 'filename': filename}, self.id)
 
-    def on_connect(self, handler: Union[Callable, Awaitable]) -> None:
+    def on_connect(self, handler: Union[Callable[..., Any], Awaitable]) -> None:
         """Register a callback to be called when the client connects."""
         self.connect_handlers.append(handler)
 
-    def on_disconnect(self, handler: Union[Callable, Awaitable]) -> None:
+    def on_disconnect(self, handler: Union[Callable[..., Any], Awaitable]) -> None:
         """Register a callback to be called when the client disconnects."""
         self.disconnect_handlers.append(handler)

+ 12 - 8
nicegui/element.py

@@ -3,7 +3,7 @@ from __future__ import annotations
 import re
 import warnings
 from copy import deepcopy
-from typing import TYPE_CHECKING, Any, Callable, Dict, List, Optional, Union
+from typing import TYPE_CHECKING, Any, Callable, Dict, Iterator, List, Optional, Union
 
 from typing_extensions import Self
 
@@ -75,9 +75,14 @@ class Element(Visibility):
     def __exit__(self, *_):
         self.default_slot.__exit__(*_)
 
+    def __iter__(self) -> Iterator[Element]:
+        for slot in self.slots.values():
+            for child in slot:
+                yield child
+
     def _collect_slot_dict(self) -> Dict[str, List[int]]:
         return {
-            name: {'template': slot.template, 'ids': [child.id for child in slot.children]}
+            name: {'template': slot.template, 'ids': [child.id for child in slot]}
             for name, slot in self.slots.items()
         }
 
@@ -197,7 +202,7 @@ class Element(Visibility):
 
     def on(self,
            type: str,
-           handler: Optional[Callable],
+           handler: Optional[Callable[..., Any]] = None,
            args: Optional[List[str]] = None, *,
            throttle: float = 0.0,
            leading_events: bool = True,
@@ -251,9 +256,8 @@ class Element(Visibility):
 
     def _collect_descendant_ids(self) -> List[int]:
         ids: List[int] = [self.id]
-        for slot in self.slots.values():
-            for child in slot.children:
-                ids.extend(child._collect_descendant_ids())
+        for child in self:
+            ids.extend(child._collect_descendant_ids())
         return ids
 
     def clear(self) -> None:
@@ -286,12 +290,12 @@ class Element(Visibility):
         :param element: either the element instance or its ID
         """
         if isinstance(element, int):
-            children = [child for slot in self.slots.values() for child in slot.children]
+            children = list(self)
             element = children[element]
         binding.remove([element], Element)
         del self.client.elements[element.id]
         for slot in self.slots.values():
-            slot.children[:] = [e for e in slot.children if e.id != element.id]
+            slot.children[:] = [e for e in slot if e.id != element.id]
         self.update()
 
     def delete(self) -> None:

+ 2 - 2
nicegui/elements/button.py

@@ -1,5 +1,5 @@
 import asyncio
-from typing import Callable, Optional
+from typing import Any, Callable, Optional
 
 from ..colors import set_background_color
 from ..events import ClickEventArguments, handle_event
@@ -11,7 +11,7 @@ class Button(TextElement, DisableableElement):
 
     def __init__(self,
                  text: str = '', *,
-                 on_click: Optional[Callable] = None,
+                 on_click: Optional[Callable[..., Any]] = None,
                  color: Optional[str] = 'primary',
                  ) -> None:
         """Button

+ 2 - 2
nicegui/elements/checkbox.py

@@ -1,4 +1,4 @@
-from typing import Callable, Optional
+from typing import Any, Callable, Optional
 
 from .mixins.disableable_element import DisableableElement
 from .mixins.text_element import TextElement
@@ -7,7 +7,7 @@ from .mixins.value_element import ValueElement
 
 class Checkbox(TextElement, ValueElement, DisableableElement):
 
-    def __init__(self, text: str = '', *, value: bool = False, on_change: Optional[Callable] = None) -> None:
+    def __init__(self, text: str = '', *, value: bool = False, on_change: Optional[Callable[..., Any]] = None) -> None:
         """Checkbox
 
         :param text: the label to display next to the checkbox

+ 5 - 1
nicegui/elements/choice_element.py

@@ -6,7 +6,11 @@ from .mixins.value_element import ValueElement
 class ChoiceElement(ValueElement):
 
     def __init__(self, *,
-                 tag: str, options: Union[List, Dict], value: Any, on_change: Optional[Callable] = None) -> None:
+                 tag: str,
+                 options: Union[List, Dict],
+                 value: Any,
+                 on_change: Optional[Callable[..., Any]] = None,
+                 ) -> None:
         self.options = options
         self._values: List[str] = []
         self._labels: List[str] = []

+ 7 - 3
nicegui/elements/color_input.py

@@ -1,4 +1,4 @@
-from typing import Callable, Optional
+from typing import Any, Callable, Optional
 
 from nicegui import ui
 
@@ -10,8 +10,12 @@ from .mixins.value_element import ValueElement
 class ColorInput(ValueElement, DisableableElement):
     LOOPBACK = False
 
-    def __init__(self, label: Optional[str] = None, *,
-                 placeholder: Optional[str] = None, value: str = '', on_change: Optional[Callable] = None) -> None:
+    def __init__(self,
+                 label: Optional[str] = None, *,
+                 placeholder: Optional[str] = None,
+                 value: str = '',
+                 on_change: Optional[Callable[..., Any]] = None,
+                 ) -> None:
         """Color Input
 
         :param label: displayed label for the color input

+ 2 - 2
nicegui/elements/color_picker.py

@@ -1,4 +1,4 @@
-from typing import Callable, Dict
+from typing import Any, Callable, Dict
 
 from nicegui.events import ColorPickEventArguments, handle_event
 
@@ -8,7 +8,7 @@ from .menu import Menu
 
 class ColorPicker(Menu):
 
-    def __init__(self, *, on_pick: Callable, value: bool = False) -> None:
+    def __init__(self, *, on_pick: Callable[..., Any], value: bool = False) -> None:
         """Color Picker
 
         :param on_pick: callback to execute when a color is picked

+ 2 - 2
nicegui/elements/date.py

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

+ 2 - 2
nicegui/elements/input.py

@@ -14,9 +14,9 @@ class Input(ValueElement, DisableableElement):
                  value: str = '',
                  password: bool = False,
                  password_toggle_button: bool = False,
-                 on_change: Optional[Callable] = None,
+                 on_change: Optional[Callable[..., Any]] = None,
                  autocomplete: Optional[List[str]] = None,
-                 validation: Dict[str, Callable] = {}) -> None:
+                 validation: Dict[str, Callable[..., bool]] = {}) -> None:
         """Text Input
 
         This element is based on Quasar's `QInput <https://quasar.dev/vue-components/input>`_ component.

+ 7 - 3
nicegui/elements/interactive_image.py

@@ -1,6 +1,6 @@
 from __future__ import annotations
 
-from typing import Callable, Dict, List, Optional
+from typing import Any, Callable, Dict, List, Optional
 
 from ..dependencies import register_component
 from ..events import MouseEventArguments, handle_event
@@ -13,9 +13,13 @@ register_component('interactive_image', __file__, 'interactive_image.js')
 class InteractiveImage(SourceElement, ContentElement):
     CONTENT_PROP = 'content'
 
-    def __init__(self, source: str = '', *,
+    def __init__(self,
+                 source: str = '', *,
                  content: str = '',
-                 on_mouse: Optional[Callable] = None, events: List[str] = ['click'], cross: bool = False) -> None:
+                 on_mouse: Optional[Callable[..., Any]] = None,
+                 events: List[str] = ['click'],
+                 cross: bool = False,
+                 ) -> None:
         """Interactive Image
 
         Create an image with an SVG overlay that handles mouse events and yields image coordinates.

+ 3 - 3
nicegui/elements/joystick.py

@@ -10,9 +10,9 @@ register_component('joystick', __file__, 'joystick.vue', ['lib/nipplejs.min.js']
 class Joystick(Element):
 
     def __init__(self, *,
-                 on_start: Optional[Callable] = None,
-                 on_move: Optional[Callable] = None,
-                 on_end: Optional[Callable] = None,
+                 on_start: Optional[Callable[..., Any]] = None,
+                 on_move: Optional[Callable[..., Any]] = None,
+                 on_end: Optional[Callable[..., Any]] = None,
                  throttle: float = 0.05,
                  ** options: Any) -> None:
         """Joystick

+ 2 - 2
nicegui/elements/keyboard.py

@@ -1,4 +1,4 @@
-from typing import Callable, Dict, List
+from typing import Any, Callable, Dict, List
 
 from typing_extensions import Literal
 
@@ -14,7 +14,7 @@ class Keyboard(Element):
     active = BindableProperty()
 
     def __init__(self,
-                 on_key: Callable, *,
+                 on_key: Callable[..., Any], *,
                  active: bool = True,
                  repeating: bool = True,
                  ignore: List[Literal['input', 'select', 'button', 'textarea']] = ['input', 'select', 'button', 'textarea'],

+ 9 - 3
nicegui/elements/line_plot.py

@@ -1,11 +1,17 @@
-from typing import List
+from typing import Any, List
 
 from .pyplot import Pyplot
 
 
 class LinePlot(Pyplot):
 
-    def __init__(self, *, n: int = 1, limit: int = 100, update_every: int = 1, close: bool = True, **kwargs) -> None:
+    def __init__(self, *,
+                 n: int = 1,
+                 limit: int = 100,
+                 update_every: int = 1,
+                 close: bool = True,
+                 **kwargs: Any,
+                 ) -> None:
         """Line Plot
 
         Create a line plot using pyplot.
@@ -26,7 +32,7 @@ class LinePlot(Pyplot):
         self.update_every = update_every
         self.push_counter = 0
 
-    def with_legend(self, titles: List[str], **kwargs):
+    def with_legend(self, titles: List[str], **kwargs: Any):
         self.fig.gca().legend(titles, **kwargs)
         self._convert_to_html()
         return self

+ 2 - 2
nicegui/elements/link.py

@@ -1,4 +1,4 @@
-from typing import Callable, Union
+from typing import Any, Callable, Union
 
 from .. import globals
 from ..dependencies import register_component
@@ -10,7 +10,7 @@ register_component('link', __file__, 'link.js')
 
 class Link(TextElement):
 
-    def __init__(self, text: str = '', target: Union[Callable, str] = '#', new_tab: bool = False) -> None:
+    def __init__(self, text: str = '', target: Union[Callable[..., Any], str] = '#', new_tab: bool = False) -> None:
         """Link
 
         Create a hyperlink.

+ 6 - 2
nicegui/elements/menu.py

@@ -1,4 +1,4 @@
-from typing import Callable, Optional
+from typing import Any, Callable, Optional
 
 from .. import globals
 from ..events import ClickEventArguments, handle_event
@@ -28,7 +28,11 @@ class Menu(ValueElement):
 
 class MenuItem(TextElement):
 
-    def __init__(self, text: str = '', on_click: Optional[Callable] = None, *, auto_close: bool = True) -> None:
+    def __init__(self,
+                 text: str = '',
+                 on_click: Optional[Callable[..., Any]] = None, *,
+                 auto_close: bool = True,
+                 ) -> None:
         """Menu Item
 
         A menu item to be added to a menu.

+ 8 - 5
nicegui/elements/mixins/content_element.py

@@ -10,7 +10,7 @@ class ContentElement(Element):
     CONTENT_PROP = 'innerHTML'
     content = BindableProperty(on_change=lambda sender, content: sender.on_content_change(content))
 
-    def __init__(self, *, content: str, **kwargs) -> None:
+    def __init__(self, *, content: str, **kwargs: Any) -> None:
         super().__init__(**kwargs)
         self.content = content
         self.on_content_change(content)
@@ -18,7 +18,8 @@ class ContentElement(Element):
     def bind_content_to(self,
                         target_object: Any,
                         target_name: str = 'content',
-                        forward: Callable = lambda x: x) -> Self:
+                        forward: Callable[..., Any] = lambda x: x,
+                        ) -> Self:
         """Bind the content of this element to the target object's target_name property.
 
         The binding works one way only, from this element to the target.
@@ -33,7 +34,8 @@ class ContentElement(Element):
     def bind_content_from(self,
                           target_object: Any,
                           target_name: str = 'content',
-                          backward: Callable = lambda x: x) -> Self:
+                          backward: Callable[..., Any] = lambda x: x,
+                          ) -> Self:
         """Bind the content of this element from the target object's target_name property.
 
         The binding works one way only, from the target to this element.
@@ -48,8 +50,9 @@ class ContentElement(Element):
     def bind_content(self,
                      target_object: Any,
                      target_name: str = 'content', *,
-                     forward: Callable = lambda x: x,
-                     backward: Callable = lambda x: x) -> Self:
+                     forward: Callable[..., Any] = lambda x: x,
+                     backward: Callable[..., Any] = lambda x: x,
+                     ) -> Self:
         """Bind the content of this element to the target object's target_name property.
 
         The binding works both ways, from this element to the target and from the target to this element.

+ 8 - 5
nicegui/elements/mixins/disableable_element.py

@@ -9,7 +9,7 @@ from ...element import Element
 class DisableableElement(Element):
     enabled = BindableProperty(on_change=lambda sender, value: sender.on_enabled_change(value))
 
-    def __init__(self, **kwargs) -> None:
+    def __init__(self, **kwargs: Any) -> None:
         super().__init__(**kwargs)
         self.enabled = True
 
@@ -24,7 +24,8 @@ class DisableableElement(Element):
     def bind_enabled_to(self,
                         target_object: Any,
                         target_name: str = 'enabled',
-                        forward: Callable = lambda x: x) -> Self:
+                        forward: Callable[..., Any] = lambda x: x,
+                        ) -> Self:
         """Bind the enabled state of this element to the target object's target_name property.
 
         The binding works one way only, from this element to the target.
@@ -39,7 +40,8 @@ class DisableableElement(Element):
     def bind_enabled_from(self,
                           target_object: Any,
                           target_name: str = 'enabled',
-                          backward: Callable = lambda x: x) -> Self:
+                          backward: Callable[..., Any] = lambda x: x,
+                          ) -> Self:
         """Bind the enabled state of this element from the target object's target_name property.
 
         The binding works one way only, from the target to this element.
@@ -54,8 +56,9 @@ class DisableableElement(Element):
     def bind_enabled(self,
                      target_object: Any,
                      target_name: str = 'enabled', *,
-                     forward: Callable = lambda x: x,
-                     backward: Callable = lambda x: x) -> Self:
+                     forward: Callable[..., Any] = lambda x: x,
+                     backward: Callable[..., Any] = lambda x: x,
+                     ) -> Self:
         """Bind the enabled state of this element to the target object's target_name property.
 
         The binding works both ways, from this element to the target and from the target to this element.

+ 8 - 5
nicegui/elements/mixins/filter_element.py

@@ -10,7 +10,7 @@ class FilterElement(Element):
     FILTER_PROP = 'filter'
     filter = BindableProperty(on_change=lambda sender, filter: sender.on_filter_change(filter))
 
-    def __init__(self, *, filter: Optional[str] = None, **kwargs) -> None:
+    def __init__(self, *, filter: Optional[str] = None, **kwargs: Any) -> None:
         super().__init__(**kwargs)
         self.filter = filter
         self._props[self.FILTER_PROP] = filter
@@ -18,7 +18,8 @@ class FilterElement(Element):
     def bind_filter_to(self,
                        target_object: Any,
                        target_name: str = 'filter',
-                       forward: Callable = lambda x: x) -> Self:
+                       forward: Callable[..., Any] = lambda x: x,
+                       ) -> Self:
         """Bind the filter of this element to the target object's target_name property.
 
         The binding works one way only, from this element to the target.
@@ -33,7 +34,8 @@ class FilterElement(Element):
     def bind_filter_from(self,
                          target_object: Any,
                          target_name: str = 'filter',
-                         backward: Callable = lambda x: x) -> Self:
+                         backward: Callable[..., Any] = lambda x: x,
+                         ) -> Self:
         """Bind the filter of this element from the target object's target_name property.
 
         The binding works one way only, from the target to this element.
@@ -48,8 +50,9 @@ class FilterElement(Element):
     def bind_filter(self,
                     target_object: Any,
                     target_name: str = 'filter', *,
-                    forward: Callable = lambda x: x,
-                    backward: Callable = lambda x: x) -> Self:
+                    forward: Callable[..., Any] = lambda x: x,
+                    backward: Callable[..., Any] = lambda x: x,
+                    ) -> Self:
         """Bind the filter of this element to the target object's target_name property.
 
         The binding works both ways, from this element to the target and from the target to this element.

+ 8 - 5
nicegui/elements/mixins/source_element.py

@@ -9,7 +9,7 @@ from ...element import Element
 class SourceElement(Element):
     source = BindableProperty(on_change=lambda sender, source: sender.on_source_change(source))
 
-    def __init__(self, *, source: str, **kwargs) -> None:
+    def __init__(self, *, source: str, **kwargs: Any) -> None:
         super().__init__(**kwargs)
         self.source = source
         self._props['src'] = source
@@ -17,7 +17,8 @@ class SourceElement(Element):
     def bind_source_to(self,
                        target_object: Any,
                        target_name: str = 'source',
-                       forward: Callable = lambda x: x) -> Self:
+                       forward: Callable[..., Any] = lambda x: x,
+                       ) -> Self:
         """Bind the source of this element to the target object's target_name property.
 
         The binding works one way only, from this element to the target.
@@ -32,7 +33,8 @@ class SourceElement(Element):
     def bind_source_from(self,
                          target_object: Any,
                          target_name: str = 'source',
-                         backward: Callable = lambda x: x) -> Self:
+                         backward: Callable[..., Any] = lambda x: x,
+                         ) -> Self:
         """Bind the source of this element from the target object's target_name property.
 
         The binding works one way only, from the target to this element.
@@ -47,8 +49,9 @@ class SourceElement(Element):
     def bind_source(self,
                     target_object: Any,
                     target_name: str = 'source', *,
-                    forward: Callable = lambda x: x,
-                    backward: Callable = lambda x: x) -> Self:
+                    forward: Callable[..., Any] = lambda x: x,
+                    backward: Callable[..., Any] = lambda x: x,
+                    ) -> Self:
         """Bind the source of this element to the target object's target_name property.
 
         The binding works both ways, from this element to the target and from the target to this element.

+ 8 - 5
nicegui/elements/mixins/text_element.py

@@ -9,7 +9,7 @@ from ...element import Element
 class TextElement(Element):
     text = BindableProperty(on_change=lambda sender, text: sender.on_text_change(text))
 
-    def __init__(self, *, text: str, **kwargs) -> None:
+    def __init__(self, *, text: str, **kwargs: Any) -> None:
         super().__init__(**kwargs)
         self.text = text
         self._text_to_model_text(text)
@@ -17,7 +17,8 @@ class TextElement(Element):
     def bind_text_to(self,
                      target_object: Any,
                      target_name: str = 'text',
-                     forward: Callable = lambda x: x) -> Self:
+                     forward: Callable[..., Any] = lambda x: x,
+                     ) -> Self:
         """Bind the text of this element to the target object's target_name property.
 
         The binding works one way only, from this element to the target.
@@ -32,7 +33,8 @@ class TextElement(Element):
     def bind_text_from(self,
                        target_object: Any,
                        target_name: str = 'text',
-                       backward: Callable = lambda x: x) -> Self:
+                       backward: Callable[..., Any] = lambda x: x,
+                       ) -> Self:
         """Bind the text of this element from the target object's target_name property.
 
         The binding works one way only, from the target to this element.
@@ -47,8 +49,9 @@ class TextElement(Element):
     def bind_text(self,
                   target_object: Any,
                   target_name: str = 'text', *,
-                  forward: Callable = lambda x: x,
-                  backward: Callable = lambda x: x) -> Self:
+                  forward: Callable[..., Any] = lambda x: x,
+                  backward: Callable[..., Any] = lambda x: x,
+                  ) -> Self:
         """Bind the text of this element to the target object's target_name property.
 
         The binding works both ways, from this element to the target and from the target to this element.

+ 13 - 5
nicegui/elements/mixins/value_element.py

@@ -13,7 +13,12 @@ class ValueElement(Element):
     LOOPBACK = True
     value = BindableProperty(on_change=lambda sender, value: sender.on_value_change(value))
 
-    def __init__(self, *, value: Any, on_value_change: Optional[Callable], throttle: float = 0, **kwargs) -> None:
+    def __init__(self, *,
+                 value: Any,
+                 on_value_change: Optional[Callable[..., Any]],
+                 throttle: float = 0,
+                 **kwargs: Any,
+                 ) -> None:
         super().__init__(**kwargs)
         self.set_value(value)
         self._props[self.VALUE_PROP] = self._value_to_model_value(value)
@@ -30,7 +35,8 @@ class ValueElement(Element):
     def bind_value_to(self,
                       target_object: Any,
                       target_name: str = 'value',
-                      forward: Callable = lambda x: x) -> Self:
+                      forward: Callable[..., Any] = lambda x: x,
+                      ) -> Self:
         """Bind the value of this element to the target object's target_name property.
 
         The binding works one way only, from this element to the target.
@@ -45,7 +51,8 @@ class ValueElement(Element):
     def bind_value_from(self,
                         target_object: Any,
                         target_name: str = 'value',
-                        backward: Callable = lambda x: x) -> Self:
+                        backward: Callable[..., Any] = lambda x: x,
+                        ) -> Self:
         """Bind the value of this element from the target object's target_name property.
 
         The binding works one way only, from the target to this element.
@@ -60,8 +67,9 @@ class ValueElement(Element):
     def bind_value(self,
                    target_object: Any,
                    target_name: str = 'value', *,
-                   forward: Callable = lambda x: x,
-                   backward: Callable = lambda x: x) -> Self:
+                   forward: Callable[..., Any] = lambda x: x,
+                   backward: Callable[..., Any] = lambda x: x,
+                   ) -> Self:
         """Bind the value of this element to the target object's target_name property.
 
         The binding works both ways, from this element to the target and from the target to this element.

+ 8 - 6
nicegui/elements/mixins/visibility.py

@@ -11,14 +11,15 @@ if TYPE_CHECKING:
 class Visibility:
     visible = BindableProperty(on_change=lambda sender, visible: sender.on_visibility_change(visible))
 
-    def __init__(self, **kwargs) -> None:
+    def __init__(self, **kwargs: Any) -> None:
         super().__init__(**kwargs)
         self.visible = True
 
     def bind_visibility_to(self,
                            target_object: Any,
                            target_name: str = 'visible',
-                           forward: Callable = lambda x: x) -> Self:
+                           forward: Callable[..., Any] = lambda x: x,
+                           ) -> Self:
         """Bind the visibility of this element to the target object's target_name property.
 
         The binding works one way only, from this element to the target.
@@ -33,7 +34,7 @@ class Visibility:
     def bind_visibility_from(self,
                              target_object: Any,
                              target_name: str = 'visible',
-                             backward: Callable = lambda x: x, *,
+                             backward: Callable[..., Any] = lambda x: x, *,
                              value: Any = None) -> Self:
         """Bind the visibility of this element from the target object's target_name property.
 
@@ -52,9 +53,10 @@ class Visibility:
     def bind_visibility(self,
                         target_object: Any,
                         target_name: str = 'visible', *,
-                        forward: Callable = lambda x: x,
-                        backward: Callable = lambda x: x,
-                        value: Any = None) -> Self:
+                        forward: Callable[..., Any] = lambda x: x,
+                        backward: Callable[..., Any] = lambda x: x,
+                        value: Any = None,
+                        ) -> Self:
         """Bind the visibility of this element to the target object's target_name property.
 
         The binding works both ways, from this element to the target and from the target to this element.

+ 3 - 2
nicegui/elements/number.py

@@ -17,8 +17,9 @@ class Number(ValueElement, DisableableElement):
                  prefix: Optional[str] = None,
                  suffix: Optional[str] = None,
                  format: Optional[str] = None,
-                 on_change: Optional[Callable] = None,
-                 validation: Dict[str, Callable] = {}) -> None:
+                 on_change: Optional[Callable[..., Any]] = None,
+                 validation: Dict[str, Callable[..., bool]] = {},
+                 ) -> None:
         """Number Input
 
         This element is based on Quasar's `QInput <https://quasar.dev/vue-components/input>`_ component.

+ 2 - 1
nicegui/elements/pyplot.py

@@ -1,5 +1,6 @@
 import asyncio
 import io
+from typing import Any
 
 import matplotlib.pyplot as plt
 
@@ -9,7 +10,7 @@ from ..element import Element
 
 class Pyplot(Element):
 
-    def __init__(self, *, close: bool = True, **kwargs) -> None:
+    def __init__(self, *, close: bool = True, **kwargs: Any) -> None:
         """Pyplot Context
 
         Create a context to configure a `Matplotlib <https://matplotlib.org/>`_ plot.

+ 5 - 1
nicegui/elements/radio.py

@@ -6,7 +6,11 @@ from .mixins.disableable_element import DisableableElement
 
 class Radio(ChoiceElement, DisableableElement):
 
-    def __init__(self, options: Union[List, Dict], *, value: Any = None, on_change: Optional[Callable] = None):
+    def __init__(self,
+                 options: Union[List, Dict], *,
+                 value: Any = None,
+                 on_change: Optional[Callable[..., Any]] = None,
+                 ) -> None:
         """Radio Selection
 
         The options can be specified as a list of values, or as a dictionary mapping values to labels.

+ 2 - 1
nicegui/elements/scene.py

@@ -57,7 +57,8 @@ class Scene(Element):
                  width: int = 400,
                  height: int = 300,
                  grid: bool = True,
-                 on_click: Optional[Callable] = None) -> None:
+                 on_click: Optional[Callable[..., Any]] = None,
+                 ) -> None:
         """3D Scene
 
         Display a 3d scene using `three.js <https://threejs.org/>`_.

+ 3 - 2
nicegui/elements/select.py

@@ -15,8 +15,9 @@ class Select(ChoiceElement, DisableableElement):
     def __init__(self, options: Union[List, Dict], *,
                  label: Optional[str] = None,
                  value: Any = None,
-                 on_change: Optional[Callable] = None,
-                 with_input: bool = False) -> None:
+                 on_change: Optional[Callable[..., Any]] = None,
+                 with_input: bool = False,
+                 ) -> None:
         """Dropdown Selection
 
         The options can be specified as a list of values, or as a dictionary mapping values to labels.

+ 3 - 2
nicegui/elements/slider.py

@@ -1,4 +1,4 @@
-from typing import Callable, Optional
+from typing import Any, Callable, Optional
 
 from .mixins.disableable_element import DisableableElement
 from .mixins.value_element import ValueElement
@@ -11,7 +11,8 @@ class Slider(ValueElement, DisableableElement):
                  max: float,
                  step: float = 1.0,
                  value: Optional[float] = None,
-                 on_change: Optional[Callable] = None) -> None:
+                 on_change: Optional[Callable[..., Any]] = None,
+                 ) -> None:
         """Slider
 
         :param min: lower bound of the slider

+ 3 - 2
nicegui/elements/splitter.py

@@ -1,4 +1,4 @@
-from typing import Callable, Optional, Tuple
+from typing import Any, Callable, Optional, Tuple
 
 from .mixins.disableable_element import DisableableElement
 from .mixins.value_element import ValueElement
@@ -11,7 +11,8 @@ class Splitter(ValueElement, DisableableElement):
                  reverse: Optional[bool] = False,
                  limits: Optional[Tuple[float, float]] = (0, 100),
                  value: Optional[float] = 50,
-                 on_change: Optional[Callable] = None) -> None:
+                 on_change: Optional[Callable[..., Any]] = None,
+                 ) -> None:
         """Splitter
 
         The `ui.splitter` element divides the screen space into resizable sections, 

+ 2 - 2
nicegui/elements/switch.py

@@ -1,4 +1,4 @@
-from typing import Callable, Optional
+from typing import Any, Callable, Optional
 
 from .mixins.disableable_element import DisableableElement
 from .mixins.text_element import TextElement
@@ -7,7 +7,7 @@ from .mixins.value_element import ValueElement
 
 class Switch(TextElement, ValueElement, DisableableElement):
 
-    def __init__(self, text: str = '', *, value: bool = False, on_change: Optional[Callable] = None) -> None:
+    def __init__(self, text: str = '', *, value: bool = False, on_change: Optional[Callable[..., Any]] = None) -> None:
         """Switch
 
         :param text: the label to display next to the switch

+ 2 - 2
nicegui/elements/table.py

@@ -1,4 +1,4 @@
-from typing import Callable, Dict, List, Optional
+from typing import Any, Callable, Dict, List, Optional
 
 from typing_extensions import Literal
 
@@ -16,7 +16,7 @@ class Table(FilterElement):
                  title: Optional[str] = None,
                  selection: Optional[Literal['single', 'multiple']] = None,
                  pagination: Optional[int] = None,
-                 on_select: Optional[Callable] = None,
+                 on_select: Optional[Callable[..., Any]] = None,
                  ) -> None:
         """Table
 

+ 3 - 2
nicegui/elements/tabs.py

@@ -9,7 +9,8 @@ class Tabs(ValueElement):
 
     def __init__(self, *,
                  value: Any = None,
-                 on_change: Optional[Callable] = None) -> None:
+                 on_change: Optional[Callable[..., Any]] = None,
+                 ) -> None:
         """Tabs
 
         This element represents `Quasar's QTabs <https://quasar.dev/vue-components/tabs#qtabs-api>`_ component.
@@ -47,7 +48,7 @@ class TabPanels(ValueElement):
     def __init__(self,
                  tabs: Tabs, *,
                  value: Any = None,
-                 on_change: Optional[Callable] = None,
+                 on_change: Optional[Callable[..., Any]] = None,
                  animated: bool = True,
                  ) -> None:
         """Tab Panels

+ 4 - 3
nicegui/elements/textarea.py

@@ -1,4 +1,4 @@
-from typing import Callable, Dict, Optional
+from typing import Any, Callable, Dict, Optional
 
 from .input import Input
 
@@ -9,8 +9,9 @@ class Textarea(Input):
                  label: Optional[str] = None, *,
                  placeholder: Optional[str] = None,
                  value: str = '',
-                 on_change: Optional[Callable] = None,
-                 validation: Dict[str, Callable] = {}) -> None:
+                 on_change: Optional[Callable[..., Any]] = None,
+                 validation: Dict[str, Callable[..., bool]] = {},
+                 ) -> None:
         """Textarea
 
         This element is based on Quasar's `QInput <https://quasar.dev/vue-components/input>`_ component.

+ 4 - 4
nicegui/elements/time.py

@@ -1,4 +1,4 @@
-from typing import Callable, Optional
+from typing import Any, Callable, Optional
 
 from .mixins.disableable_element import DisableableElement
 from .mixins.value_element import ValueElement
@@ -7,10 +7,10 @@ from .mixins.value_element import ValueElement
 class Time(ValueElement, DisableableElement):
 
     def __init__(self,
-                 value: Optional[str] = None,
-                 *,
+                 value: Optional[str] = None, *,
                  mask: str = 'HH:mm',
-                 on_change: Optional[Callable] = None) -> None:
+                 on_change: Optional[Callable[..., Any]] = None,
+                 ) -> None:
         """Time Input
 
         This element is based on Quasar's `QTime <https://quasar.dev/vue-components/date>`_ component.

+ 5 - 1
nicegui/elements/toggle.py

@@ -6,7 +6,11 @@ from .mixins.disableable_element import DisableableElement
 
 class Toggle(ChoiceElement, DisableableElement):
 
-    def __init__(self, options: Union[List, Dict], *, value: Any = None, on_change: Optional[Callable] = None) -> None:
+    def __init__(self,
+                 options: Union[List, Dict], *,
+                 value: Any = None,
+                 on_change: Optional[Callable[..., Any]] = None,
+                 ) -> None:
         """Toggle
 
         The options can be specified as a list of values, or as a dictionary mapping values to labels.

+ 4 - 3
nicegui/elements/tree.py

@@ -11,9 +11,10 @@ class Tree(Element):
                  node_key: str = 'id',
                  label_key: str = 'label',
                  children_key: str = 'children',
-                 on_select: Optional[Callable] = None,
-                 on_expand: Optional[Callable] = None,
-                 on_tick: Optional[Callable] = None) -> None:
+                 on_select: Optional[Callable[..., Any]] = None,
+                 on_expand: Optional[Callable[..., Any]] = None,
+                 on_tick: Optional[Callable[..., Any]] = None,
+                 ) -> None:
         """Tree
 
         Display hierarchical data using Quasar's `QTree <https://quasar.dev/vue-components/tree>`_ component.

+ 3 - 3
nicegui/elements/upload.py

@@ -1,4 +1,4 @@
-from typing import Callable, Optional
+from typing import Any, Callable, Optional
 
 from fastapi import Request, Response
 
@@ -17,8 +17,8 @@ class Upload(DisableableElement):
                  max_file_size: Optional[int] = None,
                  max_total_size: Optional[int] = None,
                  max_files: Optional[int] = None,
-                 on_upload: Optional[Callable] = None,
-                 on_rejected: Optional[Callable] = None,
+                 on_upload: Optional[Callable[..., Any]] = None,
+                 on_rejected: Optional[Callable[..., Any]] = None,
                  label: str = '',
                  auto_upload: bool = False,
                  ) -> None:

+ 1 - 1
nicegui/events.py

@@ -268,7 +268,7 @@ class KeyEventArguments(EventArguments):
     modifiers: KeyboardModifiers
 
 
-def handle_event(handler: Optional[Callable],
+def handle_event(handler: Optional[Callable[..., Any]],
                  arguments: Union[EventArguments, Dict], *,
                  sender: Optional['Element'] = None) -> None:
     try:

+ 1 - 1
nicegui/functions/notify.py

@@ -10,7 +10,7 @@ def notify(message: Any, *,
            closeBtn: Union[bool, str] = False,
            type: Optional[Literal['positive', 'negative', 'warning', 'info', 'ongoing']] = None,
            color: Optional[str] = None,
-           **kwargs,
+           **kwargs: Any,
            ) -> None:
     """Notification
 

+ 2 - 2
nicegui/functions/open.py

@@ -1,9 +1,9 @@
-from typing import Callable, Union
+from typing import Any, Callable, Union
 
 from .. import globals
 
 
-def open(target: Union[Callable, str]) -> None:
+def open(target: Union[Callable[..., Any], str]) -> None:
     """Open
 
     Can be used to programmatically trigger redirects for a specific client.

+ 3 - 3
nicegui/functions/refreshable.py

@@ -12,7 +12,7 @@ register_component('refreshable', __file__, 'refreshable.js')
 
 class refreshable:
 
-    def __init__(self, func: Callable) -> None:
+    def __init__(self, func: Callable[..., Any]) -> None:
         """Refreshable UI functions
 
         The `@ui.refreshable` decorator allows you to create functions that have a `refresh` method.
@@ -26,7 +26,7 @@ class refreshable:
         self.instance = instance
         return self
 
-    def __call__(self, *args, **kwargs) -> None:
+    def __call__(self, *args: Any, **kwargs: Any) -> None:
         self.prune()
         with Element('refreshable') as container:
             self.containers.append((container, args, kwargs))
@@ -50,7 +50,7 @@ class refreshable:
             if container.client.id in globals.clients
         ]
 
-    def _run_in_container(self, container: Element, *args, **kwargs) -> None:
+    def _run_in_container(self, container: Element, *args: Any, **kwargs: Any) -> None:
         if is_coroutine(self.func):
             async def wait_for_result() -> None:
                 with container:

+ 10 - 4
nicegui/functions/timer.py

@@ -1,6 +1,6 @@
 import asyncio
 import time
-from typing import Callable
+from typing import Any, Callable
 
 from .. import background_tasks, globals
 from ..binding import BindableProperty
@@ -11,7 +11,12 @@ class Timer:
     active = BindableProperty()
     interval = BindableProperty()
 
-    def __init__(self, interval: float, callback: Callable, *, active: bool = True, once: bool = False) -> None:
+    def __init__(self,
+                 interval: float,
+                 callback: Callable[..., Any], *,
+                 active: bool = True,
+                 once: bool = False,
+                 ) -> None:
         """Timer
 
         One major drive behind the creation of NiceGUI was the necessity to have a simple approach to update the interface in regular intervals,
@@ -78,10 +83,11 @@ class Timer:
             globals.handle_exception(e)
 
     async def _connected(self, timeout: float = 60.0) -> bool:
-        '''Wait for the client connection before the timer callback can be allowed to manipulate the state.
+        """Wait for the client connection before the timer callback can be allowed to manipulate the state.
+
         See https://github.com/zauberzeug/nicegui/issues/206 for details.
         Returns True if the client is connected, False if the client is not connected and the timer should be cancelled.
-        '''
+        """
         if self.slot.parent.client.shared:
             return True
         else:

+ 7 - 7
nicegui/globals.py

@@ -3,7 +3,7 @@ import inspect
 import logging
 from contextlib import contextmanager
 from enum import Enum
-from typing import TYPE_CHECKING, Awaitable, Callable, Dict, List, Optional, Union
+from typing import TYPE_CHECKING, Any, Awaitable, Callable, Dict, List, Optional, Union
 
 from socketio import AsyncServer
 from uvicorn import Server
@@ -48,13 +48,13 @@ slot_stacks: Dict[int, List['Slot']] = {}
 clients: Dict[str, 'Client'] = {}
 index_client: 'Client'
 
-page_routes: Dict[Callable, str] = {}
+page_routes: Dict[Callable[..., Any], str] = {}
 
-startup_handlers: List[Union[Callable, Awaitable]] = []
-shutdown_handlers: List[Union[Callable, Awaitable]] = []
-connect_handlers: List[Union[Callable, Awaitable]] = []
-disconnect_handlers: List[Union[Callable, Awaitable]] = []
-exception_handlers: List[Callable] = [log.exception]
+startup_handlers: List[Union[Callable[..., Any], Awaitable]] = []
+shutdown_handlers: List[Union[Callable[..., Any], Awaitable]] = []
+connect_handlers: List[Union[Callable[..., Any], Awaitable]] = []
+disconnect_handlers: List[Union[Callable[..., Any], Awaitable]] = []
+exception_handlers: List[Callable[..., Any]] = [log.exception]
 
 
 def get_task_id() -> int:

+ 1 - 1
nicegui/helpers.py

@@ -23,7 +23,7 @@ def is_coroutine(object: Any) -> bool:
     return asyncio.iscoroutinefunction(object)
 
 
-def safe_invoke(func: Union[Callable, Awaitable], client: Optional['Client'] = None) -> None:
+def safe_invoke(func: Union[Callable[..., Any], Awaitable], client: Optional['Client'] = None) -> None:
     try:
         if isinstance(func, Awaitable):
             async def func_with_client():

+ 3 - 3
nicegui/page.py

@@ -1,7 +1,7 @@
 import asyncio
 import inspect
 import time
-from typing import Callable, Optional
+from typing import Any, Callable, Optional
 
 from fastapi import Request, Response
 
@@ -21,7 +21,7 @@ class page:
                  dark: Optional[bool] = ...,
                  language: Language = ...,
                  response_timeout: float = 3.0,
-                 **kwargs,
+                 **kwargs: Any,
                  ) -> None:
         """Page
 
@@ -62,7 +62,7 @@ class page:
     def resolve_language(self) -> Optional[str]:
         return self.language if self.language is not ... else globals.language
 
-    def __call__(self, func: Callable) -> Callable:
+    def __call__(self, func: Callable[..., Any]) -> Callable[..., Any]:
         globals.app.remove_route(self.path)  # NOTE make sure only the latest route definition is used
         parameters_of_decorated_func = list(inspect.signature(func).parameters.keys())
 

+ 2 - 2
nicegui/run.py

@@ -2,7 +2,7 @@ import logging
 import multiprocessing
 import os
 import sys
-from typing import List, Optional, Tuple
+from typing import Any, List, Optional, Tuple
 
 import __main__
 import uvicorn
@@ -46,7 +46,7 @@ def run(*,
         uvicorn_reload_excludes: str = '.*, .py[cod], .sw.*, ~*',
         exclude: str = '',
         tailwind: bool = True,
-        **kwargs,
+        **kwargs: Any,
         ) -> None:
     '''ui.run
 

+ 4 - 1
nicegui/slot.py

@@ -1,4 +1,4 @@
-from typing import TYPE_CHECKING, List, Optional
+from typing import TYPE_CHECKING, Iterator, List, Optional
 
 from typing_extensions import Self
 
@@ -23,3 +23,6 @@ class Slot:
     def __exit__(self, *_) -> None:
         globals.get_slot_stack().pop()
         globals.prune_slot_stack()
+
+    def __iter__(self) -> Iterator['Element']:
+        return iter(self.children)

+ 2 - 1
nicegui/templates/index.html

@@ -155,8 +155,9 @@
           const query = { client_id: "{{ client_id }}" };
           const url = window.location.protocol === 'https:' ? 'wss://' : 'ws://' + window.location.host;
           const extraHeaders = {{ socket_io_js_extra_headers | safe }};
+          const transports = ['websocket', 'polling'];
           window.path_prefix = "{{ prefix | safe }}";
-          window.socket = io(url, { path: "{{ prefix | safe }}/_nicegui_ws/socket.io", query, extraHeaders });
+          window.socket = io(url, { path: "{{ prefix | safe }}/_nicegui_ws/socket.io", query, extraHeaders, transports });
           window.socket.on("connect", () => {
             window.socket.emit("handshake", (ok) => {
               if (!ok) window.location.reload();