ex4nicegui 0.7.1__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.
Files changed (50) hide show
  1. ex4nicegui/reactive/EChartsComponent/ECharts.js +2 -3
  2. ex4nicegui/reactive/EChartsComponent/ECharts.py +2 -4
  3. ex4nicegui/reactive/__init__.py +2 -1
  4. ex4nicegui/reactive/base.py +108 -50
  5. ex4nicegui/reactive/mermaid/mermaid.py +2 -5
  6. ex4nicegui/reactive/mixins/backgroundColor.py +4 -8
  7. ex4nicegui/reactive/mixins/disableable.py +4 -8
  8. ex4nicegui/reactive/mixins/flexLayout.py +51 -0
  9. ex4nicegui/reactive/mixins/textColor.py +8 -10
  10. ex4nicegui/reactive/mixins/value_element.py +27 -0
  11. ex4nicegui/reactive/officials/card.py +32 -2
  12. ex4nicegui/reactive/officials/checkbox.py +7 -12
  13. ex4nicegui/reactive/officials/chip.py +11 -1
  14. ex4nicegui/reactive/officials/circular_progress.py +7 -11
  15. ex4nicegui/reactive/officials/color_picker.py +4 -10
  16. ex4nicegui/reactive/officials/column.py +19 -11
  17. ex4nicegui/reactive/officials/date.py +4 -11
  18. ex4nicegui/reactive/officials/dialog.py +4 -11
  19. ex4nicegui/reactive/officials/echarts.py +9 -7
  20. ex4nicegui/reactive/officials/expansion.py +4 -11
  21. ex4nicegui/reactive/officials/input.py +13 -13
  22. ex4nicegui/reactive/officials/knob.py +4 -13
  23. ex4nicegui/reactive/officials/linear_progress.py +6 -11
  24. ex4nicegui/reactive/officials/number.py +7 -11
  25. ex4nicegui/reactive/officials/radio.py +4 -12
  26. ex4nicegui/reactive/officials/row.py +18 -11
  27. ex4nicegui/reactive/officials/slider.py +4 -12
  28. ex4nicegui/reactive/officials/switch.py +5 -12
  29. ex4nicegui/reactive/officials/tab_panels.py +4 -8
  30. ex4nicegui/reactive/officials/tabs.py +4 -9
  31. ex4nicegui/reactive/officials/textarea.py +6 -12
  32. ex4nicegui/reactive/services/reactive_service.py +2 -1
  33. ex4nicegui/reactive/vfor.py +4 -0
  34. ex4nicegui/reactive/view_model.py +147 -6
  35. ex4nicegui/utils/proxy/__init__.py +19 -0
  36. ex4nicegui/utils/proxy/base.py +9 -0
  37. ex4nicegui/utils/proxy/bool.py +58 -0
  38. ex4nicegui/utils/proxy/date.py +88 -0
  39. ex4nicegui/utils/proxy/descriptor.py +126 -0
  40. ex4nicegui/utils/proxy/dict.py +110 -0
  41. ex4nicegui/utils/proxy/float.py +153 -0
  42. ex4nicegui/utils/proxy/int.py +223 -0
  43. ex4nicegui/utils/proxy/list.py +147 -0
  44. ex4nicegui/utils/proxy/string.py +430 -0
  45. ex4nicegui/utils/proxy/utils.py +6 -0
  46. ex4nicegui/utils/signals.py +9 -1
  47. {ex4nicegui-0.7.1.dist-info → ex4nicegui-0.8.1.dist-info}/METADATA +497 -244
  48. {ex4nicegui-0.7.1.dist-info → ex4nicegui-0.8.1.dist-info}/RECORD +50 -37
  49. {ex4nicegui-0.7.1.dist-info → ex4nicegui-0.8.1.dist-info}/LICENSE +0 -0
  50. {ex4nicegui-0.7.1.dist-info → ex4nicegui-0.8.1.dist-info}/WHEEL +0 -0
@@ -1,3 +1,4 @@
1
+ import 'echarts'
1
2
  import { convertDynamicProperties } from "../../static/utils/dynamic_properties.js";
2
3
 
3
4
  function collectMapRegisterTask() {
@@ -54,10 +55,8 @@ const mapRegisterTasks = collectMapRegisterTask();
54
55
  export default {
55
56
  template: "<div></div>",
56
57
  async mounted() {
57
- await this.$nextTick(); // wait for Tailwind classes to be applied
58
-
58
+ await new Promise((resolve) => setTimeout(resolve, 0)); // wait for Tailwind classes to be applied
59
59
  this.chart = echarts.init(this.$el, this.theme);
60
-
61
60
  this.resizeObs = new ResizeObserver(this.chart.resize)
62
61
 
63
62
  // Prevent interruption of chart animations due to resize operations.
@@ -23,7 +23,7 @@ NG_ROOT = Path(nicegui.__file__).parent / "elements"
23
23
  libraries = [NG_ROOT / "lib/echarts/echarts.min.js"]
24
24
 
25
25
 
26
- class echarts(Element, component="ECharts.js", libraries=libraries): # type: ignore
26
+ class echarts(Element, component="ECharts.js", dependencies=libraries): # type: ignore
27
27
  def __init__(
28
28
  self,
29
29
  options: Optional[dict] = None,
@@ -112,7 +112,7 @@ class echarts(Element, component="ECharts.js", libraries=libraries): # type: ig
112
112
  self.run_method("echarts_on", ui_event_name, query)
113
113
 
114
114
  def run_chart_method(
115
- self, name: str, *args, timeout: float = 1, check_interval: float = 0.01
115
+ self, name: str, *args, timeout: float = 1
116
116
  ) -> AwaitableResponse:
117
117
  """Run a method of the JSONEditor instance.
118
118
 
@@ -124,7 +124,6 @@ class echarts(Element, component="ECharts.js", libraries=libraries): # type: ig
124
124
  :param name: name of the method (a prefix ":" indicates that the arguments are JavaScript expressions)
125
125
  :param args: arguments to pass to the method (Python objects or JavaScript expressions)
126
126
  :param timeout: timeout in seconds (default: 1 second)
127
- :param check_interval: interval in seconds to check for a response (default: 0.01 seconds)
128
127
 
129
128
  :return: AwaitableResponse that can be awaited to get the result of the method call
130
129
  """
@@ -133,5 +132,4 @@ class echarts(Element, component="ECharts.js", libraries=libraries): # type: ig
133
132
  name,
134
133
  *args,
135
134
  timeout=timeout,
136
- check_interval=check_interval,
137
135
  )
@@ -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
 
@@ -10,11 +10,9 @@ NG_ROOT = Path(nicegui.__file__).parent / "elements"
10
10
 
11
11
  EX4_LIBS_ROOT = Path(__file__).parent.parent.parent / "libs"
12
12
 
13
- exposed_libraries = [
13
+ dependencies = [
14
14
  NG_ROOT / "lib/mermaid/mermaid.esm.min.mjs",
15
15
  EX4_LIBS_ROOT / "d3/*.js",
16
- ]
17
- extra_libraries = [
18
16
  NG_ROOT / "lib/mermaid/*.js",
19
17
  ]
20
18
 
@@ -27,8 +25,7 @@ class NodeClickEventArguments(UiEventArguments):
27
25
  class Mermaid( # type: ignore
28
26
  ContentElement,
29
27
  component="mermaid.js",
30
- exposed_libraries=exposed_libraries, # type: ignore
31
- extra_libraries=extra_libraries, # type: ignore
28
+ dependencies=dependencies, # type: ignore
32
29
  ):
33
30
  CONTENT_PROP = "content"
34
31
 
@@ -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)
@@ -0,0 +1,51 @@
1
+ from typing import (
2
+ Any,
3
+ Callable,
4
+ Protocol,
5
+ )
6
+ from typing_extensions import Self
7
+ import signe
8
+ from ex4nicegui.utils.signals import to_value, TMaybeRef
9
+ from nicegui import ui
10
+
11
+
12
+ class FlexWrapMixin(Protocol):
13
+ _ui_signal_on: Callable[[Callable[..., Any]], signe.Effect[None]]
14
+
15
+ @property
16
+ def element(self) -> ui.element:
17
+ ...
18
+
19
+ def bind_style(self, classes) -> Self:
20
+ ...
21
+
22
+ def bind_wrap(self, value: TMaybeRef[bool]) -> Self:
23
+ return self.bind_style(
24
+ {"flex-wrap": lambda: "wrap" if to_value(value) else "nowrap"}
25
+ )
26
+
27
+ def _bind_specified_props(self, prop: str, value: TMaybeRef[Any]):
28
+ if prop == "wrap":
29
+ return self.bind_wrap(value)
30
+
31
+ return None
32
+
33
+
34
+ class FlexAlignItemsMixin(Protocol):
35
+ _ui_signal_on: Callable[[Callable[..., Any]], signe.Effect[None]]
36
+
37
+ @property
38
+ def element(self) -> ui.element:
39
+ ...
40
+
41
+ def bind_classes(self, classes) -> Self:
42
+ ...
43
+
44
+ def bind_align_items(self, value: TMaybeRef[str]):
45
+ return self.bind_classes(lambda: f"items-{to_value(value)}")
46
+
47
+ def _bind_specified_props(self, prop: str, value: TMaybeRef[Any]):
48
+ if prop == "align-items":
49
+ return self.bind_align_items(value)
50
+
51
+ return None
@@ -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:
@@ -0,0 +1,27 @@
1
+ from typing import Any, Callable, Protocol, Generic, TypeVar
2
+ import signe
3
+ from ex4nicegui.utils.signals import to_value, TMaybeRef
4
+ from nicegui.elements.mixins.value_element import ValueElement
5
+
6
+ T = TypeVar("T", contravariant=True)
7
+
8
+
9
+ class ValueElementMixin(Protocol, Generic[T]):
10
+ _ui_signal_on: Callable[[Callable[..., Any]], signe.Effect[None]]
11
+
12
+ @property
13
+ def element(self) -> ValueElement:
14
+ ...
15
+
16
+ def bind_value(self, value: TMaybeRef[T]):
17
+ @self._ui_signal_on(value) # type: ignore
18
+ def _():
19
+ self.element.set_value(to_value(value))
20
+
21
+ return self
22
+
23
+ def _bind_specified_props(self, prop: str, value: TMaybeRef[T]):
24
+ if prop == "value":
25
+ return self.bind_value(value)
26
+
27
+ return None
@@ -1,18 +1,48 @@
1
1
  from typing import (
2
2
  Any,
3
+ Literal,
4
+ Optional,
3
5
  )
4
6
  from nicegui import ui
5
7
  from .base import BindableUi
8
+ from ex4nicegui.reactive.mixins.flexLayout import FlexAlignItemsMixin
9
+ from ex4nicegui.utils.signals import TMaybeRef
10
+ from ex4nicegui.reactive.services.reactive_service import ParameterClassifier
6
11
 
7
12
 
8
- class CardBindableUi(BindableUi[ui.card]):
13
+ class CardBindableUi(BindableUi[ui.card], FlexAlignItemsMixin):
9
14
  def __init__(
10
15
  self,
16
+ align_items: Optional[
17
+ TMaybeRef[Literal["start", "end", "center", "baseline", "stretch"]]
18
+ ] = None,
11
19
  ) -> None:
12
- element = ui.card()
20
+ """Card
21
+
22
+ This element is based on Quasar's `QCard <https://quasar.dev/vue-components/card>`_ component.
23
+ It provides a container with a dropped shadow.
24
+
25
+ Note:
26
+ In contrast to this element,
27
+ the original QCard has no padding by default and hides outer borders and shadows of nested elements.
28
+ If you want the original behavior, use the `tight` method.
29
+
30
+ :param align_items: alignment of the items in the card ("start", "end", "center", "baseline", or "stretch"; default: `None`)
31
+ """
32
+ pc = ParameterClassifier(locals(), maybeRefs=["wrap", "align_items"], events=[])
33
+ element = ui.card(**pc.get_values_kws())
13
34
 
14
35
  super().__init__(element)
15
36
 
37
+ for key, value in pc.get_bindings().items():
38
+ self.bind_prop(key, value) # type: ignore
39
+
40
+ def bind_prop(self, prop: str, value: TMaybeRef):
41
+ if FlexAlignItemsMixin._bind_specified_props(self, prop, value):
42
+ return self
43
+
44
+ return super().bind_prop(prop, value)
45
+
16
46
  def __enter__(self):
17
47
  self.element.__enter__()
18
48
  return self
@@ -8,15 +8,18 @@ from ex4nicegui.reactive.services.reactive_service import ParameterClassifier
8
8
  from ex4nicegui.utils.signals import (
9
9
  TGetterOrReadonlyRef,
10
10
  _TMaybeRef as TMaybeRef,
11
- to_value,
12
11
  )
13
12
  from nicegui import ui
14
13
  from .base import BindableUi, DisableableMixin
14
+ from ex4nicegui.reactive.mixins.value_element import ValueElementMixin
15
+
15
16
 
16
17
  T = TypeVar("T")
17
18
 
18
19
 
19
- class CheckboxBindableUi(BindableUi[ui.checkbox], DisableableMixin):
20
+ class CheckboxBindableUi(
21
+ BindableUi[ui.checkbox], DisableableMixin, ValueElementMixin[bool]
22
+ ):
20
23
  def __init__(
21
24
  self,
22
25
  text: TMaybeRef[str] = "",
@@ -32,7 +35,6 @@ class CheckboxBindableUi(BindableUi[ui.checkbox], DisableableMixin):
32
35
  )
33
36
 
34
37
  value_kws = pc.get_values_kws()
35
-
36
38
  element = ui.checkbox(**value_kws)
37
39
  super().__init__(element) # type: ignore
38
40
 
@@ -44,14 +46,7 @@ class CheckboxBindableUi(BindableUi[ui.checkbox], DisableableMixin):
44
46
  return self.element.value
45
47
 
46
48
  def bind_prop(self, prop: str, value: TGetterOrReadonlyRef):
47
- if prop == "value":
48
- return self.bind_value(value)
49
+ if ValueElementMixin._bind_specified_props(self, prop, value):
50
+ return self
49
51
 
50
52
  return super().bind_prop(prop, value)
51
-
52
- def bind_value(self, value: TGetterOrReadonlyRef[bool]):
53
- @self._ui_signal_on(value)
54
- def _():
55
- self.element.set_value(to_value(value))
56
-
57
- return self
@@ -9,12 +9,14 @@ from nicegui import ui
9
9
  from .base import BindableUi
10
10
  from ex4nicegui.reactive.mixins.backgroundColor import BackgroundColorableMixin
11
11
  from ex4nicegui.reactive.mixins.textColor import TextColorableMixin
12
+ from ex4nicegui.reactive.mixins.value_element import ValueElementMixin
12
13
 
13
14
 
14
15
  class ChipBindableUi(
15
16
  BindableUi[ui.chip],
16
17
  BackgroundColorableMixin,
17
18
  TextColorableMixin,
19
+ ValueElementMixin[bool],
18
20
  ):
19
21
  def __init__(
20
22
  self,
@@ -29,6 +31,7 @@ class ChipBindableUi(
29
31
  on_selection_change: Optional[Callable[..., Any]] = None,
30
32
  removable: TMaybeRef[bool] = False,
31
33
  on_value_change: Optional[Callable[..., Any]] = None,
34
+ value: TMaybeRef[bool] = True,
32
35
  ) -> None:
33
36
  pc = ParameterClassifier(
34
37
  locals(),
@@ -40,11 +43,15 @@ class ChipBindableUi(
40
43
  "selectable",
41
44
  "selected",
42
45
  "removable",
46
+ "value",
43
47
  ],
48
+ v_model=("value", "on_value_change"),
44
49
  events=["on_click", "on_selection_change", "on_value_change"],
45
50
  )
46
51
 
47
- element = ui.chip(**pc.get_values_kws())
52
+ kws = pc.get_values_kws()
53
+ kws.pop("value", None)
54
+ element = ui.chip(**kws)
48
55
  super().__init__(element)
49
56
 
50
57
  for key, value in pc.get_bindings().items():
@@ -55,6 +62,9 @@ class ChipBindableUi(
55
62
  return self.element.text
56
63
 
57
64
  def bind_prop(self, prop: str, value: TGetterOrReadonlyRef):
65
+ if ValueElementMixin._bind_specified_props(self, prop, value):
66
+ return self
67
+
58
68
  if prop == "text":
59
69
  return self.bind_text(value)
60
70