ex4nicegui 0.6.7__py3-none-any.whl → 0.6.9__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ex4nicegui/__init__.py +12 -6
- ex4nicegui/gsap/__init__.py +11 -0
- ex4nicegui/helper/__init__.py +4 -0
- ex4nicegui/helper/client_instance_locker.py +31 -0
- ex4nicegui/reactive/__init__.py +4 -0
- ex4nicegui/reactive/base.py +426 -0
- ex4nicegui/reactive/deferredTask.py +3 -2
- ex4nicegui/reactive/local_file_picker.py +1 -2
- ex4nicegui/reactive/mixins/backgroundColor.py +41 -0
- ex4nicegui/reactive/mixins/disableable.py +44 -0
- ex4nicegui/reactive/mixins/textColor.py +66 -0
- ex4nicegui/reactive/officials/aggrid.py +4 -4
- ex4nicegui/reactive/officials/base.py +4 -351
- ex4nicegui/reactive/officials/button.py +36 -3
- ex4nicegui/reactive/officials/checkbox.py +2 -3
- ex4nicegui/reactive/officials/chip.py +102 -0
- ex4nicegui/reactive/officials/circular_progress.py +6 -5
- ex4nicegui/reactive/officials/color_picker.py +3 -4
- ex4nicegui/reactive/officials/column.py +1 -1
- ex4nicegui/reactive/officials/date.py +2 -3
- ex4nicegui/reactive/officials/drawer.py +2 -3
- ex4nicegui/reactive/officials/echarts.py +2 -3
- ex4nicegui/reactive/officials/expansion.py +2 -3
- ex4nicegui/reactive/officials/grid.py +4 -3
- ex4nicegui/reactive/officials/html.py +1 -3
- ex4nicegui/reactive/officials/icon.py +5 -9
- ex4nicegui/reactive/officials/image.py +2 -4
- ex4nicegui/reactive/officials/input.py +4 -6
- ex4nicegui/reactive/officials/knob.py +6 -4
- ex4nicegui/reactive/officials/label.py +3 -10
- ex4nicegui/reactive/officials/linear_progress.py +5 -9
- ex4nicegui/reactive/officials/number.py +3 -6
- ex4nicegui/reactive/officials/radio.py +3 -5
- ex4nicegui/reactive/officials/row.py +1 -1
- ex4nicegui/reactive/officials/select.py +3 -5
- ex4nicegui/reactive/officials/slider.py +4 -6
- ex4nicegui/reactive/officials/switch.py +2 -4
- ex4nicegui/reactive/officials/tab.py +1 -1
- ex4nicegui/reactive/officials/tab_panel.py +1 -1
- ex4nicegui/reactive/officials/tab_panels.py +109 -3
- ex4nicegui/reactive/officials/table.py +7 -6
- ex4nicegui/reactive/officials/tabs.py +1 -1
- ex4nicegui/reactive/officials/textarea.py +3 -5
- ex4nicegui/reactive/officials/upload.py +2 -2
- ex4nicegui/reactive/q_pagination.py +2 -2
- ex4nicegui/reactive/scopedStyle.js +55 -0
- ex4nicegui/reactive/scopedStyle.py +25 -0
- ex4nicegui/reactive/services/pandas_service.py +31 -0
- ex4nicegui/reactive/{utils.py → services/reactive_service.py} +9 -67
- ex4nicegui/reactive/systems/color_system.py +157 -0
- ex4nicegui/reactive/systems/object_system.py +33 -0
- ex4nicegui/reactive/systems/reactive_system.py +21 -0
- ex4nicegui/reactive/useMouse/UseMouse.py +4 -4
- ex4nicegui/reactive/usePagination.py +1 -1
- ex4nicegui/reactive/vfor.py +1 -2
- ex4nicegui/reactive/vmodel.py +1 -1
- ex4nicegui/utils/apiEffect.py +5 -1
- ex4nicegui/utils/effect.py +3 -2
- ex4nicegui/utils/refComputed.py +147 -0
- ex4nicegui/utils/refWrapper.py +57 -0
- ex4nicegui/utils/scheduler.py +20 -4
- ex4nicegui/utils/signals.py +51 -192
- ex4nicegui/utils/types.py +16 -0
- ex4nicegui/version.py +3 -0
- {ex4nicegui-0.6.7.dist-info → ex4nicegui-0.6.9.dist-info}/METADATA +61 -4
- {ex4nicegui-0.6.7.dist-info → ex4nicegui-0.6.9.dist-info}/RECORD +68 -58
- ex4nicegui/reactive/EChartsComponent/__init__.py +0 -0
- ex4nicegui/reactive/UseDraggable/__init__.py +0 -0
- ex4nicegui/reactive/dropZone/__init__.py +0 -0
- ex4nicegui/reactive/mermaid/__init__.py +0 -0
- ex4nicegui/reactive/officials/__init__.py +0 -1
- ex4nicegui/reactive/officials/utils.py +0 -11
- ex4nicegui/reactive/useMouse/__init__.py +0 -0
- {ex4nicegui-0.6.7.dist-info → ex4nicegui-0.6.9.dist-info}/LICENSE +0 -0
- {ex4nicegui-0.6.7.dist-info → ex4nicegui-0.6.9.dist-info}/WHEEL +0 -0
ex4nicegui/__init__.py
CHANGED
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
from ex4nicegui import reactive as rxui
|
|
2
|
+
from ex4nicegui.utils.refComputed import ref_computed
|
|
3
|
+
from ex4nicegui.utils.types import (
|
|
4
|
+
_TMaybeRef as TMaybeRef,
|
|
5
|
+
Ref,
|
|
6
|
+
ReadonlyRef,
|
|
7
|
+
TGetterOrReadonlyRef,
|
|
8
|
+
)
|
|
9
|
+
from ex4nicegui.utils.scheduler import next_tick
|
|
2
10
|
from ex4nicegui.utils.signals import (
|
|
3
|
-
ref_computed,
|
|
4
11
|
effect,
|
|
5
12
|
effect_refreshable,
|
|
6
13
|
to_raw,
|
|
@@ -10,9 +17,6 @@ from ex4nicegui.utils.signals import (
|
|
|
10
17
|
ref,
|
|
11
18
|
on,
|
|
12
19
|
event_batch,
|
|
13
|
-
_TMaybeRef as TMaybeRef,
|
|
14
|
-
Ref,
|
|
15
|
-
ReadonlyRef,
|
|
16
20
|
reactive,
|
|
17
21
|
deep_ref,
|
|
18
22
|
is_setter_ref,
|
|
@@ -21,6 +25,7 @@ from ex4nicegui.utils.signals import (
|
|
|
21
25
|
)
|
|
22
26
|
from ex4nicegui.utils.asyncComputed import async_computed
|
|
23
27
|
from ex4nicegui.utils.clientScope import new_scope
|
|
28
|
+
from .version import __version__
|
|
24
29
|
|
|
25
30
|
__all__ = [
|
|
26
31
|
"async_computed",
|
|
@@ -36,6 +41,7 @@ __all__ = [
|
|
|
36
41
|
"on",
|
|
37
42
|
"event_batch",
|
|
38
43
|
"TMaybeRef",
|
|
44
|
+
"TGetterOrReadonlyRef",
|
|
39
45
|
"Ref",
|
|
40
46
|
"ReadonlyRef",
|
|
41
47
|
"reactive",
|
|
@@ -44,6 +50,6 @@ __all__ = [
|
|
|
44
50
|
"to_raw",
|
|
45
51
|
"is_setter_ref",
|
|
46
52
|
"new_scope",
|
|
53
|
+
"next_tick",
|
|
54
|
+
"__version__",
|
|
47
55
|
]
|
|
48
|
-
|
|
49
|
-
__version__ = "0.6.6"
|
ex4nicegui/gsap/__init__.py
CHANGED
|
@@ -2,6 +2,17 @@ from .gsap import set_defaults, from_, to, new, run_script
|
|
|
2
2
|
from .timeline import Timeline as timeline
|
|
3
3
|
|
|
4
4
|
|
|
5
|
+
import warnings
|
|
6
|
+
|
|
7
|
+
RED = "\033[91m"
|
|
8
|
+
RESET = "\033[0m"
|
|
9
|
+
|
|
10
|
+
warnings.warn(
|
|
11
|
+
f"{RED}The gsap module is deprecated and will be removed in the next major version.{RESET}",
|
|
12
|
+
DeprecationWarning,
|
|
13
|
+
stacklevel=2,
|
|
14
|
+
)
|
|
15
|
+
|
|
5
16
|
__all__ = [
|
|
6
17
|
"set_defaults",
|
|
7
18
|
"from_",
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
from typing import Callable, Optional, TypeVar, Generic
|
|
2
|
+
from nicegui import ui, Client
|
|
3
|
+
from weakref import WeakKeyDictionary
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
_T = TypeVar("_T")
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class ClientInstanceLocker(Generic[_T]):
|
|
10
|
+
def __init__(self, factory: Callable[[], _T]):
|
|
11
|
+
"""Creates a new instance locker that creates a new instance for each client.
|
|
12
|
+
|
|
13
|
+
Args:
|
|
14
|
+
factory (Callable[[], _T]): A factory function that creates a new instance.
|
|
15
|
+
"""
|
|
16
|
+
self._client_instances: WeakKeyDictionary[Client, _T] = WeakKeyDictionary()
|
|
17
|
+
self._factory = factory
|
|
18
|
+
|
|
19
|
+
def get_object(self, client: Optional[Client] = None):
|
|
20
|
+
if not ui.context.slot_stack:
|
|
21
|
+
return None
|
|
22
|
+
|
|
23
|
+
client = client or ui.context.client
|
|
24
|
+
if client not in self._client_instances:
|
|
25
|
+
self._client_instances[client] = self._factory()
|
|
26
|
+
|
|
27
|
+
@client.on_disconnect
|
|
28
|
+
def _():
|
|
29
|
+
self._client_instances.pop(client, None)
|
|
30
|
+
|
|
31
|
+
return self._client_instances[client]
|
ex4nicegui/reactive/__init__.py
CHANGED
|
@@ -51,7 +51,9 @@ from .officials.tab import TabBindableUi as tab
|
|
|
51
51
|
from .officials.tab_panels import TabPanelsBindableUi as tab_panels
|
|
52
52
|
from .officials.tab_panel import TabPanelBindableUi as tab_panel
|
|
53
53
|
from .officials.element import ElementBindableUi as element
|
|
54
|
+
from .officials.tab_panels import LazyTabPanelsBindableUi as lazy_tab_panels
|
|
54
55
|
from .q_pagination import PaginationBindableUi as q_pagination
|
|
56
|
+
from .officials.chip import ChipBindableUi as chip
|
|
55
57
|
|
|
56
58
|
from .local_file_picker import local_file_picker
|
|
57
59
|
from .UseDraggable.UseDraggable import use_draggable
|
|
@@ -70,6 +72,7 @@ pagination = q_pagination
|
|
|
70
72
|
__all__ = [
|
|
71
73
|
"element",
|
|
72
74
|
"tab_panels",
|
|
75
|
+
"lazy_tab_panels",
|
|
73
76
|
"tab_panel",
|
|
74
77
|
"tabs",
|
|
75
78
|
"tab",
|
|
@@ -120,4 +123,5 @@ __all__ = [
|
|
|
120
123
|
"q_pagination",
|
|
121
124
|
"pagination",
|
|
122
125
|
"mermaid",
|
|
126
|
+
"chip",
|
|
123
127
|
]
|
|
@@ -0,0 +1,426 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import (
|
|
5
|
+
Any,
|
|
6
|
+
Callable,
|
|
7
|
+
Dict,
|
|
8
|
+
List,
|
|
9
|
+
Optional,
|
|
10
|
+
TypeVar,
|
|
11
|
+
Generic,
|
|
12
|
+
Union,
|
|
13
|
+
cast,
|
|
14
|
+
overload,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
from typing_extensions import Self
|
|
18
|
+
from ex4nicegui.utils.apiEffect import ui_effect
|
|
19
|
+
from ex4nicegui.utils.signals import (
|
|
20
|
+
TGetterOrReadonlyRef,
|
|
21
|
+
to_value,
|
|
22
|
+
is_ref,
|
|
23
|
+
WatchedState,
|
|
24
|
+
on,
|
|
25
|
+
)
|
|
26
|
+
from ex4nicegui.utils.clientScope import new_scope
|
|
27
|
+
from nicegui import Tailwind, ui
|
|
28
|
+
from nicegui.elements.mixins.text_element import TextElement
|
|
29
|
+
from ex4nicegui.reactive.services.reactive_service import inject_handle_delete
|
|
30
|
+
from ex4nicegui.reactive.scopedStyle import ScopedStyle
|
|
31
|
+
from ex4nicegui.reactive.mixins.disableable import DisableableMixin
|
|
32
|
+
from functools import partial
|
|
33
|
+
|
|
34
|
+
T = TypeVar("T")
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
TWidget = TypeVar("TWidget", bound=ui.element)
|
|
38
|
+
|
|
39
|
+
_T_bind_classes_type_dict = Dict[str, TGetterOrReadonlyRef[bool]]
|
|
40
|
+
_T_bind_classes_type_ref_dict = TGetterOrReadonlyRef[Dict[str, bool]]
|
|
41
|
+
_T_bind_classes_type_single = TGetterOrReadonlyRef[str]
|
|
42
|
+
_T_bind_classes_type_array = List[_T_bind_classes_type_single]
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
_T_bind_classes_type = Union[
|
|
46
|
+
_T_bind_classes_type_dict,
|
|
47
|
+
_T_bind_classes_type_ref_dict,
|
|
48
|
+
_T_bind_classes_type_single,
|
|
49
|
+
_T_bind_classes_type_array,
|
|
50
|
+
]
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class BindableUi(Generic[TWidget]):
|
|
54
|
+
def __init__(self, element: TWidget) -> None:
|
|
55
|
+
self._element = element
|
|
56
|
+
inject_handle_delete(self.element, self._on_element_delete)
|
|
57
|
+
self.tailwind = Tailwind(cast(ui.element, self._element))
|
|
58
|
+
self._effect_scope = new_scope()
|
|
59
|
+
self.__used_scope_style = False
|
|
60
|
+
|
|
61
|
+
def _on_element_delete(self):
|
|
62
|
+
self._effect_scope.dispose()
|
|
63
|
+
|
|
64
|
+
if self.__used_scope_style:
|
|
65
|
+
scope_style = ScopedStyle.get()
|
|
66
|
+
if scope_style:
|
|
67
|
+
scope_style.remove_style(self.element)
|
|
68
|
+
|
|
69
|
+
@property
|
|
70
|
+
def _ui_effect(self):
|
|
71
|
+
return partial(ui_effect, scope=self._effect_scope)
|
|
72
|
+
|
|
73
|
+
@property
|
|
74
|
+
def _ui_signal_on(self):
|
|
75
|
+
return partial(on, scope=self._effect_scope)
|
|
76
|
+
|
|
77
|
+
def props(self, add: Optional[str] = None, *, remove: Optional[str] = None):
|
|
78
|
+
cast(ui.element, self.element).props(add, remove=remove)
|
|
79
|
+
return self
|
|
80
|
+
|
|
81
|
+
def classes(
|
|
82
|
+
self,
|
|
83
|
+
add: Optional[str] = None,
|
|
84
|
+
*,
|
|
85
|
+
remove: Optional[str] = None,
|
|
86
|
+
replace: Optional[str] = None,
|
|
87
|
+
):
|
|
88
|
+
cast(ui.element, self.element).classes(add, remove=remove, replace=replace)
|
|
89
|
+
return self
|
|
90
|
+
|
|
91
|
+
def style(
|
|
92
|
+
self,
|
|
93
|
+
add: Optional[str] = None,
|
|
94
|
+
*,
|
|
95
|
+
remove: Optional[str] = None,
|
|
96
|
+
replace: Optional[str] = None,
|
|
97
|
+
):
|
|
98
|
+
cast(ui.element, self.element).style(add, remove=remove, replace=replace)
|
|
99
|
+
return self
|
|
100
|
+
|
|
101
|
+
def __enter__(self) -> Self:
|
|
102
|
+
self.element.__enter__()
|
|
103
|
+
return self
|
|
104
|
+
|
|
105
|
+
def __exit__(self, *_):
|
|
106
|
+
self.element.default_slot.__exit__(*_)
|
|
107
|
+
|
|
108
|
+
def tooltip(self, text: str) -> Self:
|
|
109
|
+
cast(ui.element, self.element).tooltip(text)
|
|
110
|
+
return self
|
|
111
|
+
|
|
112
|
+
def add_slot(self, name: str, template: Optional[str] = None):
|
|
113
|
+
"""Add a slot to the element.
|
|
114
|
+
|
|
115
|
+
:param name: name of the slot
|
|
116
|
+
:param template: Vue template of the slot
|
|
117
|
+
:return: the slot
|
|
118
|
+
"""
|
|
119
|
+
return cast(ui.element, self.element).add_slot(name, template)
|
|
120
|
+
|
|
121
|
+
@property
|
|
122
|
+
def element(self):
|
|
123
|
+
return self._element
|
|
124
|
+
|
|
125
|
+
def delete(self) -> None:
|
|
126
|
+
"""Delete the element."""
|
|
127
|
+
self.element.delete()
|
|
128
|
+
|
|
129
|
+
def move(
|
|
130
|
+
self, target_container: Optional[ui.element] = None, target_index: int = -1
|
|
131
|
+
):
|
|
132
|
+
"""Move the element to another container.
|
|
133
|
+
|
|
134
|
+
:param target_container: container to move the element to (default: the parent container)
|
|
135
|
+
:param target_index: index within the target slot (default: append to the end)
|
|
136
|
+
"""
|
|
137
|
+
return self.element.move(target_container, target_index)
|
|
138
|
+
|
|
139
|
+
def remove(self, element: Union[ui.element, int]) -> None:
|
|
140
|
+
"""Remove a child element.
|
|
141
|
+
|
|
142
|
+
:param element: either the element instance or its ID
|
|
143
|
+
"""
|
|
144
|
+
return self.element.remove(element)
|
|
145
|
+
|
|
146
|
+
def bind_prop(self, prop: str, ref_ui: TGetterOrReadonlyRef[Any]):
|
|
147
|
+
"""data binding is manipulating an element's property
|
|
148
|
+
|
|
149
|
+
@see - https://github.com/CrystalWindSnake/ex4nicegui/blob/main/README.en.md#bind_prop
|
|
150
|
+
@中文文档 - https://gitee.com/carson_add/ex4nicegui/tree/main/#bind_prop
|
|
151
|
+
|
|
152
|
+
Args:
|
|
153
|
+
prop (str): property name
|
|
154
|
+
ref_ui (TGetterOrReadonlyRef[Any]): a reference to the value to bind to
|
|
155
|
+
|
|
156
|
+
"""
|
|
157
|
+
if prop == "visible":
|
|
158
|
+
return self.bind_visible(ref_ui)
|
|
159
|
+
|
|
160
|
+
if prop == "text" and isinstance(self.element, TextElement):
|
|
161
|
+
|
|
162
|
+
@self._ui_effect
|
|
163
|
+
def _():
|
|
164
|
+
cast(TextElement, self.element).set_text(to_value(ref_ui))
|
|
165
|
+
self.element.update()
|
|
166
|
+
|
|
167
|
+
@self._ui_effect
|
|
168
|
+
def _():
|
|
169
|
+
element = cast(ui.element, self.element)
|
|
170
|
+
element._props[prop] = to_value(ref_ui)
|
|
171
|
+
element.update()
|
|
172
|
+
|
|
173
|
+
return self
|
|
174
|
+
|
|
175
|
+
def bind_visible(self, ref_ui: TGetterOrReadonlyRef[bool]):
|
|
176
|
+
@self._ui_effect
|
|
177
|
+
def _():
|
|
178
|
+
element = cast(ui.element, self.element)
|
|
179
|
+
element.set_visibility(to_value(ref_ui))
|
|
180
|
+
|
|
181
|
+
return self
|
|
182
|
+
|
|
183
|
+
def bind_not_visible(self, ref_ui: TGetterOrReadonlyRef[bool]):
|
|
184
|
+
return self.bind_visible(lambda: not to_value(ref_ui))
|
|
185
|
+
|
|
186
|
+
def on(
|
|
187
|
+
self,
|
|
188
|
+
type: str,
|
|
189
|
+
handler: Optional[Callable[..., Any]] = None,
|
|
190
|
+
args: Optional[List[str]] = None,
|
|
191
|
+
*,
|
|
192
|
+
throttle: float = 0.0,
|
|
193
|
+
leading_events: bool = True,
|
|
194
|
+
trailing_events: bool = True,
|
|
195
|
+
):
|
|
196
|
+
ele = cast(ui.element, self.element)
|
|
197
|
+
ele.on(
|
|
198
|
+
type,
|
|
199
|
+
handler,
|
|
200
|
+
args,
|
|
201
|
+
throttle=throttle,
|
|
202
|
+
leading_events=leading_events,
|
|
203
|
+
trailing_events=trailing_events,
|
|
204
|
+
)
|
|
205
|
+
|
|
206
|
+
return self
|
|
207
|
+
|
|
208
|
+
def clear(self) -> None:
|
|
209
|
+
cast(ui.element, self.element).clear()
|
|
210
|
+
|
|
211
|
+
@overload
|
|
212
|
+
def bind_classes(self, classes: Dict[str, TGetterOrReadonlyRef[bool]]):
|
|
213
|
+
...
|
|
214
|
+
|
|
215
|
+
@overload
|
|
216
|
+
def bind_classes(self, classes: TGetterOrReadonlyRef[Dict[str, bool]]):
|
|
217
|
+
...
|
|
218
|
+
|
|
219
|
+
@overload
|
|
220
|
+
def bind_classes(self, classes: List[TGetterOrReadonlyRef[str]]):
|
|
221
|
+
...
|
|
222
|
+
|
|
223
|
+
@overload
|
|
224
|
+
def bind_classes(self, classes: TGetterOrReadonlyRef[str]):
|
|
225
|
+
...
|
|
226
|
+
|
|
227
|
+
def bind_classes(self, classes: _T_bind_classes_type):
|
|
228
|
+
"""data binding is manipulating an element's class list
|
|
229
|
+
|
|
230
|
+
@see - https://github.com/CrystalWindSnake/ex4nicegui/blob/main/README.en.md#bind-class-names
|
|
231
|
+
@中文文档 - https://gitee.com/carson_add/ex4nicegui/tree/main/#%E7%BB%91%E5%AE%9A%E7%B1%BB%E5%90%8D
|
|
232
|
+
|
|
233
|
+
Args:
|
|
234
|
+
classes (_T_bind_classes_type): dict of refs | ref to dict | str ref | list of refs
|
|
235
|
+
|
|
236
|
+
## usage
|
|
237
|
+
|
|
238
|
+
bind class names with dict,value is bool ref, for example:
|
|
239
|
+
|
|
240
|
+
```python
|
|
241
|
+
bg_color = to_ref(True)
|
|
242
|
+
has_error = to_ref(False)
|
|
243
|
+
|
|
244
|
+
rxui.label('Hello').bind_classes({'bg-blue':bg_color, 'text-red':has_error})
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
bind list of class names with ref
|
|
248
|
+
|
|
249
|
+
```python
|
|
250
|
+
color = to_ref('red')
|
|
251
|
+
bg_color = lambda: f"bg-{color.value}"
|
|
252
|
+
|
|
253
|
+
rxui.label('Hello').bind_classes([bg_color])
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
bind single class name with ref
|
|
257
|
+
|
|
258
|
+
```python
|
|
259
|
+
color = to_ref('red')
|
|
260
|
+
bg_color = lambda: f"bg-{color.value}"
|
|
261
|
+
|
|
262
|
+
rxui.label('Hello').bind_classes(bg_color)
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
"""
|
|
266
|
+
if isinstance(classes, dict):
|
|
267
|
+
for name, ref_obj in classes.items():
|
|
268
|
+
|
|
269
|
+
@self._ui_effect
|
|
270
|
+
def _(name=name, ref_obj=ref_obj):
|
|
271
|
+
if to_value(ref_obj):
|
|
272
|
+
self.classes(add=name)
|
|
273
|
+
else:
|
|
274
|
+
self.classes(remove=name)
|
|
275
|
+
|
|
276
|
+
elif is_ref(classes) or isinstance(classes, Callable):
|
|
277
|
+
ref_obj = to_value(classes) # type: ignore
|
|
278
|
+
|
|
279
|
+
if isinstance(ref_obj, dict):
|
|
280
|
+
|
|
281
|
+
@self._ui_effect
|
|
282
|
+
def _():
|
|
283
|
+
for name, value in cast(Dict, to_value(classes)).items(): # type: ignore
|
|
284
|
+
if value:
|
|
285
|
+
self.classes(add=name)
|
|
286
|
+
else:
|
|
287
|
+
self.classes(remove=name)
|
|
288
|
+
else:
|
|
289
|
+
self._bind_single_class(cast(_T_bind_classes_type_single, classes))
|
|
290
|
+
|
|
291
|
+
elif isinstance(classes, list):
|
|
292
|
+
for ref_name in classes:
|
|
293
|
+
self._bind_single_class(ref_name)
|
|
294
|
+
|
|
295
|
+
return self
|
|
296
|
+
|
|
297
|
+
def _bind_single_class(self, class_name: _T_bind_classes_type_single):
|
|
298
|
+
if is_ref(class_name) or isinstance(class_name, Callable):
|
|
299
|
+
|
|
300
|
+
@on(class_name)
|
|
301
|
+
def _(state: WatchedState):
|
|
302
|
+
self.classes(add=state.current, remove=state.previous)
|
|
303
|
+
else:
|
|
304
|
+
self.classes(class_name) # type: ignore
|
|
305
|
+
|
|
306
|
+
return self
|
|
307
|
+
|
|
308
|
+
def bind_style(self, style: Dict[str, TGetterOrReadonlyRef[Any]]):
|
|
309
|
+
"""data binding is manipulating an element's style
|
|
310
|
+
|
|
311
|
+
@see - https://github.com/CrystalWindSnake/ex4nicegui/blob/main/README.en.md#bind-style
|
|
312
|
+
@中文文档 - https://gitee.com/carson_add/ex4nicegui/tree/main/#bind-style
|
|
313
|
+
|
|
314
|
+
Args:
|
|
315
|
+
style (Dict[str, Union[ReadonlyRef[str], Ref[str]]]): dict of style name and ref value
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
## usage
|
|
319
|
+
```python
|
|
320
|
+
bg_color = to_ref("blue")
|
|
321
|
+
text_color = to_ref("red")
|
|
322
|
+
|
|
323
|
+
rxui.label("test").bind_style(
|
|
324
|
+
{
|
|
325
|
+
"background-color": bg_color,
|
|
326
|
+
"color": text_color,
|
|
327
|
+
}
|
|
328
|
+
)
|
|
329
|
+
```
|
|
330
|
+
"""
|
|
331
|
+
if isinstance(style, dict):
|
|
332
|
+
for name, ref_obj in style.items():
|
|
333
|
+
if is_ref(ref_obj) or isinstance(ref_obj, Callable):
|
|
334
|
+
|
|
335
|
+
@self._ui_effect
|
|
336
|
+
def _(name=name, ref_obj=ref_obj):
|
|
337
|
+
self.element._style[name] = str(to_value(ref_obj))
|
|
338
|
+
self.element.update()
|
|
339
|
+
|
|
340
|
+
return self
|
|
341
|
+
|
|
342
|
+
def scoped_style(self, selector: str, style: Union[str, Path]):
|
|
343
|
+
"""add scoped style to the element
|
|
344
|
+
|
|
345
|
+
@see - https://github.com/CrystalWindSnake/ex4nicegui/blob/main/README.en.md#scoped_style
|
|
346
|
+
@中文文档 - https://gitee.com/carson_add/ex4nicegui/tree/main/#scoped_style
|
|
347
|
+
|
|
348
|
+
Args:
|
|
349
|
+
selector (str): css selector
|
|
350
|
+
style (Union[str, Path]): path to css file or inline style string
|
|
351
|
+
|
|
352
|
+
## usage
|
|
353
|
+
```python
|
|
354
|
+
# all children of the element will have red outline, excluding itself
|
|
355
|
+
with rxui.row().scoped_style("*", "outline: 1px solid red;") as row:
|
|
356
|
+
ui.label("Hello")
|
|
357
|
+
ui.label("World")
|
|
358
|
+
|
|
359
|
+
# all children of the element will have red outline, including the element itself
|
|
360
|
+
with rxui.row().scoped_style(":self *", "outline: 1px solid red;") as row:
|
|
361
|
+
ui.label("Hello")
|
|
362
|
+
ui.label("World")
|
|
363
|
+
|
|
364
|
+
# all children of the element will have red outline when element is hovered
|
|
365
|
+
with rxui.row().scoped_style(":hover *", "outline: 1px solid red;") as row:
|
|
366
|
+
ui.label("Hello")
|
|
367
|
+
ui.label("World")
|
|
368
|
+
|
|
369
|
+
# all children of the element and itself will have red outline when element is hovered
|
|
370
|
+
with rxui.row().scoped_style(":self:hover *", "outline: 1px solid red;") as row:
|
|
371
|
+
ui.label("Hello")
|
|
372
|
+
ui.label("World")
|
|
373
|
+
```
|
|
374
|
+
"""
|
|
375
|
+
|
|
376
|
+
is_css_file = isinstance(style, Path)
|
|
377
|
+
|
|
378
|
+
if is_css_file:
|
|
379
|
+
style = style.read_text(encoding="utf-8")
|
|
380
|
+
|
|
381
|
+
id = f"c{self.element.id}"
|
|
382
|
+
selector_with_self = _utils._parent_id_with_selector(id, selector, is_css_file)
|
|
383
|
+
css = ""
|
|
384
|
+
if is_css_file:
|
|
385
|
+
css = f"{selector_with_self} {style}"
|
|
386
|
+
else:
|
|
387
|
+
css = f"{selector_with_self}{{{style}}}"
|
|
388
|
+
|
|
389
|
+
scope_style = ScopedStyle.get()
|
|
390
|
+
assert scope_style, "can not find scope style"
|
|
391
|
+
scope_style.create_style(self.element, css)
|
|
392
|
+
self.__used_scope_style = True
|
|
393
|
+
return self
|
|
394
|
+
|
|
395
|
+
def update(self):
|
|
396
|
+
"""Update the element on the client side."""
|
|
397
|
+
self.element.update()
|
|
398
|
+
|
|
399
|
+
|
|
400
|
+
class _utils:
|
|
401
|
+
@staticmethod
|
|
402
|
+
def _parent_id_with_selector(
|
|
403
|
+
parent_id: str, selector: str, is_css_file=False
|
|
404
|
+
) -> str:
|
|
405
|
+
selector_with_self = f"#{parent_id}"
|
|
406
|
+
|
|
407
|
+
selector = selector.strip()
|
|
408
|
+
if (not selector) and (not is_css_file):
|
|
409
|
+
selector = "* "
|
|
410
|
+
|
|
411
|
+
if selector.startswith(":self"):
|
|
412
|
+
selector = selector[5:].lstrip()
|
|
413
|
+
parent_selector = f"#{parent_id}"
|
|
414
|
+
if selector.startswith(":"):
|
|
415
|
+
parent_selector = f"{parent_selector}{selector.split()[0]}"
|
|
416
|
+
|
|
417
|
+
selector_with_self = f"{parent_selector},{selector_with_self}"
|
|
418
|
+
|
|
419
|
+
if not selector.startswith(":"):
|
|
420
|
+
selector_with_self = selector_with_self + " "
|
|
421
|
+
|
|
422
|
+
selector_with_self = selector_with_self + selector
|
|
423
|
+
return selector_with_self
|
|
424
|
+
|
|
425
|
+
|
|
426
|
+
DisableableBindableUi = DisableableMixin
|
|
@@ -14,10 +14,11 @@ class DeferredTask:
|
|
|
14
14
|
for task in self._tasks:
|
|
15
15
|
task()
|
|
16
16
|
|
|
17
|
-
self._tasks.clear()
|
|
18
|
-
|
|
19
17
|
# Avoid events becoming ineffective due to page refresh when sharing the client.
|
|
20
18
|
if not client.shared:
|
|
19
|
+
# In a shared page, execution is required with every refresh.
|
|
20
|
+
# note:https://github.com/CrystalWindSnake/ex4nicegui/issues/227
|
|
21
|
+
self._tasks.clear()
|
|
21
22
|
client.connect_handlers.remove(on_client_connect) # type: ignore
|
|
22
23
|
|
|
23
24
|
ui.context.client.on_connect(on_client_connect)
|
|
@@ -6,7 +6,6 @@ from pathlib import Path
|
|
|
6
6
|
from ex4nicegui.utils.signals import (
|
|
7
7
|
Ref,
|
|
8
8
|
effect_refreshable,
|
|
9
|
-
ReadonlyRef,
|
|
10
9
|
effect,
|
|
11
10
|
ref_computed as computed,
|
|
12
11
|
to_ref,
|
|
@@ -17,7 +16,7 @@ SelectMode = Literal["dir", "file"]
|
|
|
17
16
|
|
|
18
17
|
|
|
19
18
|
class LocalFilePickerResult:
|
|
20
|
-
def __init__(self, ref:
|
|
19
|
+
def __init__(self, ref: Ref[str], open_fn: Callable[..., None]) -> None:
|
|
21
20
|
self.__open_fn = open_fn
|
|
22
21
|
self._ref = ref
|
|
23
22
|
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
from typing import (
|
|
2
|
+
Any,
|
|
3
|
+
Callable,
|
|
4
|
+
Protocol,
|
|
5
|
+
)
|
|
6
|
+
|
|
7
|
+
import signe
|
|
8
|
+
from ex4nicegui.utils.signals import (
|
|
9
|
+
TGetterOrReadonlyRef,
|
|
10
|
+
WatchedState,
|
|
11
|
+
)
|
|
12
|
+
from nicegui import ui
|
|
13
|
+
from ex4nicegui.reactive.systems import color_system
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class BackgroundColorableMixin(Protocol):
|
|
17
|
+
_ui_signal_on: Callable[[Callable[..., Any]], signe.Effect[None]]
|
|
18
|
+
|
|
19
|
+
@property
|
|
20
|
+
def element(self) -> ui.element:
|
|
21
|
+
...
|
|
22
|
+
|
|
23
|
+
def _bind_background_color(self, ref_ui: TGetterOrReadonlyRef[str]):
|
|
24
|
+
@self._ui_signal_on(ref_ui) # type: ignore
|
|
25
|
+
def _(state: WatchedState):
|
|
26
|
+
if state.previous is not None:
|
|
27
|
+
color_system.remove_background_color(self.element, state.previous)
|
|
28
|
+
|
|
29
|
+
color_system.add_background_color(self.element, state.current)
|
|
30
|
+
|
|
31
|
+
self.element.update()
|
|
32
|
+
|
|
33
|
+
def bind_color(self, ref_ui: TGetterOrReadonlyRef[str]):
|
|
34
|
+
"""bind color to the element
|
|
35
|
+
|
|
36
|
+
Args:
|
|
37
|
+
ref_ui (TGetterOrReadonlyRef[str]): a reference to the color value
|
|
38
|
+
|
|
39
|
+
"""
|
|
40
|
+
self._bind_background_color(ref_ui)
|
|
41
|
+
return self
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import (
|
|
4
|
+
Any,
|
|
5
|
+
Callable,
|
|
6
|
+
Protocol,
|
|
7
|
+
TypeVar,
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
import signe
|
|
11
|
+
from ex4nicegui.utils.signals import (
|
|
12
|
+
TGetterOrReadonlyRef,
|
|
13
|
+
to_value,
|
|
14
|
+
)
|
|
15
|
+
from nicegui.elements.mixins.disableable_element import DisableableElement
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
_T_DisableableBinder = TypeVar("_T_DisableableBinder", bound=DisableableElement)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class DisableableMixin(Protocol):
|
|
22
|
+
_ui_effect: Callable[[Callable[..., Any]], signe.Effect[None]]
|
|
23
|
+
|
|
24
|
+
@property
|
|
25
|
+
def element(self) -> DisableableElement:
|
|
26
|
+
...
|
|
27
|
+
|
|
28
|
+
def bind_enabled(self, ref_ui: TGetterOrReadonlyRef[bool]):
|
|
29
|
+
@self._ui_effect
|
|
30
|
+
def _():
|
|
31
|
+
value = to_value(ref_ui)
|
|
32
|
+
self.element.set_enabled(value)
|
|
33
|
+
self.element._handle_enabled_change(value)
|
|
34
|
+
|
|
35
|
+
return self
|
|
36
|
+
|
|
37
|
+
def bind_disable(self, ref_ui: TGetterOrReadonlyRef[bool]):
|
|
38
|
+
@self._ui_effect
|
|
39
|
+
def _():
|
|
40
|
+
value = not to_value(ref_ui)
|
|
41
|
+
self.element.set_enabled(value)
|
|
42
|
+
self.element._handle_enabled_change(value)
|
|
43
|
+
|
|
44
|
+
return self
|