ex4nicegui 0.6.6__py3-none-any.whl → 0.6.8__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 (70) hide show
  1. ex4nicegui/__init__.py +10 -6
  2. ex4nicegui/gsap/__init__.py +11 -0
  3. ex4nicegui/helper/__init__.py +4 -0
  4. ex4nicegui/helper/client_instance_locker.py +31 -0
  5. ex4nicegui/libs/gsap/.DS_Store +0 -0
  6. ex4nicegui/libs/gsap/gsap.mjs +6 -0
  7. ex4nicegui/reactive/EChartsComponent/ECharts.js +19 -9
  8. ex4nicegui/reactive/local_file_picker.py +1 -2
  9. ex4nicegui/reactive/officials/aggrid.py +7 -7
  10. ex4nicegui/reactive/officials/base.py +198 -86
  11. ex4nicegui/reactive/officials/button.py +5 -5
  12. ex4nicegui/reactive/officials/checkbox.py +5 -6
  13. ex4nicegui/reactive/officials/circular_progress.py +5 -6
  14. ex4nicegui/reactive/officials/color_picker.py +7 -8
  15. ex4nicegui/reactive/officials/column.py +4 -3
  16. ex4nicegui/reactive/officials/date.py +6 -12
  17. ex4nicegui/reactive/officials/drawer.py +2 -3
  18. ex4nicegui/reactive/officials/echarts.py +32 -15
  19. ex4nicegui/reactive/officials/expansion.py +5 -5
  20. ex4nicegui/reactive/officials/grid.py +1 -1
  21. ex4nicegui/reactive/officials/html.py +1 -3
  22. ex4nicegui/reactive/officials/icon.py +9 -10
  23. ex4nicegui/reactive/officials/image.py +5 -7
  24. ex4nicegui/reactive/officials/input.py +8 -10
  25. ex4nicegui/reactive/officials/knob.py +5 -7
  26. ex4nicegui/reactive/officials/label.py +5 -4
  27. ex4nicegui/reactive/officials/linear_progress.py +9 -10
  28. ex4nicegui/reactive/officials/number.py +23 -8
  29. ex4nicegui/reactive/officials/radio.py +7 -9
  30. ex4nicegui/reactive/officials/row.py +1 -1
  31. ex4nicegui/reactive/officials/select.py +7 -9
  32. ex4nicegui/reactive/officials/slider.py +6 -8
  33. ex4nicegui/reactive/officials/switch.py +5 -7
  34. ex4nicegui/reactive/officials/tab.py +1 -1
  35. ex4nicegui/reactive/officials/tab_panel.py +1 -1
  36. ex4nicegui/reactive/officials/tab_panels.py +15 -3
  37. ex4nicegui/reactive/officials/table.py +12 -10
  38. ex4nicegui/reactive/officials/tabs.py +4 -3
  39. ex4nicegui/reactive/officials/textarea.py +6 -8
  40. ex4nicegui/reactive/officials/upload.py +2 -2
  41. ex4nicegui/reactive/q_pagination.py +5 -4
  42. ex4nicegui/reactive/scopedStyle.js +55 -0
  43. ex4nicegui/reactive/scopedStyle.py +22 -0
  44. ex4nicegui/reactive/services/color_service.py +56 -0
  45. ex4nicegui/reactive/services/pandas_service.py +31 -0
  46. ex4nicegui/reactive/{utils.py → services/reactive_service.py} +9 -67
  47. ex4nicegui/reactive/systems/color_system.py +25 -0
  48. ex4nicegui/reactive/systems/object_system.py +33 -0
  49. ex4nicegui/reactive/systems/reactive_system.py +21 -0
  50. ex4nicegui/reactive/useMouse/UseMouse.py +4 -4
  51. ex4nicegui/reactive/usePagination.py +1 -1
  52. ex4nicegui/reactive/vfor.py +1 -2
  53. ex4nicegui/reactive/vmodel.py +1 -1
  54. ex4nicegui/utils/refComputed.py +147 -0
  55. ex4nicegui/utils/refWrapper.py +57 -0
  56. ex4nicegui/utils/signals.py +51 -192
  57. ex4nicegui/utils/types.py +16 -0
  58. ex4nicegui/version.py +3 -0
  59. {ex4nicegui-0.6.6.dist-info → ex4nicegui-0.6.8.dist-info}/METADATA +1274 -1124
  60. {ex4nicegui-0.6.6.dist-info → ex4nicegui-0.6.8.dist-info}/RECORD +81 -74
  61. {ex4nicegui-0.6.6.dist-info → ex4nicegui-0.6.8.dist-info}/WHEEL +1 -2
  62. ex4nicegui/reactive/EChartsComponent/__init__.py +0 -0
  63. ex4nicegui/reactive/UseDraggable/__init__.py +0 -0
  64. ex4nicegui/reactive/dropZone/__init__.py +0 -0
  65. ex4nicegui/reactive/mermaid/__init__.py +0 -0
  66. ex4nicegui/reactive/officials/__init__.py +0 -1
  67. ex4nicegui/reactive/officials/utils.py +0 -11
  68. ex4nicegui/reactive/useMouse/__init__.py +0 -0
  69. ex4nicegui-0.6.6.dist-info/top_level.txt +0 -1
  70. {ex4nicegui-0.6.6.dist-info → ex4nicegui-0.6.8.dist-info}/LICENSE +0 -0
ex4nicegui/__init__.py CHANGED
@@ -1,6 +1,12 @@
1
1
  from ex4nicegui import reactive as rxui
2
+ from ex4nicegui.utils.refComputed import ref_computed
3
+ from ex4nicegui.utils.types import (
4
+ _TMaybeRef as TMaybeRef,
5
+ Ref,
6
+ ReadonlyRef,
7
+ TGetterOrReadonlyRef,
8
+ )
2
9
  from ex4nicegui.utils.signals import (
3
- ref_computed,
4
10
  effect,
5
11
  effect_refreshable,
6
12
  to_raw,
@@ -10,9 +16,6 @@ from ex4nicegui.utils.signals import (
10
16
  ref,
11
17
  on,
12
18
  event_batch,
13
- _TMaybeRef as TMaybeRef,
14
- Ref,
15
- ReadonlyRef,
16
19
  reactive,
17
20
  deep_ref,
18
21
  is_setter_ref,
@@ -21,6 +24,7 @@ from ex4nicegui.utils.signals import (
21
24
  )
22
25
  from ex4nicegui.utils.asyncComputed import async_computed
23
26
  from ex4nicegui.utils.clientScope import new_scope
27
+ from .version import __version__
24
28
 
25
29
  __all__ = [
26
30
  "async_computed",
@@ -36,6 +40,7 @@ __all__ = [
36
40
  "on",
37
41
  "event_batch",
38
42
  "TMaybeRef",
43
+ "TGetterOrReadonlyRef",
39
44
  "Ref",
40
45
  "ReadonlyRef",
41
46
  "reactive",
@@ -44,6 +49,5 @@ __all__ = [
44
49
  "to_raw",
45
50
  "is_setter_ref",
46
51
  "new_scope",
52
+ "__version__",
47
53
  ]
48
-
49
- __version__ = "0.6.6"
@@ -2,6 +2,17 @@ from .gsap import set_defaults, from_, to, new, run_script
2
2
  from .timeline import Timeline as timeline
3
3
 
4
4
 
5
+ import warnings
6
+
7
+ RED = "\033[91m"
8
+ RESET = "\033[0m"
9
+
10
+ warnings.warn(
11
+ f"{RED}The gsap module is deprecated and will be removed in the next major version.{RESET}",
12
+ DeprecationWarning,
13
+ stacklevel=2,
14
+ )
15
+
5
16
  __all__ = [
6
17
  "set_defaults",
7
18
  "from_",
@@ -0,0 +1,4 @@
1
+ from .client_instance_locker import ClientInstanceLocker
2
+
3
+
4
+ __all__ = ["ClientInstanceLocker"]
@@ -0,0 +1,31 @@
1
+ from typing import Callable, TypeVar, Generic
2
+ from nicegui import ui, Client
3
+ from weakref import WeakKeyDictionary
4
+
5
+
6
+ _T = TypeVar("_T")
7
+
8
+
9
+ class ClientInstanceLocker(Generic[_T]):
10
+ def __init__(self, factory: Callable[[], _T]):
11
+ """Creates a new instance locker that creates a new instance for each client.
12
+
13
+ Args:
14
+ factory (Callable[[], _T]): A factory function that creates a new instance.
15
+ """
16
+ self._client_instances: WeakKeyDictionary[Client, _T] = WeakKeyDictionary()
17
+ self._factory = factory
18
+
19
+ def get_object(self):
20
+ if not ui.context.slot_stack:
21
+ return None
22
+
23
+ client = ui.context.client
24
+ if client not in self._client_instances:
25
+ self._client_instances[client] = self._factory()
26
+
27
+ @client.on_disconnect
28
+ def _():
29
+ del self._client_instances[client]
30
+
31
+ return self._client_instances[client]
Binary file
@@ -0,0 +1,6 @@
1
+ import { gsap, Power0, Power1, Power2, Power3, Power4, Linear, Quad, Cubic, Quart, Quint, Strong, Elastic, Back, SteppedEase, Bounce, Sine, Expo, Circ, TweenLite, TimelineLite, TimelineMax } from "./gsap-core.js";
2
+ import { CSSPlugin } from "./CSSPlugin.js";
3
+ var gsapWithCSS = gsap.registerPlugin(CSSPlugin) || gsap,
4
+ // to protect from tree shaking
5
+ TweenMaxWithCSS = gsapWithCSS.core.Tween;
6
+ export { gsapWithCSS as gsap, gsapWithCSS as default, CSSPlugin, TweenMaxWithCSS as TweenMax, TweenLite, TimelineMax, TimelineLite, Power0, Power1, Power2, Power3, Power4, Linear, Quad, Cubic, Quart, Quint, Strong, Elastic, Back, SteppedEase, Bounce, Sine, Expo, Circ };
@@ -5,13 +5,23 @@ function collectMapRegisterTask() {
5
5
 
6
6
  if (typeof window.ex4ngEchartsMapTasks !== "undefined") {
7
7
 
8
- for (const [mapName, src] of window.ex4ngEchartsMapTasks.entries()) {
9
-
8
+ for (const [mapName, opt] of window.ex4ngEchartsMapTasks.entries()) {
9
+ const { src, type: mapDataType, specialAreas } = opt;
10
10
  const registerPromise = new Promise((resolve, reject) => {
11
11
  fetch(src)
12
- .then((response) => response.json())
12
+ .then((response) => {
13
+ if (mapDataType === "genJSON") {
14
+ return response.json();
15
+ }
16
+
17
+ return response.text();
18
+ })
13
19
  .then((data) => {
14
- echarts.registerMap(mapName, data);
20
+ if (mapDataType === "svg") {
21
+ data = { svg: data }
22
+ }
23
+
24
+ echarts.registerMap(mapName, data, specialAreas);
15
25
  resolve();
16
26
  });
17
27
 
@@ -46,10 +56,6 @@ export default {
46
56
  async mounted() {
47
57
  await this.$nextTick(); // wait for Tailwind classes to be applied
48
58
 
49
- if (hasMapOrGeo(this.options)) {
50
- await Promise.all(Array.from(mapRegisterTasks.values()));
51
- }
52
-
53
59
  this.chart = echarts.init(this.$el, this.theme);
54
60
 
55
61
  this.resizeObs = new ResizeObserver(this.chart.resize)
@@ -63,10 +69,14 @@ export default {
63
69
  this.chart.on("finished", createResizeObserver);
64
70
 
65
71
  if (this.options) {
72
+ if (hasMapOrGeo(this.options)) {
73
+ await Promise.all(Array.from(mapRegisterTasks.values()));
74
+ }
66
75
  this.update_chart();
67
76
  } else {
68
77
  const fn = new Function('return ' + this.code)()
69
- fn(this.chart)
78
+ await Promise.all(Array.from(mapRegisterTasks.values()));
79
+ fn(this.chart, echarts)
70
80
  this.$emit("__update_options_from_client", this.chart.getOption())
71
81
  }
72
82
  this.chart.getZr().on("click", (e) => {
@@ -6,7 +6,6 @@ from pathlib import Path
6
6
  from ex4nicegui.utils.signals import (
7
7
  Ref,
8
8
  effect_refreshable,
9
- ReadonlyRef,
10
9
  effect,
11
10
  ref_computed as computed,
12
11
  to_ref,
@@ -17,7 +16,7 @@ SelectMode = Literal["dir", "file"]
17
16
 
18
17
 
19
18
  class LocalFilePickerResult:
20
- def __init__(self, ref: ReadonlyRef[str], open_fn: Callable[..., None]) -> None:
19
+ def __init__(self, ref: Ref[str], open_fn: Callable[..., None]) -> None:
21
20
  self.__open_fn = open_fn
22
21
  self._ref = ref
23
22
 
@@ -6,16 +6,16 @@ from typing import (
6
6
  Optional,
7
7
  )
8
8
  from ex4nicegui.utils.signals import (
9
- ReadonlyRef,
10
9
  is_ref,
11
10
  ref_computed,
12
11
  to_value,
13
- _TMaybeRef as TMaybeRef,
12
+ TMaybeRef,
13
+ TGetterOrReadonlyRef,
14
14
  )
15
- from ex4nicegui.utils.apiEffect import ui_effect
16
15
  from nicegui import ui
17
16
  from .base import BindableUi
18
- from ex4nicegui.reactive.utils import ParameterClassifier, dataframe2col_str
17
+ from ex4nicegui.reactive.services.reactive_service import ParameterClassifier
18
+ from ex4nicegui.reactive.services.pandas_service import dataframe2col_str
19
19
 
20
20
 
21
21
  class AggridBindableUi(BindableUi[ui.aggrid]):
@@ -78,14 +78,14 @@ class AggridBindableUi(BindableUi[ui.aggrid]):
78
78
  options = {"columnDefs": columnDefs, "rowData": rowData}
79
79
  return cls(options, **org_kws)
80
80
 
81
- def bind_prop(self, prop: str, ref_ui: ReadonlyRef[Any]):
81
+ def bind_prop(self, prop: str, ref_ui: TGetterOrReadonlyRef[Any]):
82
82
  if prop == "options":
83
83
  return self.bind_options(ref_ui)
84
84
 
85
85
  return super().bind_prop(prop, ref_ui)
86
86
 
87
- def bind_options(self, ref_ui: ReadonlyRef[List[Dict]]):
88
- @ui_effect
87
+ def bind_options(self, ref_ui: TGetterOrReadonlyRef[List[Dict]]):
88
+ @self._ui_effect
89
89
  def _():
90
90
  ele = self.element
91
91
  data = to_value(ref_ui)
@@ -1,5 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
+ from pathlib import Path
3
4
  from typing import (
4
5
  Any,
5
6
  Callable,
@@ -11,7 +12,7 @@ from typing import (
11
12
  Generic,
12
13
  Union,
13
14
  cast,
14
- Literal,
15
+ overload,
15
16
  )
16
17
 
17
18
  from typing_extensions import Self
@@ -19,21 +20,18 @@ from ex4nicegui.utils.apiEffect import ui_effect
19
20
  import signe
20
21
  from ex4nicegui.utils.signals import (
21
22
  TGetterOrReadonlyRef,
22
- effect,
23
23
  to_value,
24
24
  is_ref,
25
25
  WatchedState,
26
26
  on,
27
27
  )
28
+ from ex4nicegui.utils.clientScope import new_scope
28
29
  from nicegui import Tailwind, ui
29
- from nicegui.elements.mixins.color_elements import (
30
- TextColorElement,
31
- QUASAR_COLORS,
32
- TAILWIND_COLORS,
33
- )
34
30
  from nicegui.elements.mixins.text_element import TextElement
35
31
  from nicegui.elements.mixins.disableable_element import DisableableElement
36
-
32
+ from ex4nicegui.reactive.services.reactive_service import inject_handle_delete
33
+ from ex4nicegui.reactive.scopedStyle import ScopedStyle
34
+ from functools import partial
37
35
 
38
36
  T = TypeVar("T")
39
37
 
@@ -42,21 +40,38 @@ TWidget = TypeVar("TWidget", bound=ui.element)
42
40
 
43
41
  _T_bind_classes_type_dict = Dict[str, TGetterOrReadonlyRef[bool]]
44
42
  _T_bind_classes_type_ref_dict = TGetterOrReadonlyRef[Dict[str, bool]]
45
- _T_bind_classes_type_array = List[TGetterOrReadonlyRef[str]]
43
+ _T_bind_classes_type_single = TGetterOrReadonlyRef[str]
44
+ _T_bind_classes_type_array = List[_T_bind_classes_type_single]
46
45
 
47
46
 
48
47
  _T_bind_classes_type = Union[
49
- _T_bind_classes_type_dict, _T_bind_classes_type_ref_dict, _T_bind_classes_type_array
48
+ _T_bind_classes_type_dict,
49
+ _T_bind_classes_type_ref_dict,
50
+ _T_bind_classes_type_single,
51
+ _T_bind_classes_type_array,
50
52
  ]
51
53
 
52
54
 
53
55
  class BindableUi(Generic[TWidget]):
54
56
  def __init__(self, element: TWidget) -> None:
55
57
  self._element = element
58
+ inject_handle_delete(self.element, self._on_element_delete)
56
59
  self.tailwind = Tailwind(cast(ui.element, self._element))
60
+ self._effect_scope = new_scope()
57
61
 
58
- def _ui_effect(self, fn: Callable):
59
- return ui_effect(fn)
62
+ def _on_element_delete(self):
63
+ self._effect_scope.dispose()
64
+ scope_style = ScopedStyle.get()
65
+ if scope_style:
66
+ scope_style.remove_style(self.element)
67
+
68
+ @property
69
+ def _ui_effect(self):
70
+ return partial(ui_effect, scope=self._effect_scope)
71
+
72
+ @property
73
+ def _ui_signal_on(self):
74
+ return partial(on, scope=self._effect_scope)
60
75
 
61
76
  def props(self, add: Optional[str] = None, *, remove: Optional[str] = None):
62
77
  cast(ui.element, self.element).props(add, remove=remove)
@@ -108,7 +123,7 @@ class BindableUi(Generic[TWidget]):
108
123
 
109
124
  def delete(self) -> None:
110
125
  """Delete the element."""
111
- self.delete()
126
+ self.element.delete()
112
127
 
113
128
  def move(
114
129
  self, target_container: Optional[ui.element] = None, target_index: int = -1
@@ -118,7 +133,7 @@ class BindableUi(Generic[TWidget]):
118
133
  :param target_container: container to move the element to (default: the parent container)
119
134
  :param target_index: index within the target slot (default: append to the end)
120
135
  """
121
- return self.move(target_container, target_index)
136
+ return self.element.move(target_container, target_index)
122
137
 
123
138
  def remove(self, element: Union[ui.element, int]) -> None:
124
139
  """Remove a child element.
@@ -128,6 +143,16 @@ class BindableUi(Generic[TWidget]):
128
143
  return self.element.remove(element)
129
144
 
130
145
  def bind_prop(self, prop: str, ref_ui: TGetterOrReadonlyRef[Any]):
146
+ """data binding is manipulating an element's property
147
+
148
+ @see - https://github.com/CrystalWindSnake/ex4nicegui/blob/main/README.en.md#bind_prop
149
+ @中文文档 - https://gitee.com/carson_add/ex4nicegui/tree/main/#bind_prop
150
+
151
+ Args:
152
+ prop (str): property name
153
+ ref_ui (TGetterOrReadonlyRef[Any]): a reference to the value to bind to
154
+
155
+ """
131
156
  if prop == "visible":
132
157
  return self.bind_visible(ref_ui)
133
158
 
@@ -182,6 +207,22 @@ class BindableUi(Generic[TWidget]):
182
207
  def clear(self) -> None:
183
208
  cast(ui.element, self.element).clear()
184
209
 
210
+ @overload
211
+ def bind_classes(self, classes: Dict[str, TGetterOrReadonlyRef[bool]]):
212
+ ...
213
+
214
+ @overload
215
+ def bind_classes(self, classes: TGetterOrReadonlyRef[Dict[str, bool]]):
216
+ ...
217
+
218
+ @overload
219
+ def bind_classes(self, classes: List[TGetterOrReadonlyRef[str]]):
220
+ ...
221
+
222
+ @overload
223
+ def bind_classes(self, classes: TGetterOrReadonlyRef[str]):
224
+ ...
225
+
185
226
  def bind_classes(self, classes: _T_bind_classes_type):
186
227
  """data binding is manipulating an element's class list
187
228
 
@@ -189,7 +230,37 @@ class BindableUi(Generic[TWidget]):
189
230
  @中文文档 - https://gitee.com/carson_add/ex4nicegui/tree/main/#%E7%BB%91%E5%AE%9A%E7%B1%BB%E5%90%8D
190
231
 
191
232
  Args:
192
- classes (_T_bind_classes_type):
233
+ classes (_T_bind_classes_type): dict of refs | ref to dict | str ref | list of refs
234
+
235
+ ## usage
236
+
237
+ bind class names with dict,value is bool ref, for example:
238
+
239
+ ```python
240
+ bg_color = to_ref(True)
241
+ has_error = to_ref(False)
242
+
243
+ rxui.label('Hello').bind_classes({'bg-blue':bg_color, 'text-red':has_error})
244
+ ```
245
+
246
+ bind list of class names with ref
247
+
248
+ ```python
249
+ color = to_ref('red')
250
+ bg_color = lambda: f"bg-{color.value}"
251
+
252
+ rxui.label('Hello').bind_classes([bg_color])
253
+ ```
254
+
255
+ bind single class name with ref
256
+
257
+ ```python
258
+ color = to_ref('red')
259
+ bg_color = lambda: f"bg-{color.value}"
260
+
261
+ rxui.label('Hello').bind_classes(bg_color)
262
+ ```
263
+
193
264
  """
194
265
  if isinstance(classes, dict):
195
266
  for name, ref_obj in classes.items():
@@ -203,24 +274,33 @@ class BindableUi(Generic[TWidget]):
203
274
 
204
275
  elif is_ref(classes) or isinstance(classes, Callable):
205
276
  ref_obj = to_value(classes) # type: ignore
206
- assert isinstance(ref_obj, dict)
207
277
 
208
- @effect
209
- def _():
210
- for name, value in cast(Dict, to_value(classes)).items(): # type: ignore
211
- if value:
212
- self.classes(add=name)
213
- else:
214
- self.classes(remove=name)
278
+ if isinstance(ref_obj, dict):
279
+
280
+ @self._ui_effect
281
+ def _():
282
+ for name, value in cast(Dict, to_value(classes)).items(): # type: ignore
283
+ if value:
284
+ self.classes(add=name)
285
+ else:
286
+ self.classes(remove=name)
287
+ else:
288
+ self._bind_single_class(cast(_T_bind_classes_type_single, classes))
289
+
215
290
  elif isinstance(classes, list):
216
291
  for ref_name in classes:
217
- if is_ref(ref_name) or isinstance(ref_name, Callable):
292
+ self._bind_single_class(ref_name)
293
+
294
+ return self
218
295
 
219
- @on(ref_name)
220
- def _(state: WatchedState):
221
- self.classes(add=state.current, remove=state.previous)
222
- else:
223
- self.classes(ref_name) # type: ignore
296
+ def _bind_single_class(self, class_name: _T_bind_classes_type_single):
297
+ if is_ref(class_name) or isinstance(class_name, Callable):
298
+
299
+ @on(class_name)
300
+ def _(state: WatchedState):
301
+ self.classes(add=state.current, remove=state.previous)
302
+ else:
303
+ self.classes(class_name) # type: ignore
224
304
 
225
305
  return self
226
306
 
@@ -231,7 +311,21 @@ class BindableUi(Generic[TWidget]):
231
311
  @中文文档 - https://gitee.com/carson_add/ex4nicegui/tree/main/#bind-style
232
312
 
233
313
  Args:
234
- style (Dict[str, Union[ReadonlyRef[str], Ref[str]]]): _description_
314
+ style (Dict[str, Union[ReadonlyRef[str], Ref[str]]]): dict of style name and ref value
315
+
316
+
317
+ ## usage
318
+ ```python
319
+ bg_color = to_ref("blue")
320
+ text_color = to_ref("red")
321
+
322
+ rxui.label("test").bind_style(
323
+ {
324
+ "background-color": bg_color,
325
+ "color": text_color,
326
+ }
327
+ )
328
+ ```
235
329
  """
236
330
  if isinstance(style, dict):
237
331
  for name, ref_obj in style.items():
@@ -244,26 +338,88 @@ class BindableUi(Generic[TWidget]):
244
338
 
245
339
  return self
246
340
 
341
+ def scoped_style(self, selector: str, style: Union[str, Path]):
342
+ """add scoped style to the element
343
+
344
+ @see - https://github.com/CrystalWindSnake/ex4nicegui/blob/main/README.en.md#scoped_style
345
+ @中文文档 - https://gitee.com/carson_add/ex4nicegui/tree/main/#scoped_style
346
+
347
+ Args:
348
+ selector (str): css selector
349
+ style (Union[str, Path]): path to css file or inline style string
350
+
351
+ ## usage
352
+ ```python
353
+ # all children of the element will have red outline, excluding itself
354
+ with rxui.row().scoped_style("*", "outline: 1px solid red;") as row:
355
+ ui.label("Hello")
356
+ ui.label("World")
357
+
358
+ # all children of the element will have red outline, including the element itself
359
+ with rxui.row().scoped_style(":self *", "outline: 1px solid red;") as row:
360
+ ui.label("Hello")
361
+ ui.label("World")
362
+
363
+ # all children of the element will have red outline when element is hovered
364
+ with rxui.row().scoped_style(":hover *", "outline: 1px solid red;") as row:
365
+ ui.label("Hello")
366
+ ui.label("World")
367
+
368
+ # all children of the element and itself will have red outline when element is hovered
369
+ with rxui.row().scoped_style(":self:hover *", "outline: 1px solid red;") as row:
370
+ ui.label("Hello")
371
+ ui.label("World")
372
+ ```
373
+ """
374
+
375
+ is_css_file = isinstance(style, Path)
376
+
377
+ if is_css_file:
378
+ style = style.read_text(encoding="utf-8")
379
+
380
+ id = f"c{self.element.id}"
381
+ selector_with_self = _utils._parent_id_with_selector(id, selector, is_css_file)
382
+ css = ""
383
+ if is_css_file:
384
+ css = f"{selector_with_self} {style}"
385
+ else:
386
+ css = f"{selector_with_self}{{{style}}}"
387
+
388
+ scope_style = ScopedStyle.get()
389
+ assert scope_style, "can not find scope style"
390
+ scope_style.create_style(self.element, css)
391
+
392
+ return self
393
+
247
394
  def update(self):
248
395
  """Update the element on the client side."""
249
396
  self.element.update()
250
397
 
251
398
 
252
- # class SingleValueBindableUi(BindableUi[TWidget], Generic[T, TWidget]):
253
- # def __init__(self, value: TMaybeRef[T], element: TWidget) -> None:
254
- # super().__init__(element)
255
- # self._ref = to_ref(value)
399
+ class _utils:
400
+ @staticmethod
401
+ def _parent_id_with_selector(
402
+ parent_id: str, selector: str, is_css_file=False
403
+ ) -> str:
404
+ selector_with_self = f"#{parent_id}"
405
+
406
+ selector = selector.strip()
407
+ if (not selector) and (not is_css_file):
408
+ selector = "* "
409
+
410
+ if selector.startswith(":self"):
411
+ selector = selector[5:].lstrip()
412
+ parent_selector = f"#{parent_id}"
413
+ if selector.startswith(":"):
414
+ parent_selector = f"{parent_selector}{selector.split()[0]}"
256
415
 
257
- # @property
258
- # def value(self) -> T:
259
- # return self._ref.value # type: ignore
416
+ selector_with_self = f"{parent_selector},{selector_with_self}"
260
417
 
261
- # def bind_ref(self, ref: TRef[T]):
262
- # @effect
263
- # def _():
264
- # ref.value = self._ref.value # type: ignore
418
+ if not selector.startswith(":"):
419
+ selector_with_self = selector_with_self + " "
265
420
 
266
- # return self
421
+ selector_with_self = selector_with_self + selector
422
+ return selector_with_self
267
423
 
268
424
 
269
425
  _T_DisableableBinder = TypeVar("_T_DisableableBinder", bound=DisableableElement)
@@ -296,47 +452,3 @@ class DisableableMixin(Protocol):
296
452
 
297
453
 
298
454
  DisableableBindableUi = DisableableMixin
299
-
300
-
301
- _color_sys_type = Literal["QUASAR", "TAILWIND", "STYLE"]
302
- _color_attr_name = "data-ex4ng-color"
303
-
304
-
305
- def _bind_color(bindable_ui: BindableUi, ref_ui: TGetterOrReadonlyRef):
306
- @effect
307
- def _():
308
- ele = cast(TextColorElement, bindable_ui.element)
309
- color = to_value(ref_ui)
310
-
311
- # get exists color
312
- # e.g 'QUASAR:red'
313
- pre_color = ele._props.get(_color_attr_name) # type: str | None
314
- if pre_color:
315
- color_sys, value = pre_color.split(":") # type: ignore
316
- color_sys: _color_sys_type
317
-
318
- if color_sys == "QUASAR":
319
- del ele._props[ele.TEXT_COLOR_PROP]
320
- elif color_sys == "TAILWIND":
321
- ele.classes(remove=value)
322
- else:
323
- del ele._style["color"]
324
-
325
- cur_sys: _color_sys_type = "STYLE"
326
- cur_color = color
327
-
328
- if color in QUASAR_COLORS:
329
- ele._props[ele.TEXT_COLOR_PROP] = color
330
- cur_sys = "QUASAR"
331
- elif color in TAILWIND_COLORS:
332
- cur_color = f"text-{color}"
333
- ele.classes(replace=cur_color)
334
- cur_sys = "TAILWIND"
335
- elif color is not None:
336
- ele._style["color"] = color
337
-
338
- ele._props[_color_attr_name] = f"{cur_sys}:{color}"
339
-
340
- ele.update()
341
-
342
- return bindable_ui
@@ -3,9 +3,9 @@ from typing import (
3
3
  Callable,
4
4
  Optional,
5
5
  )
6
- from ex4nicegui.reactive.utils import ParameterClassifier
6
+ from ex4nicegui.reactive.services.reactive_service import ParameterClassifier
7
7
  from ex4nicegui.utils.signals import (
8
- ReadonlyRef,
8
+ TGetterOrReadonlyRef,
9
9
  _TMaybeRef as TMaybeRef,
10
10
  to_value,
11
11
  )
@@ -33,7 +33,7 @@ class ButtonBindableUi(BindableUi[ui.button], DisableableMixin):
33
33
  for key, value in pc.get_bindings().items():
34
34
  self.bind_prop(key, value) # type: ignore
35
35
 
36
- def bind_prop(self, prop: str, ref_ui: ReadonlyRef):
36
+ def bind_prop(self, prop: str, ref_ui: TGetterOrReadonlyRef):
37
37
  if prop == "text":
38
38
  return self.bind_text(ref_ui)
39
39
  if prop == "icon":
@@ -41,7 +41,7 @@ class ButtonBindableUi(BindableUi[ui.button], DisableableMixin):
41
41
 
42
42
  return super().bind_prop(prop, ref_ui)
43
43
 
44
- def bind_text(self, ref_ui: ReadonlyRef):
44
+ def bind_text(self, ref_ui: TGetterOrReadonlyRef):
45
45
  @self._ui_effect
46
46
  def _():
47
47
  ele = self.element
@@ -50,7 +50,7 @@ class ButtonBindableUi(BindableUi[ui.button], DisableableMixin):
50
50
 
51
51
  return self
52
52
 
53
- def bind_icon(self, ref_ui: ReadonlyRef):
53
+ def bind_icon(self, ref_ui: TGetterOrReadonlyRef[str]):
54
54
  @self._ui_effect
55
55
  def _():
56
56
  ele = self.element