HomeDashboard/.venv/lib/python3.12/site-packages/nicegui/props.py
2026-01-03 14:54:18 +01:00

141 lines
5.6 KiB
Python

import ast
import re
import weakref
from collections.abc import Iterator
from contextlib import contextmanager
from typing import TYPE_CHECKING, Any, Generic, Optional, TypeVar
from . import helpers
from .observables import ObservableDict
if TYPE_CHECKING:
from .element import Element
PROPS_PATTERN = re.compile(r'''
# Match a key or key-value pair optionally followed by whitespace or end of string
# The value can be unquoted, or enclosed in quotes, or square or curly brackets
(?P<key>[:\w\-]+) # Capture group 1: Key
( # Optional non-capturing group for value
= # An equals sign
( # followed by one of ...
(?P<double> # a value enclosed in double quotes
" # Match double quote
[^"\\]* # Match any character except quotes or backslashes zero or more times
(\\.[^"\\]*)* # Match any escaped character followed by any character except quotes or backslashes zero or more times
" # Match the closing quote
)
| # or
(?P<single> # a value enclosed in single quotes
' # Match a single quote
[^'\\]* # Match any character except quotes or backslashes zero or more times
(\\.[^'\\]*)* # Match any escaped character followed by any character except quotes or backslashes zero or more times
' # Match the closing quote
)
| # or
(?P<square> # a value enclosed in square brackets
\[ # Match the opening [
[^\]\\]* # Match any character except a ] or backslashes zero or more times
(\\.[^\]\\]*)* # Match any escaped character followed by any character except ] or backslashes zero or more times
\] # Match the closing ]
)
| # or
(?P<curly> # a value enclosed in curly braces
\{ # Match the opening {
[^\}\\]* # Match any character except } or backslashes zero or more times
(\\.[^\}\\]*)* # Match any escaped character followed by any character except ] or backslashes zero or more times
\} # Match the closing }
)
| # or, as a final alternative ....
(?P<unquoted>[\w\-.,%:\/=?&;+#@~$]+) # a value without quotes
)
)? # End of optional non-capturing group for value
($|\s) # Match end of string or whitespace
''', re.VERBOSE)
T = TypeVar('T', bound='Element')
class Props(ObservableDict, Generic[T]):
def __init__(self, *args, element: T, **kwargs) -> None:
super().__init__(*args, on_change=self._update, **kwargs)
self._element = weakref.ref(element)
self._warnings: dict[str, str] = {}
self._suspend_count = 0
@contextmanager
def suspend_updates(self) -> Iterator[None]:
"""Suspend updates."""
self._suspend_count += 1
try:
yield
finally:
self._suspend_count -= 1
@property
def element(self) -> T:
"""The element this props object belongs to."""
element = self._element()
if element is None:
raise RuntimeError('The element this props object belongs to has been deleted.')
return element
def _update(self) -> None:
if self._suspend_count > 0:
return
element = self._element()
if element is not None:
element.update()
def add_warning(self, prop: str, message: str) -> None:
"""Add a warning message for a prop."""
self._warnings[prop] = message
def __call__(self,
add: Optional[str] = None, *,
remove: Optional[str] = None) -> T:
"""Add or remove props.
This allows modifying the look of the element or its layout using `Quasar <https://quasar.dev/>`_ props.
Since props are simply applied as HTML attributes, they can be used with any HTML element.
Boolean properties are assumed ``True`` if no value is specified.
:param add: whitespace-delimited list of either boolean values or key=value pair to add
:param remove: whitespace-delimited list of property keys to remove
"""
element = self.element
for key in self.parse(remove):
if key in self:
del self[key]
for key, value in self.parse(add).items():
if self.get(key) != value:
self[key] = value
for name, message in self._warnings.items():
if name in self:
del self[name]
helpers.warn_once(message)
return element
@staticmethod
def parse(text: Optional[str]) -> dict[str, Any]:
"""Parse a string of props into a dictionary."""
if not text:
return {}
props: dict[str, Any] = {}
for match in PROPS_PATTERN.finditer(text):
match_groups = match.groupdict()
key = match_groups['key']
for group in ['single', 'double', 'square', 'curly']:
if match_groups[group] is not None:
props[key] = ast.literal_eval(match_groups[group])
break
else:
if match_groups['unquoted'] is not None:
props[key] = match_groups['unquoted']
else:
props[key] = True
return props