ex4nicegui 0.6.9__py3-none-any.whl → 0.7.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.
Files changed (51) 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 +14 -0
  5. ex4nicegui/reactive/base.py +64 -55
  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/avatar.py +86 -0
  11. ex4nicegui/reactive/officials/badge.py +102 -0
  12. ex4nicegui/reactive/officials/button.py +15 -15
  13. ex4nicegui/reactive/officials/checkbox.py +6 -7
  14. ex4nicegui/reactive/officials/chip.py +6 -7
  15. ex4nicegui/reactive/officials/circular_progress.py +7 -7
  16. ex4nicegui/reactive/officials/color_picker.py +8 -8
  17. ex4nicegui/reactive/officials/column.py +5 -5
  18. ex4nicegui/reactive/officials/date.py +6 -6
  19. ex4nicegui/reactive/officials/dialog.py +49 -0
  20. ex4nicegui/reactive/officials/echarts.py +49 -51
  21. ex4nicegui/reactive/officials/expansion.py +6 -6
  22. ex4nicegui/reactive/officials/icon.py +7 -13
  23. ex4nicegui/reactive/officials/image.py +6 -6
  24. ex4nicegui/reactive/officials/input.py +10 -11
  25. ex4nicegui/reactive/officials/knob.py +7 -7
  26. ex4nicegui/reactive/officials/label.py +11 -9
  27. ex4nicegui/reactive/officials/linear_progress.py +7 -7
  28. ex4nicegui/reactive/officials/number.py +10 -10
  29. ex4nicegui/reactive/officials/radio.py +10 -10
  30. ex4nicegui/reactive/officials/row.py +5 -5
  31. ex4nicegui/reactive/officials/select.py +11 -10
  32. ex4nicegui/reactive/officials/slider.py +6 -6
  33. ex4nicegui/reactive/officials/switch.py +6 -7
  34. ex4nicegui/reactive/officials/tab.py +0 -12
  35. ex4nicegui/reactive/officials/tab_panels.py +5 -5
  36. ex4nicegui/reactive/officials/table.py +13 -13
  37. ex4nicegui/reactive/officials/tabs.py +5 -5
  38. ex4nicegui/reactive/officials/textarea.py +6 -6
  39. ex4nicegui/reactive/officials/toggle.py +88 -0
  40. ex4nicegui/reactive/officials/tooltip.py +40 -0
  41. ex4nicegui/reactive/q_pagination.py +5 -5
  42. ex4nicegui/reactive/systems/reactive_system.py +2 -2
  43. ex4nicegui/reactive/vfor.js +14 -4
  44. ex4nicegui/reactive/vfor.py +128 -58
  45. ex4nicegui/reactive/view_model.py +160 -0
  46. ex4nicegui/reactive/vmodel.py +42 -12
  47. ex4nicegui/utils/signals.py +23 -21
  48. {ex4nicegui-0.6.9.dist-info → ex4nicegui-0.7.1.dist-info}/METADATA +223 -48
  49. {ex4nicegui-0.6.9.dist-info → ex4nicegui-0.7.1.dist-info}/RECORD +51 -45
  50. {ex4nicegui-0.6.9.dist-info → ex4nicegui-0.7.1.dist-info}/LICENSE +0 -0
  51. {ex4nicegui-0.6.9.dist-info → ex4nicegui-0.7.1.dist-info}/WHEEL +0 -0
@@ -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
@@ -26,6 +26,7 @@ import executing
26
26
  import ast
27
27
  import warnings
28
28
  from ex4nicegui.reactive.systems.object_system import get_attribute, set_attribute
29
+ from ex4nicegui.utils.types import _TMaybeRef as TMaybeRef, Ref
29
30
 
30
31
  _T = TypeVar("_T")
31
32
 
@@ -138,23 +139,24 @@ def vmodel(expr: Any, *attrs: Union[str, int]) -> TRef[Any]:
138
139
  expr (Any): _description_
139
140
 
140
141
  ## Examples
141
- ```python
142
- from ex4nicegui.reactive import rxui
143
- from ex4nicegui import deep_ref
144
142
 
145
- data = deep_ref({"a": 1, "b": [1, 2, 3, 4]})
143
+ .. code-block:: python
144
+ from ex4nicegui.reactive import rxui
145
+ from ex4nicegui import deep_ref
146
146
 
147
- rxui.label(lambda: f"{data.value=!s}")
147
+ data = deep_ref({"a": 1, "b": [1, 2, 3, 4]})
148
148
 
149
- # No binding effect
150
- rxui.input(value=data.value["a"])
149
+ rxui.label(lambda: f"{data.value=!s}")
151
150
 
152
- # readonly binding
153
- rxui.input(value=lambda: data.value["a"])
151
+ # No binding effect
152
+ rxui.input(value=data.value["a"])
153
+
154
+ # readonly binding
155
+ rxui.input(value=lambda: data.value["a"])
156
+
157
+ # two-way binding
158
+ rxui.input(value=rxui.vmodel(data.value,'a'))
154
159
 
155
- # two-way binding
156
- rxui.input(value=rxui.vmodel(data.value["a"]))
157
- ```
158
160
 
159
161
  """
160
162
 
@@ -205,3 +207,31 @@ def vmodel(expr: Any, *attrs: Union[str, int]) -> TRef[Any]:
205
207
  TRef,
206
208
  wrapper,
207
209
  )
210
+
211
+
212
+ def vmodel_with_index(ref: Ref, index: TMaybeRef[int], *keys: Union[str, int]) -> Ref:
213
+ proxy = ref.value
214
+
215
+ def getter():
216
+ item = proxy[to_value(index)]
217
+ result = item
218
+
219
+ for k in keys:
220
+ result = get_attribute(result, k)
221
+ return result
222
+
223
+ def setter(value):
224
+ item = proxy[to_value(index)]
225
+
226
+ if len(keys) == 1:
227
+ set_attribute(item, keys[0], value)
228
+ return
229
+
230
+ obj = get_attribute(item, keys[0])
231
+
232
+ for k in keys[1:-1]:
233
+ set_attribute(obj, k, get_attribute(obj, k))
234
+
235
+ set_attribute(obj, keys[-1], value)
236
+
237
+ return RefWrapper(getter, setter) # type: ignore
@@ -54,11 +54,12 @@ def to_value(obj: Union[_TMaybeRef[T], RefWrapper]) -> T:
54
54
  obj (Union[_TMaybeRef[T], RefWrapper]): A getter function, an existing ref, or a non-function value.
55
55
 
56
56
  ## Example
57
- ```python
58
- to_value(1) # 1
59
- to_value(lambda: 1) # 1
60
- to_value(to_ref(1)) # 1
61
- ```
57
+
58
+ .. code-block:: python
59
+ to_value(1) # 1
60
+ to_value(lambda: 1) # 1
61
+ to_value(to_ref(1)) # 1
62
+
62
63
  """
63
64
  if is_ref(obj):
64
65
  return obj.value # type: ignore
@@ -239,26 +240,27 @@ def event_batch(event_fn: Callable[..., None]):
239
240
  Args:
240
241
  event_fn (Callable[..., None]): event callback
241
242
 
242
- @Example
243
- ```python
244
- from nicegui import ui
245
- from ex4nicegui import on, to_ref, effect, ref_computed, batch
243
+ ## Example
244
+
245
+ .. code-block:: python
246
+ from nicegui import ui
247
+ from ex4nicegui import on, to_ref, effect, ref_computed, batch
248
+
249
+ a = to_ref(0)
250
+ b = to_ref(0)
251
+ text = ref_computed(lambda: f"a={a.value};b={b.value}")
246
252
 
247
- a = to_ref(0)
248
- b = to_ref(0)
249
- text = ref_computed(lambda: f"a={a.value};b={b.value}")
253
+ @on([a, b, text])
254
+ def when_vars_changed():
255
+ ui.notify(f"a:{a.value};b:{b.value};text={text.value}")
250
256
 
251
- @on([a, b, text])
252
- def when_vars_changed():
253
- ui.notify(f"a:{a.value};b:{b.value};text={text.value}")
257
+ @event_batch
258
+ def when_click():
259
+ a.value += 1
260
+ b.value += 1
254
261
 
255
- @event_batch
256
- def when_click():
257
- a.value += 1
258
- b.value += 1
262
+ ui.button("change all values", on_click=when_click)
259
263
 
260
- ui.button("change all values", on_click=when_click)
261
- ```
262
264
  """
263
265
 
264
266
  def wrap(*args, **kwargs):