ex4nicegui 0.6.9__py3-none-any.whl → 0.7.0__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.
Files changed (47) hide show
  1. ex4nicegui/bi/dataSourceFacade.py +20 -20
  2. ex4nicegui/bi/index.py +20 -23
  3. ex4nicegui/reactive/EChartsComponent/ECharts.py +9 -8
  4. ex4nicegui/reactive/__init__.py +8 -0
  5. ex4nicegui/reactive/base.py +61 -54
  6. ex4nicegui/reactive/mixins/backgroundColor.py +5 -5
  7. ex4nicegui/reactive/mixins/disableable.py +8 -8
  8. ex4nicegui/reactive/mixins/textColor.py +10 -10
  9. ex4nicegui/reactive/officials/aggrid.py +5 -5
  10. ex4nicegui/reactive/officials/button.py +15 -15
  11. ex4nicegui/reactive/officials/checkbox.py +5 -5
  12. ex4nicegui/reactive/officials/chip.py +5 -5
  13. ex4nicegui/reactive/officials/circular_progress.py +6 -6
  14. ex4nicegui/reactive/officials/color_picker.py +7 -7
  15. ex4nicegui/reactive/officials/column.py +5 -5
  16. ex4nicegui/reactive/officials/date.py +5 -5
  17. ex4nicegui/reactive/officials/dialog.py +49 -0
  18. ex4nicegui/reactive/officials/echarts.py +49 -51
  19. ex4nicegui/reactive/officials/expansion.py +5 -5
  20. ex4nicegui/reactive/officials/icon.py +6 -6
  21. ex4nicegui/reactive/officials/image.py +5 -5
  22. ex4nicegui/reactive/officials/input.py +8 -8
  23. ex4nicegui/reactive/officials/knob.py +6 -6
  24. ex4nicegui/reactive/officials/label.py +6 -6
  25. ex4nicegui/reactive/officials/linear_progress.py +6 -6
  26. ex4nicegui/reactive/officials/number.py +9 -9
  27. ex4nicegui/reactive/officials/radio.py +8 -8
  28. ex4nicegui/reactive/officials/row.py +5 -5
  29. ex4nicegui/reactive/officials/select.py +9 -8
  30. ex4nicegui/reactive/officials/slider.py +5 -5
  31. ex4nicegui/reactive/officials/switch.py +5 -5
  32. ex4nicegui/reactive/officials/tab.py +0 -12
  33. ex4nicegui/reactive/officials/tab_panels.py +5 -5
  34. ex4nicegui/reactive/officials/table.py +13 -13
  35. ex4nicegui/reactive/officials/tabs.py +5 -5
  36. ex4nicegui/reactive/officials/textarea.py +5 -5
  37. ex4nicegui/reactive/officials/tooltip.py +40 -0
  38. ex4nicegui/reactive/q_pagination.py +5 -5
  39. ex4nicegui/reactive/vfor.js +14 -4
  40. ex4nicegui/reactive/vfor.py +128 -58
  41. ex4nicegui/reactive/view_model.py +160 -0
  42. ex4nicegui/reactive/vmodel.py +42 -12
  43. ex4nicegui/utils/signals.py +23 -21
  44. {ex4nicegui-0.6.9.dist-info → ex4nicegui-0.7.0.dist-info}/METADATA +219 -48
  45. {ex4nicegui-0.6.9.dist-info → ex4nicegui-0.7.0.dist-info}/RECORD +47 -44
  46. {ex4nicegui-0.6.9.dist-info → ex4nicegui-0.7.0.dist-info}/LICENSE +0 -0
  47. {ex4nicegui-0.6.9.dist-info → ex4nicegui-0.7.0.dist-info}/WHEEL +0 -0
@@ -49,16 +49,16 @@ class TabPanelsBindableUi(BindableUi[ui.tab_panels]):
49
49
  def value(self):
50
50
  return self.element.value
51
51
 
52
- def bind_prop(self, prop: str, ref_ui: TGetterOrReadonlyRef):
52
+ def bind_prop(self, prop: str, value: TGetterOrReadonlyRef):
53
53
  if prop == "value":
54
- return self.bind_value(ref_ui)
54
+ return self.bind_value(value)
55
55
 
56
- return super().bind_prop(prop, ref_ui)
56
+ return super().bind_prop(prop, value)
57
57
 
58
- def bind_value(self, ref_ui: TGetterOrReadonlyRef):
58
+ def bind_value(self, value: TGetterOrReadonlyRef):
59
59
  @self._ui_effect
60
60
  def _():
61
- self.element.set_value(to_value(ref_ui))
61
+ self.element.set_value(to_value(value))
62
62
 
63
63
 
64
64
  class lazy_tab_panel(ui.tab_panel):
@@ -145,22 +145,22 @@ class TableBindableUi(BindableUi[ui.table]):
145
145
  ]
146
146
  return cls(cols, rows, **other_kws)
147
147
 
148
- def bind_prop(self, prop: str, ref_ui: TGetterOrReadonlyRef):
148
+ def bind_prop(self, prop: str, value: TGetterOrReadonlyRef):
149
149
  if prop == "dataframe":
150
- return self.bind_dataframe(ref_ui)
150
+ return self.bind_dataframe(value)
151
151
 
152
152
  if prop == "rows":
153
- return self.bind_rows(ref_ui)
153
+ return self.bind_rows(value)
154
154
 
155
155
  if prop == "columns":
156
- return self.bind_columns(ref_ui)
156
+ return self.bind_columns(value)
157
157
 
158
- return super().bind_prop(prop, ref_ui)
158
+ return super().bind_prop(prop, value)
159
159
 
160
- def bind_dataframe(self, ref_df: TGetterOrReadonlyRef):
160
+ def bind_dataframe(self, dataframe: TGetterOrReadonlyRef):
161
161
  @ref_computed
162
162
  def cp_converted_df():
163
- df = ref_df.value
163
+ df = dataframe.value
164
164
  return utils_common.convert_dataframe(df)
165
165
 
166
166
  @ref_computed
@@ -182,20 +182,20 @@ class TableBindableUi(BindableUi[ui.table]):
182
182
 
183
183
  return self
184
184
 
185
- def bind_rows(self, ref_ui: TGetterOrReadonlyRef[List[Dict]]):
186
- @self._ui_signal_on(ref_ui, deep=True)
185
+ def bind_rows(self, rows: TGetterOrReadonlyRef[List[Dict]]):
186
+ @self._ui_signal_on(rows, deep=True)
187
187
  def _():
188
188
  ele = self.element
189
- ele._props["rows"] = list(to_raw(to_value(ref_ui)))
189
+ ele._props["rows"] = list(to_raw(to_value(rows)))
190
190
  ele.update()
191
191
 
192
192
  return self
193
193
 
194
- def bind_columns(self, ref_ui: TGetterOrReadonlyRef[List[Dict]]):
195
- @self._ui_signal_on(ref_ui, deep=True)
194
+ def bind_columns(self, columns: TGetterOrReadonlyRef[List[Dict]]):
195
+ @self._ui_signal_on(columns, deep=True)
196
196
  def _():
197
197
  ele = self.element
198
- ele._props["columns"] = list(to_raw(to_value(ref_ui)))
198
+ ele._props["columns"] = list(to_raw(to_value(columns)))
199
199
  ele.update()
200
200
 
201
201
  return self
@@ -32,13 +32,13 @@ class TabsBindableUi(BindableUi[ui.tabs]):
32
32
  def value(self):
33
33
  return self.element.value
34
34
 
35
- def bind_prop(self, prop: str, ref_ui: TGetterOrReadonlyRef):
35
+ def bind_prop(self, prop: str, value: TGetterOrReadonlyRef):
36
36
  if prop == "value":
37
- return self.bind_value(ref_ui)
37
+ return self.bind_value(value)
38
38
 
39
- return super().bind_prop(prop, ref_ui)
39
+ return super().bind_prop(prop, value)
40
40
 
41
- def bind_value(self, ref_ui: TGetterOrReadonlyRef):
41
+ def bind_value(self, value: TGetterOrReadonlyRef):
42
42
  @self._ui_effect
43
43
  def _():
44
- self.element.set_value(to_value(ref_ui))
44
+ self.element.set_value(to_value(value))
@@ -52,16 +52,16 @@ class TextareaBindableUi(BindableUi[ui.textarea]):
52
52
  def value(self):
53
53
  return self.element.value
54
54
 
55
- def bind_prop(self, prop: str, ref_ui: TGetterOrReadonlyRef):
55
+ def bind_prop(self, prop: str, value: TGetterOrReadonlyRef):
56
56
  if prop == "value":
57
- return self.bind_value(ref_ui)
57
+ return self.bind_value(value)
58
58
 
59
- return super().bind_prop(prop, ref_ui)
59
+ return super().bind_prop(prop, value)
60
60
 
61
- def bind_value(self, ref_ui: TGetterOrReadonlyRef[str]):
61
+ def bind_value(self, value: TGetterOrReadonlyRef[str]):
62
62
  @self._ui_effect
63
63
  def _():
64
- self.element.set_value(to_value(ref_ui))
64
+ self.element.set_value(to_value(value))
65
65
 
66
66
  return self
67
67
 
@@ -0,0 +1,40 @@
1
+ from typing import Any
2
+ from ex4nicegui.reactive.services.reactive_service import ParameterClassifier
3
+ from ex4nicegui.utils.signals import (
4
+ TGetterOrReadonlyRef,
5
+ to_value,
6
+ _TMaybeRef as TMaybeRef,
7
+ )
8
+ from nicegui import ui
9
+ from .base import BindableUi
10
+
11
+
12
+ class TooltipBindableUi(BindableUi[ui.tooltip]):
13
+ def __init__(
14
+ self,
15
+ text: TMaybeRef[Any] = "",
16
+ ) -> None:
17
+ pc = ParameterClassifier(locals(), maybeRefs=["text"], events=[])
18
+
19
+ element = ui.tooltip(**pc.get_values_kws())
20
+ super().__init__(element)
21
+
22
+ for key, value in pc.get_bindings().items():
23
+ self.bind_prop(key, value) # type: ignore
24
+
25
+ @property
26
+ def text(self):
27
+ return self.element.text
28
+
29
+ def bind_prop(self, prop: str, value: TGetterOrReadonlyRef):
30
+ if prop == "text":
31
+ return self.bind_text(value)
32
+
33
+ return super().bind_prop(prop, value)
34
+
35
+ def bind_text(self, text: TGetterOrReadonlyRef):
36
+ @self._ui_effect
37
+ def _():
38
+ self.element.set_text(str(to_value(text)))
39
+
40
+ return self
@@ -36,15 +36,15 @@ class PaginationBindableUi(BindableUi[ui.pagination]):
36
36
  def value(self):
37
37
  return self.element.value
38
38
 
39
- def bind_prop(self, prop: str, ref_ui: TGetterOrReadonlyRef):
39
+ def bind_prop(self, prop: str, value: TGetterOrReadonlyRef):
40
40
  if prop == "value":
41
- return self.bind_value(ref_ui)
41
+ return self.bind_value(value)
42
42
 
43
- return super().bind_prop(prop, ref_ui)
43
+ return super().bind_prop(prop, value)
44
44
 
45
- def bind_value(self, ref_ui: TGetterOrReadonlyRef[int]):
45
+ def bind_value(self, value: TGetterOrReadonlyRef[int]):
46
46
  @self._ui_effect
47
47
  def _():
48
- self.element.set_value(to_value(ref_ui))
48
+ self.element.set_value(to_value(value))
49
49
 
50
50
  return self
@@ -1,13 +1,23 @@
1
- export default {
2
- props: { itemIds: Array },
1
+ const { h, TransitionGroup } = Vue;
3
2
 
3
+ export default {
4
+ props: { itemIds: Array, transitionProps: Object },
4
5
  render() {
6
+ const is_transition = !!this.transitionProps;
5
7
  const slotBox = this.$slots.default()
6
-
7
8
  const slots = this.itemIds.map(({ elementId }) => {
8
9
  return slotBox.find(v => v.key === elementId)
9
10
  });
10
11
 
11
- return slots
12
+ function beforeLeave(el) {
13
+ const { marginLeft, marginTop, width, height } = window.getComputedStyle(el)
14
+ console.log('beforeLeave');
15
+ el.style.left = `${el.offsetLeft - parseFloat(marginLeft, 10)}px`
16
+ el.style.top = `${el.offsetTop - parseFloat(marginTop, 10)}px`
17
+ el.style.width = width
18
+ el.style.height = height
19
+ }
20
+
21
+ return is_transition ? h(TransitionGroup, { ...this.transitionProps, onBeforeLeave: beforeLeave }, slots) : slots;
12
22
  }
13
23
  }
@@ -3,7 +3,7 @@ from nicegui.element import Element
3
3
  from nicegui import ui
4
4
  from ex4nicegui.utils.clientScope import _CLIENT_SCOPE_MANAGER
5
5
  from ex4nicegui.utils.signals import (
6
- TReadonlyRef,
6
+ Ref,
7
7
  on,
8
8
  to_ref,
9
9
  to_ref_wrapper,
@@ -21,7 +21,7 @@ from typing import (
21
21
  TypeVar,
22
22
  Generic,
23
23
  Union,
24
- cast,
24
+ Literal,
25
25
  )
26
26
  from functools import partial
27
27
  from dataclasses import dataclass
@@ -38,35 +38,90 @@ class VforItem(Empty):
38
38
 
39
39
 
40
40
  class VforContainer(Element, component="vfor.js"):
41
- def __init__(self) -> None:
41
+ def __init__(self, transition_props: Optional[Dict[str, Any]] = None) -> None:
42
42
  super().__init__()
43
43
  self._props["itemIds"] = []
44
44
 
45
+ if transition_props:
46
+ self._props["transitionProps"] = transition_props
47
+
45
48
  def update_items(self, item_ids: List[Dict]):
46
49
  self._props["itemIds"] = item_ids
47
50
  self.update()
48
51
 
49
52
 
53
+ def wrapper_getter_setter(ref: Ref, index: Ref[int], *keys: Union[str, int]):
54
+ proxy = ref.value
55
+
56
+ def getter():
57
+ item = proxy[index.value]
58
+ result = item
59
+
60
+ for k in keys:
61
+ result = result[k]
62
+ return result
63
+
64
+ def setter(value):
65
+ item = proxy[index.value]
66
+
67
+ if len(keys) == 0:
68
+ proxy[index.value] = value
69
+
70
+ if len(keys) == 1:
71
+ item[keys[0]] = value
72
+ return
73
+
74
+ obj = item[keys[0]]
75
+ for k in keys[1:-1]:
76
+ obj = obj[k]
77
+ obj[keys[-1]] = value
78
+
79
+ return getter, setter
80
+
81
+
82
+ class VforStoreItem(Generic[_T], RefWrapper[_T]):
83
+ def __init__(
84
+ self,
85
+ source: _T_data,
86
+ index: Ref[int],
87
+ keys: Optional[List[Union[str, int]]] = None,
88
+ ) -> None:
89
+ self._source = source
90
+ self._data_index = index
91
+ self._keys = keys or []
92
+
93
+ getter, setter = wrapper_getter_setter(source, index, *self._keys)
94
+ super().__init__(getter, setter)
95
+
96
+ def __getitem__(self, key: Union[str, int]):
97
+ return VforStoreItem(self._source, self._data_index, self._keys + [key])
98
+
99
+
50
100
  class VforStore(Generic[_T]):
51
101
  def __init__(self, source: _T_data, index: int) -> None:
52
102
  self._source = source
103
+ self._raw_index = index
53
104
  self._data_index = to_ref(index)
54
105
 
55
106
  @property
56
107
  def row_index(self):
108
+ """Returns the responsive index of the current row."""
57
109
  return self._data_index
58
110
 
59
- def get(self) -> TReadonlyRef[_T]:
60
- def base_setter(value):
61
- to_value(self._source)[self._data_index.value] = value
111
+ @property
112
+ def raw_index(self):
113
+ """Returns the index of the current row."""
114
+ return self._raw_index
62
115
 
63
- wrapper = to_ref_wrapper(
64
- lambda: to_value(self._source)[self._data_index.value],
65
- base_setter,
66
- )
67
- wrapper._is_readonly = True
116
+ def get_item(self) -> _T:
117
+ """Returns the current item."""
118
+ return to_value(self._source)[self.raw_index] # type: ignore
68
119
 
69
- return cast(TReadonlyRef, wrapper)
120
+ def get(self) -> Ref[_T]:
121
+ """Returns a ref object of the current item.
122
+ Suitable for immutable types such as `int`, `str`, `float`, etc.
123
+ """
124
+ return VforStoreItem(self._source, self._data_index) # type: ignore
70
125
 
71
126
  def update(self, index: int):
72
127
  self._data_index.value = index
@@ -80,10 +135,6 @@ class StoreItem:
80
135
  scope: Scope
81
136
 
82
137
 
83
- def _get_key_with_index(idx: int, data: Any):
84
- return idx
85
-
86
-
87
138
  def _get_key_with_getter(attr: str, idx: int, data: Any):
88
139
  return get_attribute(data, attr)
89
140
 
@@ -97,28 +148,27 @@ class vfor(Generic[_T]):
97
148
 
98
149
 
99
150
  ## Examples
100
- ```python
101
- from ex4nicegui.reactive import rxui
102
- from ex4nicegui import to_ref
103
- items = to_ref(
104
- [
105
- {"message": "foo", "done": False},
106
- {"message": "bar", "done": True},
107
- ]
108
- )
109
151
 
152
+ .. code-block:: python
153
+ from ex4nicegui.reactive import rxui
154
+ from ex4nicegui import to_ref
155
+ items = to_ref(
156
+ [
157
+ {"message": "foo", "done": False},
158
+ {"message": "bar", "done": True},
159
+ ]
160
+ )
110
161
 
111
- @rxui.vfor(items,key='message')
112
- def _(store: rxui.VforStore):
113
- msg_ref = store.get("message") # this is ref object
114
162
 
115
- # type text into the input box and
116
- # the title of the checkbox changes sync
117
- with ui.card():
118
- rxui.input(value=msg_ref)
119
- rxui.checkbox(text=msg_ref, value=store.get("done"))
163
+ @rxui.vfor(items,key='message')
164
+ def _(store: rxui.VforStore):
165
+ msg_ref = store.get("message") # this is ref object
120
166
 
121
- ```
167
+ # type text into the input box and
168
+ # the title of the checkbox changes sync
169
+ with ui.card():
170
+ rxui.input(value=msg_ref)
171
+ rxui.checkbox(text=msg_ref, value=store.get("done"))
122
172
 
123
173
  """
124
174
 
@@ -128,9 +178,9 @@ class vfor(Generic[_T]):
128
178
  *,
129
179
  key: Optional[Union[str, Callable[[int, Any], Any]]] = None,
130
180
  ) -> None:
131
- self._vfor_container = VforContainer()
132
181
  self._data = to_ref_wrapper(lambda: data) if is_reactive(data) else data
133
- self._get_key = _get_key_with_index
182
+ self._get_key = vfor.index_key
183
+ self._transition_props = {}
134
184
 
135
185
  if isinstance(key, str):
136
186
  self._get_key = partial(_get_key_with_getter, key)
@@ -139,11 +189,27 @@ class vfor(Generic[_T]):
139
189
 
140
190
  self._store_map: Dict[Union[Any, int], StoreItem] = {}
141
191
 
192
+ def transition_group(
193
+ self,
194
+ css=True,
195
+ name: Optional[str] = "list",
196
+ duration: Optional[float] = None,
197
+ type: Optional[Literal["transition", "animation"]] = None,
198
+ mode: Optional[Literal["in-out", "out-in", "default"]] = None,
199
+ appear=False,
200
+ ):
201
+ kws = {k: v for k, v in locals().items() if k != "self" and v is not None}
202
+ self._transition_props.update(**kws)
203
+
204
+ return self
205
+
142
206
  def __call__(self, fn: Callable[[Any], None]):
207
+ _vfor_container = VforContainer(self._transition_props)
208
+
143
209
  def build_element(index: int, value):
144
210
  key = self._get_key(index, value)
145
211
 
146
- with self._vfor_container, VforItem() as element:
212
+ with _vfor_container, VforItem() as element:
147
213
  store = VforStore(self._data, index) # type: ignore
148
214
  scope = _CLIENT_SCOPE_MANAGER.new_scope()
149
215
 
@@ -153,18 +219,27 @@ class vfor(Generic[_T]):
153
219
 
154
220
  return key, element, store, scope
155
221
 
156
- for idx, value in enumerate(to_value(self._data)): # type: ignore
157
- key, element, store, scope = build_element(idx, value)
158
- self._store_map[key] = StoreItem(store, element.id, scope)
159
-
160
222
  ng_client = ui.context.client
161
223
 
162
- @on(self._data, deep=True)
224
+ @on(self._data, deep=True, priority_level=-1)
163
225
  def _():
164
226
  data_map = {
165
227
  self._get_key(idx, d): d for idx, d in enumerate(to_value(self._data))
166
228
  }
167
229
 
230
+ # remove item if it's not in the new data
231
+ remove_items = [
232
+ (key, value)
233
+ for key, value in self._store_map.items()
234
+ if key not in data_map
235
+ ]
236
+
237
+ for key, item in remove_items:
238
+ target = ng_client.elements.get(item.elementId)
239
+ _vfor_container.remove(target) # type: ignore
240
+ item.scope.dispose()
241
+ del self._store_map[key]
242
+
168
243
  for idx, (key, value) in enumerate(data_map.items()):
169
244
  store_item = self._store_map.get(key)
170
245
  if store_item:
@@ -177,22 +252,17 @@ class vfor(Generic[_T]):
177
252
  key, element, store, score = build_element(idx, value)
178
253
  self._store_map[key] = StoreItem(store, element.id, score)
179
254
 
180
- # remove item
181
- remove_items = [
182
- (key, value)
183
- for key, value in self._store_map.items()
184
- if key not in data_map
255
+ item_ids = [
256
+ {"key": key, "elementId": self._store_map.get(key).elementId} # type: ignore
257
+ for key in data_map.keys()
185
258
  ]
186
259
 
187
- for key, item in remove_items:
188
- target = ng_client.elements.get(item.elementId)
189
- self._vfor_container.remove(target) # type: ignore
190
- item.scope.dispose()
191
- del self._store_map[key]
260
+ _vfor_container.update_items(item_ids)
261
+
262
+ @staticmethod
263
+ def value_key(_, data):
264
+ return data
192
265
 
193
- self._vfor_container.update_items(
194
- [
195
- {"key": key, "elementId": self._store_map.get(key).elementId} # type: ignore
196
- for key in data_map.keys()
197
- ]
198
- )
266
+ @staticmethod
267
+ def index_key(idx, _):
268
+ return idx
@@ -0,0 +1,160 @@
1
+ from __future__ import annotations
2
+ from typing import Callable, Union, Type, TypeVar
3
+ from ex4nicegui.utils.signals import (
4
+ deep_ref,
5
+ is_ref,
6
+ to_value,
7
+ to_ref,
8
+ on,
9
+ to_raw,
10
+ ref_computed as computed,
11
+ Ref,
12
+ )
13
+ from ex4nicegui.utils.types import ReadonlyRef
14
+ from functools import partial
15
+ from signe.core.reactive import NoProxy
16
+
17
+ _CACHED_VARS_FLAG = "__vm_cached__"
18
+
19
+ _T = TypeVar("_T")
20
+
21
+
22
+ class ViewModel(NoProxy):
23
+ """A base class for reactive view models.
24
+
25
+ @see - https://github.com/CrystalWindSnake/ex4nicegui/blob/main/README.en.md#ViewModel
26
+
27
+ @中文文档 - https://gitee.com/carson_add/ex4nicegui/tree/main/#ViewModel
28
+
29
+ Example:
30
+ .. code-block:: python
31
+ from ex4nicegui import rxui
32
+
33
+ class MyVm(rxui.ViewModel):
34
+ count = rxui.var(0)
35
+ data = rxui.var(lambda: [1,2,3])
36
+
37
+ vm = MyVm()
38
+
39
+ rxui.label(vm.count)
40
+ rxui.number(value=vm.count)
41
+
42
+
43
+ """
44
+
45
+ def __init__(self):
46
+ for name, value in self.__class__.__dict__.items():
47
+ if is_ref(value):
48
+ setattr(self, name, deep_ref(to_value(value)))
49
+ if callable(value) and hasattr(value, _CACHED_VARS_FLAG):
50
+ setattr(self, name, computed(partial(value, self)))
51
+
52
+ @staticmethod
53
+ def display(model: Union[ViewModel, Type]):
54
+ result = to_ref("")
55
+
56
+ watch_refs = _recursive_to_refs(model)
57
+
58
+ all_refs = {
59
+ key: value for key, value in model.__dict__.items() if is_ref(value)
60
+ }
61
+
62
+ @on(watch_refs)
63
+ def _():
64
+ result.value = str(
65
+ {key: _recursive_to_value(value) for key, value in all_refs.items()}
66
+ )
67
+
68
+ return result
69
+
70
+
71
+ def _recursive_to_value(value_or_model):
72
+ value = to_raw(to_value(value_or_model))
73
+
74
+ if isinstance(value, ViewModel):
75
+ all_refs = {
76
+ key: value for key, value in value.__dict__.items() if is_ref(value)
77
+ }
78
+ return {key: _recursive_to_value(value) for key, value in all_refs.items()}
79
+ elif isinstance(value, dict):
80
+ return {key: _recursive_to_value(val) for key, val in value.items()}
81
+ elif isinstance(value, list):
82
+ return [_recursive_to_value(val) for val in value]
83
+ else:
84
+ return value
85
+
86
+
87
+ def _recursive_to_refs(model):
88
+ result = []
89
+ stack = [model]
90
+
91
+ def handle_ref(value):
92
+ if is_ref(value):
93
+ stack.append(value.value)
94
+ result.append(value)
95
+
96
+ elif isinstance(value, (ViewModel, Type)):
97
+ stack.append(value)
98
+ elif isinstance(value, dict):
99
+ stack.append(value)
100
+
101
+ while stack:
102
+ current = stack.pop()
103
+
104
+ if isinstance(current, (ViewModel, Type)):
105
+ for key, value in current.__dict__.items():
106
+ handle_ref(value)
107
+
108
+ elif isinstance(current, dict):
109
+ for key, value in current.items():
110
+ handle_ref(value)
111
+
112
+ elif isinstance(current, list):
113
+ for value in current:
114
+ handle_ref(value)
115
+
116
+ return result
117
+
118
+
119
+ _T_Var_Value = TypeVar("_T_Var_Value")
120
+
121
+
122
+ def var(value: Union[_T_Var_Value, Callable[[], _T_Var_Value]]) -> Ref[_T_Var_Value]:
123
+ """Create a reactive variable. Only use within rxui.ViewModel.
124
+
125
+ Args:
126
+ value (Union[_T_Var_Value, Callable[[], _T_Var_Value]]): The initial value or a function to generate the initial value.
127
+
128
+ Example:
129
+ .. code-block:: python
130
+ from ex4nicegui import rxui
131
+ class MyVm(rxui.ViewModel):
132
+ count = rxui.var(0)
133
+ data = rxui.var(lambda: [1,2,3])
134
+
135
+
136
+ """
137
+ if callable(value):
138
+ return deep_ref(value())
139
+ return deep_ref(value)
140
+
141
+
142
+ def cached_var(func: Callable[..., _T]) -> ReadonlyRef[_T]:
143
+ """A decorator to cache the result of a function. Only use within rxui.ViewModel.
144
+
145
+ Args:
146
+ func (Callable): The function to cache.
147
+
148
+ Example:
149
+ .. code-block:: python
150
+ from ex4nicegui import rxui
151
+ class MyVm(rxui.ViewModel):
152
+ name = rxui.var("John")
153
+
154
+ @rxui.cached_var
155
+ def uppper_name(self):
156
+ return self.name.value.upper()
157
+
158
+ """
159
+ setattr(func, _CACHED_VARS_FLAG, None)
160
+ return func # type: ignore