ex4nicegui 0.8.0__py3-none-any.whl → 0.8.1__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/reactive/__init__.py +2 -1
- ex4nicegui/reactive/base.py +108 -50
- ex4nicegui/reactive/mixins/backgroundColor.py +4 -8
- ex4nicegui/reactive/mixins/disableable.py +4 -8
- ex4nicegui/reactive/mixins/textColor.py +8 -10
- ex4nicegui/reactive/officials/checkbox.py +0 -1
- ex4nicegui/reactive/officials/input.py +1 -1
- ex4nicegui/reactive/officials/textarea.py +2 -2
- ex4nicegui/reactive/services/reactive_service.py +2 -1
- ex4nicegui/reactive/vfor.py +4 -0
- ex4nicegui/reactive/view_model.py +147 -6
- ex4nicegui/utils/proxy/__init__.py +19 -0
- ex4nicegui/utils/proxy/base.py +9 -0
- ex4nicegui/utils/proxy/bool.py +58 -0
- ex4nicegui/utils/proxy/date.py +88 -0
- ex4nicegui/utils/proxy/descriptor.py +126 -0
- ex4nicegui/utils/proxy/dict.py +110 -0
- ex4nicegui/utils/proxy/float.py +153 -0
- ex4nicegui/utils/proxy/int.py +223 -0
- ex4nicegui/utils/proxy/list.py +147 -0
- ex4nicegui/utils/proxy/string.py +430 -0
- ex4nicegui/utils/proxy/utils.py +6 -0
- ex4nicegui/utils/signals.py +9 -1
- {ex4nicegui-0.8.0.dist-info → ex4nicegui-0.8.1.dist-info}/METADATA +508 -290
- {ex4nicegui-0.8.0.dist-info → ex4nicegui-0.8.1.dist-info}/RECORD +27 -16
- {ex4nicegui-0.8.0.dist-info → ex4nicegui-0.8.1.dist-info}/LICENSE +0 -0
- {ex4nicegui-0.8.0.dist-info → ex4nicegui-0.8.1.dist-info}/WHEEL +0 -0
ex4nicegui/reactive/__init__.py
CHANGED
|
@@ -70,7 +70,7 @@ from .mermaid.mermaid import Mermaid as mermaid
|
|
|
70
70
|
from .officials.dialog import DialogBindableUi as dialog
|
|
71
71
|
from .vfor import vfor, VforStore
|
|
72
72
|
from .vmodel import vmodel
|
|
73
|
-
from .view_model import ViewModel, var, cached_var
|
|
73
|
+
from .view_model import ViewModel, var, cached_var, list_var
|
|
74
74
|
|
|
75
75
|
pagination = q_pagination
|
|
76
76
|
|
|
@@ -97,6 +97,7 @@ __all__ = [
|
|
|
97
97
|
"ViewModel",
|
|
98
98
|
"var",
|
|
99
99
|
"cached_var",
|
|
100
|
+
"list_var",
|
|
100
101
|
"html",
|
|
101
102
|
"aggrid",
|
|
102
103
|
"button",
|
ex4nicegui/reactive/base.py
CHANGED
|
@@ -48,6 +48,8 @@ _T_bind_classes_type = Union[
|
|
|
48
48
|
_T_bind_classes_type_ref_dict,
|
|
49
49
|
_T_bind_classes_type_single,
|
|
50
50
|
_T_bind_classes_type_array,
|
|
51
|
+
Dict[str, bool],
|
|
52
|
+
List[str],
|
|
51
53
|
]
|
|
52
54
|
|
|
53
55
|
|
|
@@ -150,6 +152,56 @@ class BindableUi(Generic[TWidget]):
|
|
|
150
152
|
"""
|
|
151
153
|
return self.element.remove(element)
|
|
152
154
|
|
|
155
|
+
@overload
|
|
156
|
+
def bind_props(self, props: Dict[str, TMaybeRef[Any]]) -> Self: ...
|
|
157
|
+
|
|
158
|
+
@overload
|
|
159
|
+
def bind_props(self, props: TMaybeRef[str]) -> Self: ...
|
|
160
|
+
|
|
161
|
+
def bind_props(self, props: Union[Dict[str, TMaybeRef[Any]], TMaybeRef[str]]):
|
|
162
|
+
"""data binding is manipulating an element's props
|
|
163
|
+
|
|
164
|
+
Args:
|
|
165
|
+
props (Union[Dict[str, TMaybeRef[Any]], TMaybeRef[str]]): dict of prop name and ref value
|
|
166
|
+
|
|
167
|
+
## usage
|
|
168
|
+
|
|
169
|
+
.. code-block:: python
|
|
170
|
+
outlined = to_ref(True)
|
|
171
|
+
size = to_ref("xs")
|
|
172
|
+
|
|
173
|
+
def props_str():
|
|
174
|
+
return f'{"flat" if flat.value else ""} {"size=" + size.value}'
|
|
175
|
+
|
|
176
|
+
rxui.button("click me").bind_props(props_str)
|
|
177
|
+
rxui.button("click me").bind_props({
|
|
178
|
+
"outlined": outlined,
|
|
179
|
+
"size": size,
|
|
180
|
+
})
|
|
181
|
+
|
|
182
|
+
"""
|
|
183
|
+
if isinstance(props, dict):
|
|
184
|
+
|
|
185
|
+
def props_str():
|
|
186
|
+
props_dict = (
|
|
187
|
+
f"""{name if isinstance(raw_value,bool) else f"{name}='{raw_value}'"}"""
|
|
188
|
+
for name, value in props.items()
|
|
189
|
+
if (raw_value := to_value(value))
|
|
190
|
+
)
|
|
191
|
+
|
|
192
|
+
return " ".join(props_dict)
|
|
193
|
+
|
|
194
|
+
self._bind_props_for_str_fn(props_str)
|
|
195
|
+
else:
|
|
196
|
+
self._bind_props_for_str_fn(props)
|
|
197
|
+
|
|
198
|
+
return self
|
|
199
|
+
|
|
200
|
+
def _bind_props_for_str_fn(self, props_str: TMaybeRef[str]):
|
|
201
|
+
@self._ui_signal_on(props_str, onchanges=False, deep=False)
|
|
202
|
+
def _(state: WatchedState):
|
|
203
|
+
self.props(add=state.current, remove=state.previous)
|
|
204
|
+
|
|
153
205
|
def bind_prop(self, prop: str, value: TGetterOrReadonlyRef[Any]):
|
|
154
206
|
"""data binding is manipulating an element's property
|
|
155
207
|
|
|
@@ -179,7 +231,7 @@ class BindableUi(Generic[TWidget]):
|
|
|
179
231
|
|
|
180
232
|
return self
|
|
181
233
|
|
|
182
|
-
def bind_visible(self, value:
|
|
234
|
+
def bind_visible(self, value: TMaybeRef[bool]):
|
|
183
235
|
@self._ui_effect
|
|
184
236
|
def _():
|
|
185
237
|
element = cast(ui.element, self.element)
|
|
@@ -187,7 +239,7 @@ class BindableUi(Generic[TWidget]):
|
|
|
187
239
|
|
|
188
240
|
return self
|
|
189
241
|
|
|
190
|
-
def bind_not_visible(self, value:
|
|
242
|
+
def bind_not_visible(self, value: TMaybeRef[bool]):
|
|
191
243
|
return self.bind_visible(lambda: not to_value(value))
|
|
192
244
|
|
|
193
245
|
def on(
|
|
@@ -216,26 +268,28 @@ class BindableUi(Generic[TWidget]):
|
|
|
216
268
|
cast(ui.element, self.element).clear()
|
|
217
269
|
|
|
218
270
|
@overload
|
|
219
|
-
def bind_classes(self, classes: Dict[str, TGetterOrReadonlyRef[bool]]) -> Self:
|
|
220
|
-
|
|
271
|
+
def bind_classes(self, classes: Dict[str, TGetterOrReadonlyRef[bool]]) -> Self: ...
|
|
272
|
+
|
|
273
|
+
@overload
|
|
274
|
+
def bind_classes(self, classes: Dict[str, bool]) -> Self: ...
|
|
221
275
|
|
|
222
276
|
@overload
|
|
223
|
-
def bind_classes(self, classes: TGetterOrReadonlyRef[Dict[str, bool]]) -> Self:
|
|
224
|
-
...
|
|
277
|
+
def bind_classes(self, classes: TGetterOrReadonlyRef[Dict[str, bool]]) -> Self: ...
|
|
225
278
|
|
|
226
279
|
@overload
|
|
227
|
-
def bind_classes(self, classes: List[TGetterOrReadonlyRef[str]]) -> Self:
|
|
228
|
-
...
|
|
280
|
+
def bind_classes(self, classes: List[TGetterOrReadonlyRef[str]]) -> Self: ...
|
|
229
281
|
|
|
230
282
|
@overload
|
|
231
|
-
def bind_classes(self, classes:
|
|
232
|
-
|
|
283
|
+
def bind_classes(self, classes: List[str]) -> Self: ...
|
|
284
|
+
|
|
285
|
+
@overload
|
|
286
|
+
def bind_classes(self, classes: TGetterOrReadonlyRef[str]) -> Self: ...
|
|
233
287
|
|
|
234
288
|
def bind_classes(self, classes: _T_bind_classes_type) -> Self:
|
|
235
289
|
"""data binding is manipulating an element's class list
|
|
236
290
|
|
|
237
|
-
@see - https://github.com/CrystalWindSnake/ex4nicegui/blob/main/README.en.md#
|
|
238
|
-
@中文文档 - https://gitee.com/carson_add/ex4nicegui/tree/main
|
|
291
|
+
@see - https://github.com/CrystalWindSnake/ex4nicegui/blob/main/README.en.md#bind_classes
|
|
292
|
+
@中文文档 - https://gitee.com/carson_add/ex4nicegui/tree/main/#bind_classes
|
|
239
293
|
|
|
240
294
|
Args:
|
|
241
295
|
classes (_T_bind_classes_type): dict of refs | ref to dict | str ref | list of refs
|
|
@@ -271,52 +325,51 @@ class BindableUi(Generic[TWidget]):
|
|
|
271
325
|
|
|
272
326
|
"""
|
|
273
327
|
if isinstance(classes, dict):
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
self.classes(add=name)
|
|
280
|
-
else:
|
|
281
|
-
self.classes(remove=name)
|
|
328
|
+
self._bind_classes_for_str_fn(
|
|
329
|
+
lambda: " ".join(
|
|
330
|
+
name for name, value in classes.items() if to_value(value)
|
|
331
|
+
)
|
|
332
|
+
)
|
|
282
333
|
|
|
334
|
+
elif isinstance(classes, list):
|
|
335
|
+
self._bind_classes_for_str_fn(
|
|
336
|
+
lambda: " ".join(to_value(c) for c in classes)
|
|
337
|
+
)
|
|
283
338
|
elif is_ref(classes) or isinstance(classes, Callable):
|
|
284
339
|
ref_obj = to_value(classes) # type: ignore
|
|
285
340
|
|
|
286
341
|
if isinstance(ref_obj, dict):
|
|
287
342
|
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
self.classes(remove=name)
|
|
295
|
-
else:
|
|
296
|
-
self._bind_single_class(cast(_T_bind_classes_type_single, classes))
|
|
343
|
+
def classes_str():
|
|
344
|
+
return " ".join(
|
|
345
|
+
name
|
|
346
|
+
for name, value in to_value(classes).items() # type: ignore
|
|
347
|
+
if to_value(value)
|
|
348
|
+
)
|
|
297
349
|
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
350
|
+
self._bind_classes_for_str_fn(classes_str)
|
|
351
|
+
|
|
352
|
+
else:
|
|
353
|
+
self._bind_classes_for_str_fn(classes) # type: ignore
|
|
301
354
|
|
|
302
355
|
return self
|
|
303
356
|
|
|
304
|
-
def
|
|
305
|
-
|
|
357
|
+
def _bind_classes_for_str_fn(self, classes_str: TGetterOrReadonlyRef[str]):
|
|
358
|
+
@self._ui_signal_on(classes_str, onchanges=False, deep=False)
|
|
359
|
+
def _(state: WatchedState):
|
|
360
|
+
self.classes(add=state.current, remove=state.previous)
|
|
306
361
|
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
self.classes(add=state.current, remove=state.previous)
|
|
310
|
-
else:
|
|
311
|
-
self.classes(class_name) # type: ignore
|
|
362
|
+
@overload
|
|
363
|
+
def bind_style(self, style: TMaybeRef[str]) -> Self: ...
|
|
312
364
|
|
|
313
|
-
|
|
365
|
+
@overload
|
|
366
|
+
def bind_style(self, style: Dict[str, TMaybeRef[Any]]) -> Self: ...
|
|
314
367
|
|
|
315
|
-
def bind_style(self, style: Dict[str,
|
|
368
|
+
def bind_style(self, style: Union[TMaybeRef[str], Dict[str, TMaybeRef[Any]]]):
|
|
316
369
|
"""data binding is manipulating an element's style
|
|
317
370
|
|
|
318
|
-
@see - https://github.com/CrystalWindSnake/ex4nicegui/blob/main/README.en.md#
|
|
319
|
-
@中文文档 - https://gitee.com/carson_add/ex4nicegui/tree/main/#
|
|
371
|
+
@see - https://github.com/CrystalWindSnake/ex4nicegui/blob/main/README.en.md#bind_style
|
|
372
|
+
@中文文档 - https://gitee.com/carson_add/ex4nicegui/tree/main/#bind_style
|
|
320
373
|
|
|
321
374
|
Args:
|
|
322
375
|
style (Dict[str, Union[ReadonlyRef[str], Ref[str]]]): dict of style name and ref value
|
|
@@ -337,16 +390,21 @@ class BindableUi(Generic[TWidget]):
|
|
|
337
390
|
|
|
338
391
|
"""
|
|
339
392
|
if isinstance(style, dict):
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
393
|
+
self._bind_style_for_str_fn(
|
|
394
|
+
lambda: ";".join(
|
|
395
|
+
f"{name}:{to_value(value)}" for name, value in style.items()
|
|
396
|
+
)
|
|
397
|
+
)
|
|
398
|
+
else:
|
|
399
|
+
self._bind_style_for_str_fn(style)
|
|
347
400
|
|
|
348
401
|
return self
|
|
349
402
|
|
|
403
|
+
def _bind_style_for_str_fn(self, style_str: TMaybeRef[str]):
|
|
404
|
+
@self._ui_signal_on(style_str, onchanges=False, deep=False)
|
|
405
|
+
def _(state: WatchedState):
|
|
406
|
+
self.style(add=state.current, remove=state.previous)
|
|
407
|
+
|
|
350
408
|
def scoped_style(self, selector: str, style: Union[str, Path]):
|
|
351
409
|
"""add scoped style to the element
|
|
352
410
|
|
|
@@ -5,10 +5,7 @@ from typing import (
|
|
|
5
5
|
)
|
|
6
6
|
|
|
7
7
|
import signe
|
|
8
|
-
from ex4nicegui.utils.signals import
|
|
9
|
-
TGetterOrReadonlyRef,
|
|
10
|
-
WatchedState,
|
|
11
|
-
)
|
|
8
|
+
from ex4nicegui.utils.signals import WatchedState, TMaybeRef
|
|
12
9
|
from nicegui import ui
|
|
13
10
|
from ex4nicegui.reactive.systems import color_system
|
|
14
11
|
|
|
@@ -17,10 +14,9 @@ class BackgroundColorableMixin(Protocol):
|
|
|
17
14
|
_ui_signal_on: Callable[[Callable[..., Any]], signe.Effect[None]]
|
|
18
15
|
|
|
19
16
|
@property
|
|
20
|
-
def element(self) -> ui.element:
|
|
21
|
-
...
|
|
17
|
+
def element(self) -> ui.element: ...
|
|
22
18
|
|
|
23
|
-
def _bind_background_color(self, value:
|
|
19
|
+
def _bind_background_color(self, value: TMaybeRef[str]):
|
|
24
20
|
@self._ui_signal_on(value, onchanges=False) # type: ignore
|
|
25
21
|
def _(state: WatchedState):
|
|
26
22
|
if state.previous is not None:
|
|
@@ -30,7 +26,7 @@ class BackgroundColorableMixin(Protocol):
|
|
|
30
26
|
|
|
31
27
|
self.element.update()
|
|
32
28
|
|
|
33
|
-
def bind_color(self, color:
|
|
29
|
+
def bind_color(self, color: TMaybeRef[str]):
|
|
34
30
|
"""bind color to the element
|
|
35
31
|
|
|
36
32
|
Args:
|
|
@@ -8,10 +8,7 @@ from typing import (
|
|
|
8
8
|
)
|
|
9
9
|
|
|
10
10
|
import signe
|
|
11
|
-
from ex4nicegui.utils.signals import
|
|
12
|
-
TGetterOrReadonlyRef,
|
|
13
|
-
to_value,
|
|
14
|
-
)
|
|
11
|
+
from ex4nicegui.utils.signals import to_value, TMaybeRef
|
|
15
12
|
from nicegui.elements.mixins.disableable_element import DisableableElement
|
|
16
13
|
|
|
17
14
|
|
|
@@ -22,10 +19,9 @@ class DisableableMixin(Protocol):
|
|
|
22
19
|
_ui_effect: Callable[[Callable[..., Any]], signe.Effect[None]]
|
|
23
20
|
|
|
24
21
|
@property
|
|
25
|
-
def element(self) -> DisableableElement:
|
|
26
|
-
...
|
|
22
|
+
def element(self) -> DisableableElement: ...
|
|
27
23
|
|
|
28
|
-
def bind_enabled(self, value:
|
|
24
|
+
def bind_enabled(self, value: TMaybeRef[bool]):
|
|
29
25
|
@self._ui_effect
|
|
30
26
|
def _():
|
|
31
27
|
raw_value = to_value(value)
|
|
@@ -34,7 +30,7 @@ class DisableableMixin(Protocol):
|
|
|
34
30
|
|
|
35
31
|
return self
|
|
36
32
|
|
|
37
|
-
def bind_disable(self, value:
|
|
33
|
+
def bind_disable(self, value: TMaybeRef[bool]):
|
|
38
34
|
@self._ui_effect
|
|
39
35
|
def _():
|
|
40
36
|
raw_value = not to_value(value)
|
|
@@ -9,6 +9,7 @@ import signe
|
|
|
9
9
|
from ex4nicegui.utils.signals import (
|
|
10
10
|
TGetterOrReadonlyRef,
|
|
11
11
|
WatchedState,
|
|
12
|
+
TMaybeRef,
|
|
12
13
|
)
|
|
13
14
|
from nicegui import ui
|
|
14
15
|
from ex4nicegui.reactive.systems import color_system
|
|
@@ -18,10 +19,9 @@ class TextColorableMixin(Protocol):
|
|
|
18
19
|
_ui_signal_on: Callable[[Callable[[TGetterOrReadonlyRef[str]], Any]], signe.Effect]
|
|
19
20
|
|
|
20
21
|
@property
|
|
21
|
-
def element(self) -> ui.element:
|
|
22
|
-
...
|
|
22
|
+
def element(self) -> ui.element: ...
|
|
23
23
|
|
|
24
|
-
def _bind_text_color(self, color:
|
|
24
|
+
def _bind_text_color(self, color: TMaybeRef[str]):
|
|
25
25
|
@self._ui_signal_on(color, onchanges=False) # type: ignore
|
|
26
26
|
def _(state: WatchedState):
|
|
27
27
|
if state.previous is not None:
|
|
@@ -31,7 +31,7 @@ class TextColorableMixin(Protocol):
|
|
|
31
31
|
|
|
32
32
|
self.element.update()
|
|
33
33
|
|
|
34
|
-
def bind_color(self, color:
|
|
34
|
+
def bind_color(self, color: TMaybeRef[str]):
|
|
35
35
|
"""bind text color to the element
|
|
36
36
|
|
|
37
37
|
Args:
|
|
@@ -46,16 +46,14 @@ class HtmlTextColorableMixin(Protocol):
|
|
|
46
46
|
_ui_signal_on: Callable[[Callable[[TGetterOrReadonlyRef[str]], Any]], signe.Effect]
|
|
47
47
|
|
|
48
48
|
@property
|
|
49
|
-
def element(self) -> ui.element:
|
|
50
|
-
...
|
|
49
|
+
def element(self) -> ui.element: ...
|
|
51
50
|
|
|
52
|
-
def bind_style(self, style: Dict[str,
|
|
53
|
-
...
|
|
51
|
+
def bind_style(self, style: Dict[str, TMaybeRef[Any]]): ...
|
|
54
52
|
|
|
55
|
-
def _bind_text_color(self, value:
|
|
53
|
+
def _bind_text_color(self, value: TMaybeRef[str]):
|
|
56
54
|
return self.bind_style({"color": value})
|
|
57
55
|
|
|
58
|
-
def bind_color(self, color:
|
|
56
|
+
def bind_color(self, color: TMaybeRef[str]):
|
|
59
57
|
"""bind text color to the element
|
|
60
58
|
|
|
61
59
|
Args:
|
|
@@ -90,7 +90,7 @@ class LazyInputBindableUi(InputBindableUi):
|
|
|
90
90
|
password_toggle_button: TMaybeRef[bool] = False,
|
|
91
91
|
on_change: Optional[Callable[..., Any]] = None,
|
|
92
92
|
autocomplete: Optional[TMaybeRef[List[str]]] = None,
|
|
93
|
-
validation: Dict[str, Callable[..., bool]] =
|
|
93
|
+
validation: Optional[Dict[str, Callable[..., bool]]] = None,
|
|
94
94
|
) -> None:
|
|
95
95
|
org_value = value
|
|
96
96
|
is_setter_value = is_setter_ref(value)
|
|
@@ -27,7 +27,7 @@ class TextareaBindableUi(BindableUi[ui.textarea], ValueElementMixin[str]):
|
|
|
27
27
|
placeholder: Optional[TMaybeRef[str]] = None,
|
|
28
28
|
value: TMaybeRef[str] = "",
|
|
29
29
|
on_change: Optional[Callable[..., Any]] = None,
|
|
30
|
-
validation: Dict[str, Callable[..., bool]] =
|
|
30
|
+
validation: Optional[Dict[str, Callable[..., bool]]] = None,
|
|
31
31
|
) -> None:
|
|
32
32
|
pc = ParameterClassifier(
|
|
33
33
|
locals(),
|
|
@@ -68,7 +68,7 @@ class LazyTextareaBindableUi(TextareaBindableUi):
|
|
|
68
68
|
placeholder: Optional[TMaybeRef[str]] = None,
|
|
69
69
|
value: TMaybeRef[str] = "",
|
|
70
70
|
on_change: Optional[Callable[..., Any]] = None,
|
|
71
|
-
validation: Dict[str, Callable[..., bool]] =
|
|
71
|
+
validation: Optional[Dict[str, Callable[..., bool]]] = None,
|
|
72
72
|
) -> None:
|
|
73
73
|
org_value = value
|
|
74
74
|
is_setter_value = is_setter_ref(value)
|
|
@@ -15,6 +15,7 @@ from ex4nicegui.reactive.systems.reactive_system import (
|
|
|
15
15
|
convert_kws_ref2value,
|
|
16
16
|
inject_method,
|
|
17
17
|
)
|
|
18
|
+
from ex4nicegui.utils.proxy import is_base_type_proxy
|
|
18
19
|
|
|
19
20
|
|
|
20
21
|
class ParameterClassifier:
|
|
@@ -34,7 +35,7 @@ class ParameterClassifier:
|
|
|
34
35
|
exclude.append(extend_kws)
|
|
35
36
|
|
|
36
37
|
self._args: Dict[str, Any] = {
|
|
37
|
-
k: v
|
|
38
|
+
k: v._ref if is_base_type_proxy(v) else v
|
|
38
39
|
for k, v in args.items()
|
|
39
40
|
if k != "self" and k[0] != "_" and (k not in exclude)
|
|
40
41
|
}
|
ex4nicegui/reactive/vfor.py
CHANGED
|
@@ -28,6 +28,8 @@ from dataclasses import dataclass
|
|
|
28
28
|
from signe.core.scope import Scope
|
|
29
29
|
from ex4nicegui.reactive.systems.object_system import get_attribute
|
|
30
30
|
from ex4nicegui.reactive.empty import Empty
|
|
31
|
+
from ex4nicegui.utils.proxy import to_ref_if_base_type_proxy
|
|
32
|
+
|
|
31
33
|
|
|
32
34
|
_T = TypeVar("_T")
|
|
33
35
|
_T_data = Union[List[Any], TGetterOrReadonlyRef[List[Any]], RefWrapper]
|
|
@@ -178,6 +180,8 @@ class vfor(Generic[_T]):
|
|
|
178
180
|
*,
|
|
179
181
|
key: Optional[Union[str, Callable[[int, Any], Any]]] = None,
|
|
180
182
|
) -> None:
|
|
183
|
+
data = to_ref_if_base_type_proxy(data)
|
|
184
|
+
|
|
181
185
|
self._data = to_ref_wrapper(lambda: data) if is_reactive(data) else data
|
|
182
186
|
self._get_key = vfor.index_key
|
|
183
187
|
self._transition_props = {}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
|
-
from typing import Callable, Union, Type, TypeVar
|
|
2
|
+
from typing import Any, Callable, List, Optional, Union, Type, TypeVar, overload
|
|
3
3
|
from ex4nicegui.utils.signals import (
|
|
4
4
|
deep_ref,
|
|
5
5
|
is_ref,
|
|
@@ -13,8 +13,16 @@ from ex4nicegui.utils.signals import (
|
|
|
13
13
|
from ex4nicegui.utils.types import ReadonlyRef
|
|
14
14
|
from functools import partial
|
|
15
15
|
from signe.core.reactive import NoProxy
|
|
16
|
+
from ex4nicegui.utils.proxy.descriptor import class_var_setter
|
|
17
|
+
from ex4nicegui.utils.proxy.bool import BoolProxy
|
|
18
|
+
from ex4nicegui.utils.proxy.int import IntProxy
|
|
19
|
+
from ex4nicegui.utils.proxy.float import FloatProxy
|
|
20
|
+
from ex4nicegui.utils.proxy.base import Proxy
|
|
21
|
+
from ex4nicegui.utils.proxy import to_value_if_base_type_proxy
|
|
22
|
+
from ex4nicegui.utils.proxy.descriptor import ProxyDescriptor
|
|
16
23
|
|
|
17
24
|
_CACHED_VARS_FLAG = "__vm_cached__"
|
|
25
|
+
_LIST_VAR_FLAG = "__vm_list_var__"
|
|
18
26
|
|
|
19
27
|
_T = TypeVar("_T")
|
|
20
28
|
|
|
@@ -31,15 +39,30 @@ class ViewModel(NoProxy):
|
|
|
31
39
|
from ex4nicegui import rxui
|
|
32
40
|
|
|
33
41
|
class MyVm(rxui.ViewModel):
|
|
34
|
-
count =
|
|
35
|
-
data =
|
|
42
|
+
count = 0
|
|
43
|
+
data = []
|
|
44
|
+
nums = rxui.list_var(lambda: [1, 2, 3])
|
|
45
|
+
|
|
46
|
+
def __init__(self):
|
|
47
|
+
super().__init__()
|
|
48
|
+
self.data = [1, 2, 3]
|
|
49
|
+
|
|
50
|
+
def increment(self):
|
|
51
|
+
self.count += 1
|
|
52
|
+
|
|
53
|
+
def add_data(self):
|
|
54
|
+
self.data.append(4)
|
|
55
|
+
|
|
36
56
|
|
|
37
57
|
vm = MyVm()
|
|
38
58
|
|
|
39
59
|
rxui.label(vm.count)
|
|
40
60
|
rxui.number(value=vm.count)
|
|
41
61
|
|
|
42
|
-
|
|
62
|
+
ui.button("Increment", on_click=vm.increment)
|
|
63
|
+
ui.button("Add Data", on_click=vm.add_data)
|
|
64
|
+
rxui.label(vm.data)
|
|
65
|
+
rxui.label(vm.nums)
|
|
43
66
|
"""
|
|
44
67
|
|
|
45
68
|
def __init__(self):
|
|
@@ -49,8 +72,86 @@ class ViewModel(NoProxy):
|
|
|
49
72
|
if callable(value) and hasattr(value, _CACHED_VARS_FLAG):
|
|
50
73
|
setattr(self, name, computed(partial(value, self)))
|
|
51
74
|
|
|
75
|
+
def __init_subclass__(cls) -> None:
|
|
76
|
+
need_vars = (
|
|
77
|
+
(name, value)
|
|
78
|
+
for name, value in vars(cls).items()
|
|
79
|
+
if not name.startswith("_")
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
for name, value in need_vars:
|
|
83
|
+
class_var_setter(cls, name, value, _LIST_VAR_FLAG)
|
|
84
|
+
|
|
85
|
+
@overload
|
|
86
|
+
@staticmethod
|
|
87
|
+
def on_refs_changed(vm: ViewModel): ...
|
|
88
|
+
|
|
89
|
+
@overload
|
|
90
|
+
@staticmethod
|
|
91
|
+
def on_refs_changed(
|
|
92
|
+
vm: ViewModel, callback: Optional[Callable[[], Any]] = None
|
|
93
|
+
): ...
|
|
94
|
+
|
|
95
|
+
@staticmethod
|
|
96
|
+
def on_refs_changed(vm: ViewModel, callback: Optional[Callable[[], Any]] = None):
|
|
97
|
+
if callback is None:
|
|
98
|
+
|
|
99
|
+
def wrapper(fn: Callable[[], Any]):
|
|
100
|
+
return ViewModel.on_refs_changed(vm, fn)
|
|
101
|
+
|
|
102
|
+
return wrapper
|
|
103
|
+
|
|
104
|
+
refs = ViewModel.get_refs(vm)
|
|
105
|
+
|
|
106
|
+
@on(refs, onchanges=True, deep=False)
|
|
107
|
+
def _():
|
|
108
|
+
callback()
|
|
109
|
+
|
|
110
|
+
@staticmethod
|
|
111
|
+
def get_refs(vm: ViewModel):
|
|
112
|
+
"""Get all the refs of a ViewModel.
|
|
113
|
+
|
|
114
|
+
Args:
|
|
115
|
+
vm (ViewModel): The ViewModel to get refs from.
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
"""
|
|
119
|
+
var_names = [
|
|
120
|
+
name
|
|
121
|
+
for name, value in vm.__class__.__dict__.items()
|
|
122
|
+
if isinstance(value, ProxyDescriptor)
|
|
123
|
+
]
|
|
124
|
+
|
|
125
|
+
return [getattr(vm, name) for name in var_names]
|
|
126
|
+
|
|
127
|
+
@staticmethod
|
|
128
|
+
def to_value(value: Union[ViewModel, Proxy, List, dict]):
|
|
129
|
+
"""Convert a ViewModel, Proxy, List, or dict to a value.
|
|
130
|
+
|
|
131
|
+
Args:
|
|
132
|
+
value (Union[ViewModel, Proxy, List, dict]): The value to convert.
|
|
133
|
+
|
|
134
|
+
"""
|
|
135
|
+
if isinstance(value, list):
|
|
136
|
+
return [ViewModel.to_value(v) for v in value]
|
|
137
|
+
|
|
138
|
+
if isinstance(value, dict):
|
|
139
|
+
return {k: ViewModel.to_value(v) for k, v in value.items()}
|
|
140
|
+
|
|
141
|
+
if isinstance(value, ViewModel):
|
|
142
|
+
return {
|
|
143
|
+
k: ViewModel.to_value(v)
|
|
144
|
+
for k, v in value.__dict__.items()
|
|
145
|
+
if not k.startswith("_")
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if isinstance(value, Proxy):
|
|
149
|
+
return to_value_if_base_type_proxy(value)
|
|
150
|
+
|
|
151
|
+
return value
|
|
152
|
+
|
|
52
153
|
@staticmethod
|
|
53
|
-
def
|
|
154
|
+
def _display(model: Union[ViewModel, Type]):
|
|
54
155
|
result = to_ref("")
|
|
55
156
|
|
|
56
157
|
watch_refs = _recursive_to_refs(model)
|
|
@@ -67,6 +168,18 @@ class ViewModel(NoProxy):
|
|
|
67
168
|
|
|
68
169
|
return result
|
|
69
170
|
|
|
171
|
+
@staticmethod
|
|
172
|
+
def is_bool(value: Any):
|
|
173
|
+
return isinstance(value, bool) or isinstance(value, BoolProxy)
|
|
174
|
+
|
|
175
|
+
@staticmethod
|
|
176
|
+
def is_int(value: Any):
|
|
177
|
+
return isinstance(value, int) or isinstance(value, IntProxy)
|
|
178
|
+
|
|
179
|
+
@staticmethod
|
|
180
|
+
def is_float(value: Any):
|
|
181
|
+
return isinstance(value, float) or isinstance(value, FloatProxy)
|
|
182
|
+
|
|
70
183
|
|
|
71
184
|
def _recursive_to_value(value_or_model):
|
|
72
185
|
value = to_raw(to_value(value_or_model))
|
|
@@ -139,6 +252,34 @@ def var(value: Union[_T_Var_Value, Callable[[], _T_Var_Value]]) -> Ref[_T_Var_Va
|
|
|
139
252
|
return deep_ref(value)
|
|
140
253
|
|
|
141
254
|
|
|
255
|
+
def list_var(factory: Callable[[], List[_T_Var_Value]]) -> List[_T_Var_Value]:
|
|
256
|
+
"""Create implicitly proxied reactive variables for lists. Use them just like ordinary lists while maintaining reactivity. Only use within rxui.ViewModel.
|
|
257
|
+
|
|
258
|
+
Args:
|
|
259
|
+
factory (Callable[[], List[_T_Var_Value]]): A factory function that returns a new list.
|
|
260
|
+
|
|
261
|
+
Example:
|
|
262
|
+
.. code-block:: python
|
|
263
|
+
from ex4nicegui import rxui
|
|
264
|
+
class State(rxui.ViewModel):
|
|
265
|
+
data = rxui.list_var(lambda: [1, 2, 3])
|
|
266
|
+
|
|
267
|
+
def append_data(self):
|
|
268
|
+
self.data.append(len(self.data) + 1)
|
|
269
|
+
|
|
270
|
+
def display_data(self):
|
|
271
|
+
return ",".join(map(str, self.data))
|
|
272
|
+
|
|
273
|
+
state = State()
|
|
274
|
+
ui.button("Append", on_click=state.append_data)
|
|
275
|
+
rxui.label(state.display_data)
|
|
276
|
+
|
|
277
|
+
"""
|
|
278
|
+
assert callable(factory), "factory must be a callable"
|
|
279
|
+
setattr(factory, _LIST_VAR_FLAG, None)
|
|
280
|
+
return factory # type: ignore
|
|
281
|
+
|
|
282
|
+
|
|
142
283
|
def cached_var(func: Callable[..., _T]) -> ReadonlyRef[_T]:
|
|
143
284
|
"""A decorator to cache the result of a function. Only use within rxui.ViewModel.
|
|
144
285
|
|
|
@@ -149,7 +290,7 @@ def cached_var(func: Callable[..., _T]) -> ReadonlyRef[_T]:
|
|
|
149
290
|
.. code-block:: python
|
|
150
291
|
from ex4nicegui import rxui
|
|
151
292
|
class MyVm(rxui.ViewModel):
|
|
152
|
-
name =
|
|
293
|
+
name = "John"
|
|
153
294
|
|
|
154
295
|
@rxui.cached_var
|
|
155
296
|
def uppper_name(self):
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
from .base import Proxy
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def is_base_type_proxy(obj):
|
|
5
|
+
return isinstance(obj, Proxy)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def to_ref_if_base_type_proxy(obj):
|
|
9
|
+
if is_base_type_proxy(obj):
|
|
10
|
+
return obj._ref
|
|
11
|
+
else:
|
|
12
|
+
return obj
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def to_value_if_base_type_proxy(obj):
|
|
16
|
+
if is_base_type_proxy(obj):
|
|
17
|
+
return obj._ref.value
|
|
18
|
+
else:
|
|
19
|
+
return obj
|