1
0
Эх сурвалжийг харах

added typedefs for events, and improved formatting per suggestions.

Steve Jackson 2 жил өмнө
parent
commit
d570f8b30f
2 өөрчлөгдсөн 143 нэмэгдсэн , 70 устгасан
  1. 110 68
      nicegui/element.py
  2. 33 2
      nicegui/typedefs.py

+ 110 - 68
nicegui/element.py

@@ -11,22 +11,23 @@ from .elements.mixins.visibility import Visibility
 from .event_listener import EventListener
 from .events import handle_event
 from .slot import Slot
-from .typedefs import ElementAsDict
+from .typedefs import ElementAsDict, AnyHTMLEvent
+from typing_extensions import Self
 
 if TYPE_CHECKING:
     from .client import Client
 
 
-PROPS_PATTERN = re.compile(r'([\w\-]+)(?:=(?:("[^"\\]*(?:\\.[^"\\]*)*")|([\w\-.%:\/]+)))?(?:$|\s)')
+PROPS_PATTERN = re.compile(
+    r'([\w\-]+)(?:=(?:("[^"\\]*(?:\\.[^"\\]*)*")|([\w\-.%:\/]+)))?(?:$|\s)'
+)
 
 
-# noinspection PySingleQuotedDocstring
 class Element(ABC, Visibility):
-    '''
-    Define the base class for all html elements represented as Python objects.
+    """Define the base class for all HTML elements represented as Python objects.
 
-    :param tag: The html tag (e.g. div, p, etc. of the element.)
-    '''
+    :param tag: The HTML tag (e.g. div, p, etc. of the element.)
+    """
 
     def __init__(self, tag: str, *, _client: Optional[Client] = None) -> None:
         super().__init__()
@@ -38,9 +39,9 @@ class Element(ABC, Visibility):
         self._style: Dict[str, str] = {}
         self._props: Dict[str, Any] = {}
         self._event_listeners: List[EventListener] = []
-        self._text: str = ''
+        self._text: str = ""
         self.slots: Dict[str, Slot] = {}
-        self.default_slot = self.add_slot('default')
+        self.default_slot = self.add_slot("default")
 
         self.client.elements[self.id] = self
         self.parent_slot: Optional[Slot] = None
@@ -53,8 +54,8 @@ class Element(ABC, Visibility):
         self.slots[name] = Slot(self, name)
         return self.slots[name]
 
-    def __enter__(self) -> 'Element':
-        '''Allow element to be used as a context manager (with statement.)'''
+    def __enter__(self) -> Self:
+        """Allow the element to be used as a context manager (with statement.)"""
         self.default_slot.__enter__()
         return self
 
@@ -62,38 +63,50 @@ class Element(ABC, Visibility):
         self.default_slot.__exit__(*_)
 
     def to_dict(self) -> ElementAsDict:
-        '''Get important attributes of an element as a dictionary.'''
+        """Get important attributes of an element as a dictionary."""
         events: Dict[str, Dict] = {}
         for listener in self._event_listeners:
-            words = listener.type.split('.')
+            words = listener.type.split(".")
             type = words.pop(0)
-            specials = [w for w in words if w in {'capture', 'once', 'passive'}]
-            modifiers = [w for w in words if w in {'stop', 'prevent', 'self', 'ctrl', 'shift', 'alt', 'meta'}]
+            specials = [w for w in words if w in {"capture", "once", "passive"}]
+            modifiers = [w for w in words if w in {"stop", "prevent", "self", "ctrl", "shift", "alt", "meta"}]
             keys = [w for w in words if w not in specials + modifiers]
             events[listener.type] = {
-                'listener_type': listener.type,
-                'type': type,
-                'specials': specials,
-                'modifiers': modifiers,
-                'keys': keys,
-                'args': list(set(events.get(listener.type, {}).get('args', []) + listener.args)),
-                'throttle': min(events.get(listener.type, {}).get('throttle', float('inf')), listener.throttle),
+                "listener_type": listener.type,
+                "type": type,
+                "specials": specials,
+                "modifiers": modifiers,
+                "keys": keys,
+                "args": list(
+                    set(events.get(listener.type, {}).get("args", []) + listener.args)
+                ),
+                "throttle": min(
+                    events.get(listener.type, {}).get("throttle", float("inf")),
+                    listener.throttle,
+                ),
             }
         return {
-            'id': self.id,
-            'tag': self.tag,
-            'class': self._classes,
-            'style': self._style,
-            'props': self._props,
-            'events': events,
-            'text': self._text,
-            'slots': {name: [child.id for child in slot.children] for name, slot in self.slots.items()},
+            "id": self.id,
+            "tag": self.tag,
+            "class": self._classes,
+            "style": self._style,
+            "props": self._props,
+            "events": events,
+            "text": self._text,
+            "slots": {
+                name: [child.id for child in slot.children]
+                for name, slot in self.slots.items()
+            },
         }
 
-    def classes(self, add: Optional[str] = None, *, remove: Optional[str] = None, replace: Optional[str] = None) \
-            -> 'Element':
-        '''
-        Apply, remove, or replace HTML classes.
+    def classes(
+        self,
+        add: Optional[str] = None,
+        *,
+        remove: Optional[str] = None,
+        replace: Optional[str] = None,
+    ) -> "Element":
+        """Apply, remove, or replace HTML classes.
 
         Generally, this is for the purpose of modifying the look of the element or its layout, based on the
         Quasar framework.
@@ -103,12 +116,14 @@ class Element(ABC, Visibility):
         :param add: a white-space delimited string of classes
         :param remove: A white-space delimited string of classes to remove from the element.
         :param replace: A white-space delimited string of classes to use instead of existing.
-        '''
+        """
         class_list = self._classes if replace is None else []
-        class_list = [c for c in class_list if c not in (remove or '').split()]
-        class_list += (add or '').split()
-        class_list += (replace or '').split()
-        new_classes = list(dict.fromkeys(class_list))  # NOTE: remove duplicates while preserving order
+        class_list = [c for c in class_list if c not in (remove or "").split()]
+        class_list += (add or "").split()
+        class_list += (replace or "").split()
+        new_classes = list(
+            dict.fromkeys(class_list)
+        )  # NOTE: remove duplicates while preserving order
         if self._classes != new_classes:
             self._classes = new_classes
             self.update()
@@ -117,19 +132,24 @@ class Element(ABC, Visibility):
     @staticmethod
     def _parse_style(text: Optional[str]) -> Dict[str, str]:
         result = {}
-        for word in (text or '').split(';'):
+        for word in (text or "").split(";"):
             word = word.strip()
             if word:
-                key, value = word.split(':', 1)
+                key, value = word.split(":", 1)
                 result[key.strip()] = value.strip()
         return result
 
-    def style(self, add: Optional[str] = None, *, remove: Optional[str] = None, replace: Optional[str] = None)\
-            -> 'Element':
-        '''
-        Apply, remove, or replace CSS style sheet definitions to modify the look of the element.
+    def style(
+        self,
+        add: Optional[str] = None,
+        *,
+        remove: Optional[str] = None,
+        replace: Optional[str] = None,
+    ) -> "Element":
+        """Apply, remove, or replace CSS style sheet definitions to modify the look of the element.
 
         .. note::
+
             Removing styles can be helpful if the predefined style sheet definitions by NiceGUI are not wanted
             in a particular styling.
 
@@ -140,7 +160,7 @@ class Element(ABC, Visibility):
         :param add: A semicolon separated list of styles to add to the element.
         :param remove: A semicolon separated list of styles to remove from the element.
         :param replace: Like add, but existing styles will be replaced by given.
-         '''
+        """
         style_dict = deepcopy(self._style) if replace is None else {}
         for key in self._parse_style(remove):
             if key in style_dict:
@@ -155,7 +175,7 @@ class Element(ABC, Visibility):
     @staticmethod
     def _parse_props(text: Optional[str]) -> Dict[str, Any]:
         dictionary = {}
-        for match in PROPS_PATTERN.finditer(text or ''):
+        for match in PROPS_PATTERN.finditer(text or ""):
             key = match.group(1)
             value = match.group(2) or match.group(3)
             if value and value.startswith('"') and value.endswith('"'):
@@ -163,8 +183,10 @@ class Element(ABC, Visibility):
             dictionary[key] = value or True
         return dictionary
 
-    def props(self, add: Optional[str] = None, *, remove: Optional[str] = None) -> 'Element':
-        '''Add or remove Quasar-specif properties to modify the look of the element.
+    def props(
+        self, add: Optional[str] = None, *, remove: Optional[str] = None
+    ) -> "Element":
+        """Add or remove Quasar-specif properties to modify the look of the element.
 
         see https://quasar.dev/vue-components/button#design
 
@@ -176,7 +198,7 @@ class Element(ABC, Visibility):
 
         :param add: A whitespace separated list of either boolean values or key=value pair to add
         :param remove: A whitespace separated list of property keys to remove.
-        '''
+        """
         needs_update = False
         for key in self._parse_props(remove):
             if key in self._props:
@@ -192,29 +214,40 @@ class Element(ABC, Visibility):
 
     def tooltip(self, text: str):
         with self:
-            tooltip = Element('q-tooltip')
+            tooltip = Element("q-tooltip")
             tooltip._text = text
         return self
 
-    def on(self, type: str, handler: Optional[Callable], args: Optional[List[str]] = None, *, throttle: float = 0.0) \
-            -> 'Element':
+    def on(
+        self,
+        type: AnyHTMLEvent,
+        handler: Optional[Callable],
+        args: Optional[List[str]] = None,
+        *,
+        throttle: float = 0.0,
+    ) -> "Element":
         if handler:
-            args = args if args is not None else ['*']
-            listener = EventListener(element_id=self.id, type=type, args=args, handler=handler, throttle=throttle)
+            args = args if args is not None else ["*"]
+            listener = EventListener(
+                element_id=self.id,
+                type=type,
+                args=args,
+                handler=handler,
+                throttle=throttle,
+            )
             self._event_listeners.append(listener)
         return self
 
     def handle_event(self, msg: Dict) -> None:
         for listener in self._event_listeners:
-            if listener.type == msg['type']:
+            if listener.type == msg["type"]:
                 handle_event(listener.handler, msg, sender=self)
 
     def collect_descendant_ids(self) -> List[int]:
-        '''
-        Return a list of ids of the element and each of its descendents.
+        """Return a list of ids of the element and each of its descendents.
 
         ... note:: The first id in the list is that of the element.
-        '''
+        """
         ids: List[int] = [self.id]
         for slot in self.slots.values():
             for child in slot.children:
@@ -226,17 +259,25 @@ class Element(ABC, Visibility):
             return
         ids = self.collect_descendant_ids()
         elements = {id: self.client.elements[id].to_dict() for id in ids}
-        background_tasks.create(globals.sio.emit('update', {'elements': elements}, room=self.client.id))
+        background_tasks.create(
+            globals.sio.emit("update", {"elements": elements}, room=self.client.id)
+        )
 
     def run_method(self, name: str, *args: Any) -> None:
         if not globals.loop:
             return
-        data = {'id': self.id, 'name': name, 'args': args}
-        background_tasks.create(globals.sio.emit('run_method', data, room=globals._socket_id or self.client.id))
+        data = {"id": self.id, "name": name, "args": args}
+        background_tasks.create(
+            globals.sio.emit(
+                "run_method", data, room=globals._socket_id or self.client.id
+            )
+        )
 
     def clear(self) -> None:
-        '''Remove all descendant (child) elements.'''
-        descendants = [self.client.elements[id] for id in self.collect_descendant_ids()[1:]]
+        """Remove all descendant (child) elements."""
+        descendants = [
+            self.client.elements[id] for id in self.collect_descendant_ids()[1:]
+        ]
         binding.remove(descendants, Element)
         for element in descendants:
             del self.client.elements[element.id]
@@ -245,13 +286,14 @@ class Element(ABC, Visibility):
         self.update()
 
     def remove(self, element: Union[Element, int]) -> None:
-        '''
-        Remove a descendant (child) element.
+        """Remove a descendant (child) element.
 
         :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 = [
+                child for slot in self.slots.values() for child in slot.children
+            ]
             element = children[element]
         binding.remove([element], Element)
         del self.client.elements[element.id]

+ 33 - 2
nicegui/typedefs.py

@@ -1,9 +1,9 @@
 """
 Define types used throughout to assist with auto-complete.
 """
-from typing import List, Dict, Any
+from typing import List, Dict, Any, Union
 
-from typing_extensions import TypedDict
+from typing_extensions import TypedDict, Literal
 
 # note: This element of typed dict is necessary due to the use of the "class" keyword.
 # note: Ideally the dict types get better refined moving forward to give more clarity to the caller.
@@ -18,3 +18,34 @@ ElementAsDict = TypedDict('ElementAsDict', {
     'slots': Dict
 })
 
+WindowsEvents = Literal[
+    'afterprint', 'beforeprint', 'beforeunload', 'error', 'hashchange', 'load', 'message', 'offline', 'online',
+    'pagehide', 'pageshow', 'popstate', 'resize', 'storage', 'unload']
+FormEvents = Literal[
+    'blur', 'change', 'contextmenu', 'focus', 'input', 'invalid', 'reset', 'search', 'select',
+    'submit']
+KeyboardEvents = Literal[
+    'keydown', 'keypress', 'keyup'
+]
+MouseEvents = Literal[
+    'click', 'dblclick', 'mousedown', 'mousemove', 'mouseout', 'mouseover', 'mouseup',
+    'mousewheel', 'wheel'
+]
+DragEvents = Literal[
+    'drag', 'dragend', 'dragenter', 'dragleave', 'dragover', 'dragstart', 'drop', 'scroll'
+]
+ClipboardEvents = Literal[
+    'copy', 'cut', 'paste'
+]
+MediaEvents = Literal[
+    'abort', 'canplay', 'canplaythrough', 'cuechange', 'durationchange', 'emptied', 'ended', 'error', 
+    'loadeddata', 'loadedmetadata', 'loadstart', 'pause', 'play', 'playing', 'progress', 'ratechange', 
+    'seeked', 'seeking', 'stalled', 'suspend', 'timeupdate', 'volumechange', 'waiting'
+]
+MiscEvents = Literal['toggle']
+
+AnyHTMLEvent = Union[
+    WindowsEvents, FormEvents, KeyboardEvents, MouseEvents, DragEvents, ClipboardEvents, MediaEvents, MiscEvents]
+
+
+