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.
@@ -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",
@@ -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: TGetterOrReadonlyRef[bool]):
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: TGetterOrReadonlyRef[bool]):
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: TGetterOrReadonlyRef[str]) -> Self:
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#bind-class-names
238
- @中文文档 - https://gitee.com/carson_add/ex4nicegui/tree/main/#%E7%BB%91%E5%AE%9A%E7%B1%BB%E5%90%8D
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
- for name, ref_obj in classes.items():
275
-
276
- @self._ui_effect
277
- def _(name=name, ref_obj=ref_obj):
278
- if to_value(ref_obj):
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
- @self._ui_effect
289
- def _():
290
- for name, value in cast(Dict, to_value(classes)).items(): # type: ignore
291
- if value:
292
- self.classes(add=name)
293
- else:
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
- elif isinstance(classes, list):
299
- for ref_name in classes:
300
- self._bind_single_class(ref_name)
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 _bind_single_class(self, class_name: _T_bind_classes_type_single):
305
- if is_ref(class_name) or isinstance(class_name, Callable):
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
- @on(class_name)
308
- def _(state: WatchedState):
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
- return self
365
+ @overload
366
+ def bind_style(self, style: Dict[str, TMaybeRef[Any]]) -> Self: ...
314
367
 
315
- def bind_style(self, style: Dict[str, TGetterOrReadonlyRef[Any]]):
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#bind-style
319
- @中文文档 - https://gitee.com/carson_add/ex4nicegui/tree/main/#bind-style
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
- for name, ref_obj in style.items():
341
- if is_ref(ref_obj) or isinstance(ref_obj, Callable):
342
-
343
- @self._ui_effect
344
- def _(name=name, ref_obj=ref_obj):
345
- self.element._style[name] = str(to_value(ref_obj))
346
- self.element.update()
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: TGetterOrReadonlyRef[str]):
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: TGetterOrReadonlyRef[str]):
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: TGetterOrReadonlyRef[bool]):
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: TGetterOrReadonlyRef[bool]):
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: TGetterOrReadonlyRef[str]):
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: TGetterOrReadonlyRef[str]):
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, TGetterOrReadonlyRef[Any]]):
53
- ...
51
+ def bind_style(self, style: Dict[str, TMaybeRef[Any]]): ...
54
52
 
55
- def _bind_text_color(self, value: TGetterOrReadonlyRef[str]):
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: TGetterOrReadonlyRef[str]):
56
+ def bind_color(self, color: TMaybeRef[str]):
59
57
  """bind text color to the element
60
58
 
61
59
  Args:
@@ -35,7 +35,6 @@ class CheckboxBindableUi(
35
35
  )
36
36
 
37
37
  value_kws = pc.get_values_kws()
38
-
39
38
  element = ui.checkbox(**value_kws)
40
39
  super().__init__(element) # type: ignore
41
40
 
@@ -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
  }
@@ -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 = rxui.var(0)
35
- data = rxui.var(lambda: [1,2,3])
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 display(model: Union[ViewModel, Type]):
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 = rxui.var("John")
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
@@ -0,0 +1,9 @@
1
+ from typing import Any, Protocol
2
+
3
+
4
+ class ProxyProtocol(Protocol):
5
+ _ref: Any
6
+
7
+
8
+ class Proxy(ProxyProtocol):
9
+ pass