Procházet zdrojové kódy

Merge commit 'ad60420b95943027882edb1f428b2d16744b563e' into changeable_native_window

Rodja Trappe před 2 roky
rodič
revize
4e67ddbcae
55 změnil soubory, kde provedl 445 přidání a 141 odebrání
  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 config virtualenvs.create false
           poetry install
           poetry install
           # install packages to run the examples
           # 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
           # try fix issue with importlib_resources
           pip install importlib-resources
           pip install importlib-resources
       - name: test startup
       - 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('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('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('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('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('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')
             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)
 bindings: DefaultDict[Tuple[int, str], List] = defaultdict(list)
 bindable_properties: Dict[Tuple[int, str], Any] = {}
 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:
 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)
             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))
     bindings[(id(self_obj), self_name)].append((self_obj, other_obj, other_name, forward))
     if (id(self_obj), self_name) not in bindable_properties:
     if (id(self_obj), self_name) not in bindable_properties:
         active_links.append((self_obj, self_name, other_obj, other_name, forward))
         active_links.append((self_obj, self_name, other_obj, other_name, forward))
     propagate(self_obj, self_name)
     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))
     bindings[(id(other_obj), other_name)].append((other_obj, self_obj, self_name, backward))
     if (id(other_obj), other_name) not in bindable_properties:
     if (id(other_obj), other_name) not in bindable_properties:
         active_links.append((other_obj, other_name, self_obj, self_name, backward))
         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, *,
 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_from(self_obj, self_name, other_obj, other_name, backward=backward)
     bind_to(self_obj, self_name, other_obj, other_name, forward=forward)
     bind_to(self_obj, self_name, other_obj, other_name, forward=forward)
 
 
 
 
 class BindableProperty:
 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
         self.on_change = on_change
 
 
     def __set_name__(self, _, name: str) -> None:
     def __set_name__(self, _, name: str) -> None:

+ 5 - 5
nicegui/client.py

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

+ 12 - 8
nicegui/element.py

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

+ 2 - 2
nicegui/elements/button.py

@@ -1,5 +1,5 @@
 import asyncio
 import asyncio
-from typing import Callable, Optional
+from typing import Any, Callable, Optional
 
 
 from ..colors import set_background_color
 from ..colors import set_background_color
 from ..events import ClickEventArguments, handle_event
 from ..events import ClickEventArguments, handle_event
@@ -11,7 +11,7 @@ class Button(TextElement, DisableableElement):
 
 
     def __init__(self,
     def __init__(self,
                  text: str = '', *,
                  text: str = '', *,
-                 on_click: Optional[Callable] = None,
+                 on_click: Optional[Callable[..., Any]] = None,
                  color: Optional[str] = 'primary',
                  color: Optional[str] = 'primary',
                  ) -> None:
                  ) -> None:
         """Button
         """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.disableable_element import DisableableElement
 from .mixins.text_element import TextElement
 from .mixins.text_element import TextElement
@@ -7,7 +7,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] = None) -> None:
+    def __init__(self, text: str = '', *, value: bool = False, on_change: Optional[Callable[..., Any]] = None) -> None:
         """Checkbox
         """Checkbox
 
 
         :param text: the label to display next to the 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):
 class ChoiceElement(ValueElement):
 
 
     def __init__(self, *,
     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.options = options
         self._values: List[str] = []
         self._values: List[str] = []
         self._labels: 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
 from nicegui import ui
 
 
@@ -10,8 +10,12 @@ from .mixins.value_element import ValueElement
 class ColorInput(ValueElement, DisableableElement):
 class ColorInput(ValueElement, DisableableElement):
     LOOPBACK = False
     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
         """Color Input
 
 
         :param label: displayed label for the 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
 from nicegui.events import ColorPickEventArguments, handle_event
 
 
@@ -8,7 +8,7 @@ from .menu import Menu
 
 
 class ColorPicker(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
         """Color Picker
 
 
         :param on_pick: callback to execute when a color is picked
         :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.disableable_element import DisableableElement
 from .mixins.value_element import ValueElement
 from .mixins.value_element import ValueElement
@@ -11,7 +11,7 @@ class Date(ValueElement, DisableableElement):
                  value: Optional[str] = None,
                  value: Optional[str] = None,
                  *,
                  *,
                  mask: str = 'YYYY-MM-DD',
                  mask: str = 'YYYY-MM-DD',
-                 on_change: Optional[Callable] = None) -> None:
+                 on_change: Optional[Callable[..., Any]] = 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.

+ 2 - 2
nicegui/elements/input.py

@@ -14,9 +14,9 @@ class Input(ValueElement, DisableableElement):
                  value: str = '',
                  value: str = '',
                  password: bool = False,
                  password: bool = False,
                  password_toggle_button: bool = False,
                  password_toggle_button: bool = False,
-                 on_change: Optional[Callable] = None,
+                 on_change: Optional[Callable[..., Any]] = None,
                  autocomplete: Optional[List[str]] = None,
                  autocomplete: Optional[List[str]] = None,
-                 validation: Dict[str, Callable] = {}) -> None:
+                 validation: Dict[str, Callable[..., bool]] = {}) -> None:
         """Text Input
         """Text Input
 
 
         This element is based on Quasar's `QInput <https://quasar.dev/vue-components/input>`_ component.
         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 __future__ import annotations
 
 
-from typing import Callable, Dict, List, Optional
+from typing import Any, Callable, Dict, List, Optional
 
 
 from ..dependencies import register_component
 from ..dependencies import register_component
 from ..events import MouseEventArguments, handle_event
 from ..events import MouseEventArguments, handle_event
@@ -13,9 +13,13 @@ register_component('interactive_image', __file__, 'interactive_image.js')
 class InteractiveImage(SourceElement, ContentElement):
 class InteractiveImage(SourceElement, ContentElement):
     CONTENT_PROP = 'content'
     CONTENT_PROP = 'content'
 
 
-    def __init__(self, source: str = '', *,
+    def __init__(self,
+                 source: str = '', *,
                  content: 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
         """Interactive Image
 
 
         Create an image with an SVG overlay that handles mouse events and yields image coordinates.
         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):
 class Joystick(Element):
 
 
     def __init__(self, *,
     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,
                  throttle: float = 0.05,
                  ** options: Any) -> None:
                  ** options: Any) -> None:
         """Joystick
         """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
 from typing_extensions import Literal
 
 
@@ -14,7 +14,7 @@ class Keyboard(Element):
     active = BindableProperty()
     active = BindableProperty()
 
 
     def __init__(self,
     def __init__(self,
-                 on_key: Callable, *,
+                 on_key: Callable[..., Any], *,
                  active: bool = True,
                  active: bool = True,
                  repeating: bool = True,
                  repeating: bool = True,
                  ignore: List[Literal['input', 'select', 'button', 'textarea']] = ['input', 'select', 'button', 'textarea'],
                  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
 from .pyplot import Pyplot
 
 
 
 
 class LinePlot(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
         """Line Plot
 
 
         Create a line plot using pyplot.
         Create a line plot using pyplot.
@@ -26,7 +32,7 @@ class LinePlot(Pyplot):
         self.update_every = update_every
         self.update_every = update_every
         self.push_counter = 0
         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.fig.gca().legend(titles, **kwargs)
         self._convert_to_html()
         self._convert_to_html()
         return self
         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 .. import globals
 from ..dependencies import register_component
 from ..dependencies import register_component
@@ -10,7 +10,7 @@ register_component('link', __file__, 'link.js')
 
 
 class Link(TextElement):
 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
         """Link
 
 
         Create a hyperlink.
         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 .. import globals
 from ..events import ClickEventArguments, handle_event
 from ..events import ClickEventArguments, handle_event
@@ -28,7 +28,11 @@ class Menu(ValueElement):
 
 
 class MenuItem(TextElement):
 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
         """Menu Item
 
 
         A menu item to be added to a menu.
         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_PROP = 'innerHTML'
     content = BindableProperty(on_change=lambda sender, content: sender.on_content_change(content))
     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)
         super().__init__(**kwargs)
         self.content = content
         self.content = content
         self.on_content_change(content)
         self.on_content_change(content)
@@ -18,7 +18,8 @@ class ContentElement(Element):
     def bind_content_to(self,
     def bind_content_to(self,
                         target_object: Any,
                         target_object: Any,
                         target_name: str = 'content',
                         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.
         """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.
         The binding works one way only, from this element to the target.
@@ -33,7 +34,8 @@ class ContentElement(Element):
     def bind_content_from(self,
     def bind_content_from(self,
                           target_object: Any,
                           target_object: Any,
                           target_name: str = 'content',
                           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.
         """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.
         The binding works one way only, from the target to this element.
@@ -48,8 +50,9 @@ class ContentElement(Element):
     def bind_content(self,
     def bind_content(self,
                      target_object: Any,
                      target_object: Any,
                      target_name: str = 'content', *,
                      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.
         """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.
         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):
 class DisableableElement(Element):
     enabled = BindableProperty(on_change=lambda sender, value: sender.on_enabled_change(value))
     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)
         super().__init__(**kwargs)
         self.enabled = True
         self.enabled = True
 
 
@@ -24,7 +24,8 @@ class DisableableElement(Element):
     def bind_enabled_to(self,
     def bind_enabled_to(self,
                         target_object: Any,
                         target_object: Any,
                         target_name: str = 'enabled',
                         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.
         """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.
         The binding works one way only, from this element to the target.
@@ -39,7 +40,8 @@ class DisableableElement(Element):
     def bind_enabled_from(self,
     def bind_enabled_from(self,
                           target_object: Any,
                           target_object: Any,
                           target_name: str = 'enabled',
                           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.
         """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.
         The binding works one way only, from the target to this element.
@@ -54,8 +56,9 @@ class DisableableElement(Element):
     def bind_enabled(self,
     def bind_enabled(self,
                      target_object: Any,
                      target_object: Any,
                      target_name: str = 'enabled', *,
                      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.
         """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.
         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_PROP = 'filter'
     filter = BindableProperty(on_change=lambda sender, filter: sender.on_filter_change(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)
         super().__init__(**kwargs)
         self.filter = filter
         self.filter = filter
         self._props[self.FILTER_PROP] = filter
         self._props[self.FILTER_PROP] = filter
@@ -18,7 +18,8 @@ class FilterElement(Element):
     def bind_filter_to(self,
     def bind_filter_to(self,
                        target_object: Any,
                        target_object: Any,
                        target_name: str = 'filter',
                        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.
         """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.
         The binding works one way only, from this element to the target.
@@ -33,7 +34,8 @@ class FilterElement(Element):
     def bind_filter_from(self,
     def bind_filter_from(self,
                          target_object: Any,
                          target_object: Any,
                          target_name: str = 'filter',
                          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.
         """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.
         The binding works one way only, from the target to this element.
@@ -48,8 +50,9 @@ class FilterElement(Element):
     def bind_filter(self,
     def bind_filter(self,
                     target_object: Any,
                     target_object: Any,
                     target_name: str = 'filter', *,
                     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.
         """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.
         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):
 class SourceElement(Element):
     source = BindableProperty(on_change=lambda sender, source: sender.on_source_change(source))
     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)
         super().__init__(**kwargs)
         self.source = source
         self.source = source
         self._props['src'] = source
         self._props['src'] = source
@@ -17,7 +17,8 @@ class SourceElement(Element):
     def bind_source_to(self,
     def bind_source_to(self,
                        target_object: Any,
                        target_object: Any,
                        target_name: str = 'source',
                        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.
         """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.
         The binding works one way only, from this element to the target.
@@ -32,7 +33,8 @@ class SourceElement(Element):
     def bind_source_from(self,
     def bind_source_from(self,
                          target_object: Any,
                          target_object: Any,
                          target_name: str = 'source',
                          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.
         """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.
         The binding works one way only, from the target to this element.
@@ -47,8 +49,9 @@ class SourceElement(Element):
     def bind_source(self,
     def bind_source(self,
                     target_object: Any,
                     target_object: Any,
                     target_name: str = 'source', *,
                     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.
         """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.
         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):
 class TextElement(Element):
     text = BindableProperty(on_change=lambda sender, text: sender.on_text_change(text))
     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)
         super().__init__(**kwargs)
         self.text = text
         self.text = text
         self._text_to_model_text(text)
         self._text_to_model_text(text)
@@ -17,7 +17,8 @@ class TextElement(Element):
     def bind_text_to(self,
     def bind_text_to(self,
                      target_object: Any,
                      target_object: Any,
                      target_name: str = 'text',
                      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.
         """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.
         The binding works one way only, from this element to the target.
@@ -32,7 +33,8 @@ class TextElement(Element):
     def bind_text_from(self,
     def bind_text_from(self,
                        target_object: Any,
                        target_object: Any,
                        target_name: str = 'text',
                        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.
         """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.
         The binding works one way only, from the target to this element.
@@ -47,8 +49,9 @@ class TextElement(Element):
     def bind_text(self,
     def bind_text(self,
                   target_object: Any,
                   target_object: Any,
                   target_name: str = 'text', *,
                   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.
         """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.
         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
     LOOPBACK = True
     value = BindableProperty(on_change=lambda sender, value: sender.on_value_change(value))
     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)
         super().__init__(**kwargs)
         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)
@@ -30,7 +35,8 @@ class ValueElement(Element):
     def bind_value_to(self,
     def bind_value_to(self,
                       target_object: Any,
                       target_object: Any,
                       target_name: str = 'value',
                       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.
         """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.
         The binding works one way only, from this element to the target.
@@ -45,7 +51,8 @@ class ValueElement(Element):
     def bind_value_from(self,
     def bind_value_from(self,
                         target_object: Any,
                         target_object: Any,
                         target_name: str = 'value',
                         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.
         """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.
         The binding works one way only, from the target to this element.
@@ -60,8 +67,9 @@ class ValueElement(Element):
     def bind_value(self,
     def bind_value(self,
                    target_object: Any,
                    target_object: Any,
                    target_name: str = 'value', *,
                    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.
         """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.
         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:
 class Visibility:
     visible = BindableProperty(on_change=lambda sender, visible: sender.on_visibility_change(visible))
     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)
         super().__init__(**kwargs)
         self.visible = True
         self.visible = True
 
 
     def bind_visibility_to(self,
     def bind_visibility_to(self,
                            target_object: Any,
                            target_object: Any,
                            target_name: str = 'visible',
                            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.
         """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.
         The binding works one way only, from this element to the target.
@@ -33,7 +34,7 @@ class Visibility:
     def bind_visibility_from(self,
     def bind_visibility_from(self,
                              target_object: Any,
                              target_object: Any,
                              target_name: str = 'visible',
                              target_name: str = 'visible',
-                             backward: Callable = lambda x: x, *,
+                             backward: Callable[..., Any] = lambda x: x, *,
                              value: Any = None) -> Self:
                              value: Any = None) -> Self:
         """Bind the visibility of this element from the target object's target_name property.
         """Bind the visibility of this element from the target object's target_name property.
 
 
@@ -52,9 +53,10 @@ class Visibility:
     def bind_visibility(self,
     def bind_visibility(self,
                         target_object: Any,
                         target_object: Any,
                         target_name: str = 'visible', *,
                         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.
         """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.
         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,
                  prefix: Optional[str] = None,
                  suffix: Optional[str] = None,
                  suffix: Optional[str] = None,
                  format: 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
         """Number Input
 
 
         This element is based on Quasar's `QInput <https://quasar.dev/vue-components/input>`_ component.
         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 asyncio
 import io
 import io
+from typing import Any
 
 
 import matplotlib.pyplot as plt
 import matplotlib.pyplot as plt
 
 
@@ -9,7 +10,7 @@ from ..element import Element
 
 
 class Pyplot(Element):
 class Pyplot(Element):
 
 
-    def __init__(self, *, close: bool = True, **kwargs) -> None:
+    def __init__(self, *, close: bool = True, **kwargs: Any) -> None:
         """Pyplot Context
         """Pyplot Context
 
 
         Create a context to configure a `Matplotlib <https://matplotlib.org/>`_ plot.
         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):
 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
         """Radio Selection
 
 
         The options can be specified as a list of values, or as a dictionary mapping values to labels.
         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,
                  width: int = 400,
                  height: int = 300,
                  height: int = 300,
                  grid: bool = True,
                  grid: bool = True,
-                 on_click: Optional[Callable] = None) -> None:
+                 on_click: Optional[Callable[..., Any]] = None,
+                 ) -> None:
         """3D Scene
         """3D Scene
 
 
         Display a 3d scene using `three.js <https://threejs.org/>`_.
         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], *,
     def __init__(self, options: Union[List, Dict], *,
                  label: Optional[str] = None,
                  label: Optional[str] = None,
                  value: Any = 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
         """Dropdown Selection
 
 
         The options can be specified as a list of values, or as a dictionary mapping values to labels.
         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.disableable_element import DisableableElement
 from .mixins.value_element import ValueElement
 from .mixins.value_element import ValueElement
@@ -11,7 +11,8 @@ class Slider(ValueElement, DisableableElement):
                  max: float,
                  max: float,
                  step: float = 1.0,
                  step: float = 1.0,
                  value: Optional[float] = None,
                  value: Optional[float] = None,
-                 on_change: Optional[Callable] = None) -> None:
+                 on_change: Optional[Callable[..., Any]] = None,
+                 ) -> None:
         """Slider
         """Slider
 
 
         :param min: lower bound of the 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.disableable_element import DisableableElement
 from .mixins.value_element import ValueElement
 from .mixins.value_element import ValueElement
@@ -11,7 +11,8 @@ 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] = None) -> None:
+                 on_change: Optional[Callable[..., Any]] = None,
+                 ) -> None:
         """Splitter
         """Splitter
 
 
         The `ui.splitter` element divides the screen space into resizable sections, 
         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.disableable_element import DisableableElement
 from .mixins.text_element import TextElement
 from .mixins.text_element import TextElement
@@ -7,7 +7,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] = None) -> None:
+    def __init__(self, text: str = '', *, value: bool = False, on_change: Optional[Callable[..., Any]] = None) -> None:
         """Switch
         """Switch
 
 
         :param text: the label to display next to the 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
 from typing_extensions import Literal
 
 
@@ -16,7 +16,7 @@ class Table(FilterElement):
                  title: Optional[str] = None,
                  title: Optional[str] = None,
                  selection: Optional[Literal['single', 'multiple']] = None,
                  selection: Optional[Literal['single', 'multiple']] = None,
                  pagination: Optional[int] = None,
                  pagination: Optional[int] = None,
-                 on_select: Optional[Callable] = None,
+                 on_select: Optional[Callable[..., Any]] = None,
                  ) -> None:
                  ) -> None:
         """Table
         """Table
 
 

+ 3 - 2
nicegui/elements/tabs.py

@@ -9,7 +9,8 @@ class Tabs(ValueElement):
 
 
     def __init__(self, *,
     def __init__(self, *,
                  value: Any = None,
                  value: Any = None,
-                 on_change: Optional[Callable] = None) -> None:
+                 on_change: Optional[Callable[..., Any]] = None,
+                 ) -> None:
         """Tabs
         """Tabs
 
 
         This element represents `Quasar's QTabs <https://quasar.dev/vue-components/tabs#qtabs-api>`_ component.
         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,
     def __init__(self,
                  tabs: Tabs, *,
                  tabs: Tabs, *,
                  value: Any = None,
                  value: Any = None,
-                 on_change: Optional[Callable] = None,
+                 on_change: Optional[Callable[..., Any]] = None,
                  animated: bool = True,
                  animated: bool = True,
                  ) -> None:
                  ) -> None:
         """Tab Panels
         """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
 from .input import Input
 
 
@@ -9,8 +9,9 @@ class Textarea(Input):
                  label: Optional[str] = None, *,
                  label: Optional[str] = None, *,
                  placeholder: Optional[str] = None,
                  placeholder: Optional[str] = None,
                  value: str = '',
                  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
         """Textarea
 
 
         This element is based on Quasar's `QInput <https://quasar.dev/vue-components/input>`_ component.
         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.disableable_element import DisableableElement
 from .mixins.value_element import ValueElement
 from .mixins.value_element import ValueElement
@@ -7,10 +7,10 @@ from .mixins.value_element import ValueElement
 class Time(ValueElement, DisableableElement):
 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] = None) -> None:
+                 on_change: Optional[Callable[..., Any]] = None,
+                 ) -> None:
         """Time Input
         """Time Input
 
 
         This element is based on Quasar's `QTime <https://quasar.dev/vue-components/date>`_ component.
         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):
 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
         """Toggle
 
 
         The options can be specified as a list of values, or as a dictionary mapping values to labels.
         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',
                  node_key: str = 'id',
                  label_key: str = 'label',
                  label_key: str = 'label',
                  children_key: str = 'children',
                  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
         """Tree
 
 
         Display hierarchical data using Quasar's `QTree <https://quasar.dev/vue-components/tree>`_ component.
         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
 from fastapi import Request, Response
 
 
@@ -17,8 +17,8 @@ class Upload(DisableableElement):
                  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] = None,
-                 on_rejected: Optional[Callable] = None,
+                 on_upload: Optional[Callable[..., Any]] = None,
+                 on_rejected: Optional[Callable[..., Any]] = None,
                  label: str = '',
                  label: str = '',
                  auto_upload: bool = False,
                  auto_upload: bool = False,
                  ) -> None:
                  ) -> None:

+ 1 - 1
nicegui/events.py

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

+ 1 - 1
nicegui/functions/notify.py

@@ -10,7 +10,7 @@ def notify(message: Any, *,
            closeBtn: Union[bool, str] = False,
            closeBtn: Union[bool, str] = False,
            type: Optional[Literal['positive', 'negative', 'warning', 'info', 'ongoing']] = None,
            type: Optional[Literal['positive', 'negative', 'warning', 'info', 'ongoing']] = None,
            color: Optional[str] = None,
            color: Optional[str] = None,
-           **kwargs,
+           **kwargs: Any,
            ) -> None:
            ) -> None:
     """Notification
     """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
 from .. import globals
 
 
 
 
-def open(target: Union[Callable, str]) -> None:
+def open(target: Union[Callable[..., Any], str]) -> None:
     """Open
     """Open
 
 
     Can be used to programmatically trigger redirects for a specific client.
     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:
 class refreshable:
 
 
-    def __init__(self, func: Callable) -> None:
+    def __init__(self, func: Callable[..., Any]) -> None:
         """Refreshable UI functions
         """Refreshable UI functions
 
 
         The `@ui.refreshable` decorator allows you to create functions that have a `refresh` method.
         The `@ui.refreshable` decorator allows you to create functions that have a `refresh` method.
@@ -26,7 +26,7 @@ class refreshable:
         self.instance = instance
         self.instance = instance
         return self
         return self
 
 
-    def __call__(self, *args, **kwargs) -> None:
+    def __call__(self, *args: Any, **kwargs: Any) -> None:
         self.prune()
         self.prune()
         with Element('refreshable') as container:
         with Element('refreshable') as container:
             self.containers.append((container, args, kwargs))
             self.containers.append((container, args, kwargs))
@@ -50,7 +50,7 @@ class refreshable:
             if container.client.id in globals.clients
             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):
         if is_coroutine(self.func):
             async def wait_for_result() -> None:
             async def wait_for_result() -> None:
                 with container:
                 with container:

+ 10 - 4
nicegui/functions/timer.py

@@ -1,6 +1,6 @@
 import asyncio
 import asyncio
 import time
 import time
-from typing import Callable
+from typing import Any, Callable
 
 
 from .. import background_tasks, globals
 from .. import background_tasks, globals
 from ..binding import BindableProperty
 from ..binding import BindableProperty
@@ -11,7 +11,12 @@ class Timer:
     active = BindableProperty()
     active = BindableProperty()
     interval = 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
         """Timer
 
 
         One major drive behind the creation of NiceGUI was the necessity to have a simple approach to update the interface in regular intervals,
         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)
             globals.handle_exception(e)
 
 
     async def _connected(self, timeout: float = 60.0) -> bool:
     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.
         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.
         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:
         if self.slot.parent.client.shared:
             return True
             return True
         else:
         else:

+ 7 - 7
nicegui/globals.py

@@ -3,7 +3,7 @@ import inspect
 import logging
 import logging
 from contextlib import contextmanager
 from contextlib import contextmanager
 from enum import Enum
 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 socketio import AsyncServer
 from uvicorn import Server
 from uvicorn import Server
@@ -48,13 +48,13 @@ slot_stacks: Dict[int, List['Slot']] = {}
 clients: Dict[str, 'Client'] = {}
 clients: Dict[str, 'Client'] = {}
 index_client: '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:
 def get_task_id() -> int:

+ 1 - 1
nicegui/helpers.py

@@ -23,7 +23,7 @@ def is_coroutine(object: Any) -> bool:
     return asyncio.iscoroutinefunction(object)
     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:
     try:
         if isinstance(func, Awaitable):
         if isinstance(func, Awaitable):
             async def func_with_client():
             async def func_with_client():

+ 3 - 3
nicegui/page.py

@@ -1,7 +1,7 @@
 import asyncio
 import asyncio
 import inspect
 import inspect
 import time
 import time
-from typing import Callable, Optional
+from typing import Any, Callable, Optional
 
 
 from fastapi import Request, Response
 from fastapi import Request, Response
 
 
@@ -21,7 +21,7 @@ class page:
                  dark: Optional[bool] = ...,
                  dark: Optional[bool] = ...,
                  language: Language = ...,
                  language: Language = ...,
                  response_timeout: float = 3.0,
                  response_timeout: float = 3.0,
-                 **kwargs,
+                 **kwargs: Any,
                  ) -> None:
                  ) -> None:
         """Page
         """Page
 
 
@@ -62,7 +62,7 @@ class page:
     def resolve_language(self) -> Optional[str]:
     def resolve_language(self) -> Optional[str]:
         return self.language if self.language is not ... else globals.language
         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
         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())
         parameters_of_decorated_func = list(inspect.signature(func).parameters.keys())
 
 

+ 2 - 2
nicegui/run.py

@@ -2,7 +2,7 @@ import logging
 import multiprocessing
 import multiprocessing
 import os
 import os
 import sys
 import sys
-from typing import List, Optional, Tuple
+from typing import Any, List, Optional, Tuple
 
 
 import __main__
 import __main__
 import uvicorn
 import uvicorn
@@ -46,7 +46,7 @@ def run(*,
         uvicorn_reload_excludes: str = '.*, .py[cod], .sw.*, ~*',
         uvicorn_reload_excludes: str = '.*, .py[cod], .sw.*, ~*',
         exclude: str = '',
         exclude: str = '',
         tailwind: bool = True,
         tailwind: bool = True,
-        **kwargs,
+        **kwargs: Any,
         ) -> None:
         ) -> None:
     '''ui.run
     '''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
 from typing_extensions import Self
 
 
@@ -23,3 +23,6 @@ class Slot:
     def __exit__(self, *_) -> None:
     def __exit__(self, *_) -> None:
         globals.get_slot_stack().pop()
         globals.get_slot_stack().pop()
         globals.prune_slot_stack()
         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 query = { client_id: "{{ client_id }}" };
           const url = window.location.protocol === 'https:' ? 'wss://' : 'ws://' + window.location.host;
           const url = window.location.protocol === 'https:' ? 'wss://' : 'ws://' + window.location.host;
           const extraHeaders = {{ socket_io_js_extra_headers | safe }};
           const extraHeaders = {{ socket_io_js_extra_headers | safe }};
+          const transports = ['websocket', 'polling'];
           window.path_prefix = "{{ prefix | safe }}";
           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.on("connect", () => {
             window.socket.emit("handshake", (ok) => {
             window.socket.emit("handshake", (ok) => {
               if (!ok) window.location.reload();
               if (!ok) window.location.reload();