ex4nicegui 0.6.8__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 (58) hide show
  1. ex4nicegui/__init__.py +2 -0
  2. ex4nicegui/bi/dataSourceFacade.py +20 -20
  3. ex4nicegui/bi/index.py +20 -23
  4. ex4nicegui/helper/client_instance_locker.py +4 -4
  5. ex4nicegui/reactive/EChartsComponent/ECharts.py +9 -8
  6. ex4nicegui/reactive/__init__.py +12 -0
  7. ex4nicegui/reactive/base.py +433 -0
  8. ex4nicegui/reactive/deferredTask.py +3 -2
  9. ex4nicegui/reactive/mixins/backgroundColor.py +41 -0
  10. ex4nicegui/reactive/mixins/disableable.py +44 -0
  11. ex4nicegui/reactive/mixins/textColor.py +66 -0
  12. ex4nicegui/reactive/officials/aggrid.py +5 -5
  13. ex4nicegui/reactive/officials/base.py +4 -453
  14. ex4nicegui/reactive/officials/button.py +43 -10
  15. ex4nicegui/reactive/officials/checkbox.py +5 -5
  16. ex4nicegui/reactive/officials/chip.py +102 -0
  17. ex4nicegui/reactive/officials/circular_progress.py +9 -7
  18. ex4nicegui/reactive/officials/color_picker.py +7 -7
  19. ex4nicegui/reactive/officials/column.py +5 -5
  20. ex4nicegui/reactive/officials/date.py +5 -5
  21. ex4nicegui/reactive/officials/dialog.py +49 -0
  22. ex4nicegui/reactive/officials/echarts.py +49 -51
  23. ex4nicegui/reactive/officials/expansion.py +5 -5
  24. ex4nicegui/reactive/officials/grid.py +3 -2
  25. ex4nicegui/reactive/officials/icon.py +8 -11
  26. ex4nicegui/reactive/officials/image.py +5 -5
  27. ex4nicegui/reactive/officials/input.py +8 -8
  28. ex4nicegui/reactive/officials/knob.py +9 -5
  29. ex4nicegui/reactive/officials/label.py +8 -15
  30. ex4nicegui/reactive/officials/linear_progress.py +8 -11
  31. ex4nicegui/reactive/officials/number.py +9 -9
  32. ex4nicegui/reactive/officials/radio.py +8 -8
  33. ex4nicegui/reactive/officials/row.py +5 -5
  34. ex4nicegui/reactive/officials/select.py +9 -8
  35. ex4nicegui/reactive/officials/slider.py +6 -6
  36. ex4nicegui/reactive/officials/switch.py +5 -5
  37. ex4nicegui/reactive/officials/tab.py +0 -12
  38. ex4nicegui/reactive/officials/tab_panels.py +113 -7
  39. ex4nicegui/reactive/officials/table.py +13 -13
  40. ex4nicegui/reactive/officials/tabs.py +5 -5
  41. ex4nicegui/reactive/officials/textarea.py +5 -5
  42. ex4nicegui/reactive/officials/tooltip.py +40 -0
  43. ex4nicegui/reactive/q_pagination.py +5 -5
  44. ex4nicegui/reactive/scopedStyle.py +4 -1
  45. ex4nicegui/reactive/systems/color_system.py +132 -0
  46. ex4nicegui/reactive/vfor.js +14 -4
  47. ex4nicegui/reactive/vfor.py +128 -58
  48. ex4nicegui/reactive/view_model.py +160 -0
  49. ex4nicegui/reactive/vmodel.py +42 -12
  50. ex4nicegui/utils/apiEffect.py +5 -1
  51. ex4nicegui/utils/effect.py +3 -2
  52. ex4nicegui/utils/scheduler.py +20 -4
  53. ex4nicegui/utils/signals.py +23 -21
  54. {ex4nicegui-0.6.8.dist-info → ex4nicegui-0.7.0.dist-info}/METADATA +247 -48
  55. {ex4nicegui-0.6.8.dist-info → ex4nicegui-0.7.0.dist-info}/RECORD +57 -50
  56. ex4nicegui/reactive/services/color_service.py +0 -56
  57. {ex4nicegui-0.6.8.dist-info → ex4nicegui-0.7.0.dist-info}/LICENSE +0 -0
  58. {ex4nicegui-0.6.8.dist-info → ex4nicegui-0.7.0.dist-info}/WHEEL +0 -0
ex4nicegui/__init__.py CHANGED
@@ -6,6 +6,7 @@ from ex4nicegui.utils.types import (
6
6
  ReadonlyRef,
7
7
  TGetterOrReadonlyRef,
8
8
  )
9
+ from ex4nicegui.utils.scheduler import next_tick
9
10
  from ex4nicegui.utils.signals import (
10
11
  effect,
11
12
  effect_refreshable,
@@ -49,5 +50,6 @@ __all__ = [
49
50
  "to_raw",
50
51
  "is_setter_ref",
51
52
  "new_scope",
53
+ "next_tick",
52
54
  "__version__",
53
55
  ]
@@ -228,26 +228,26 @@ class DataSourceFacade(Generic[_TData]):
228
228
 
229
229
  Support pyecharts
230
230
 
231
- ```py
232
- import pandas as pd
233
- from ex4nicegui import bi
234
- from pyecharts.charts import Bar
235
-
236
- df = pd.DataFrame({"name": list("abcdc"), "value": range(5)})
237
- ds = bi.data_source(df)
238
-
239
- @ds.ui_echarts
240
- def bar(data: pd.DataFrame):
241
- c = (
242
- Bar()
243
- .add_xaxis(data["name"].tolist())
244
- .add_yaxis("value", data["value"].tolist())
245
- )
246
-
247
- return c
248
-
249
- bar.classes("h-[20rem]")
250
- ```
231
+ .. code-block:: python
232
+ import pandas as pd
233
+ from ex4nicegui import bi
234
+ from pyecharts.charts import Bar
235
+
236
+ df = pd.DataFrame({"name": list("abcdc"), "value": range(5)})
237
+ ds = bi.data_source(df)
238
+
239
+ @ds.ui_echarts
240
+ def bar(data: pd.DataFrame):
241
+ c = (
242
+ Bar()
243
+ .add_xaxis(data["name"].tolist())
244
+ .add_yaxis("value", data["value"].tolist())
245
+ )
246
+
247
+ return c
248
+
249
+ bar.classes("h-[20rem]")
250
+
251
251
 
252
252
  """
253
253
  return ui_echarts(self, fn)
ex4nicegui/bi/index.py CHANGED
@@ -24,43 +24,40 @@ def data_source(data: Union[Callable[..., _TData], _TData]) -> DataSourceFacade[
24
24
 
25
25
  ## Examples
26
26
 
27
- ---
28
-
29
27
  pandas dataframe source:
30
- ```python
31
- df = pd.DataFrame({"name": list("abcdc"), "value": range(5)})
32
- ds = bi.data_source(df)
33
- ```
28
+
29
+ .. code-block:: python
30
+ df = pd.DataFrame({"name": list("abcdc"), "value": range(5)})
31
+ ds = bi.data_source(df)
32
+
34
33
 
35
34
  ---
36
35
 
37
36
  Link multiple data sources
38
37
 
39
- ---
40
- ```python
41
- df = pd.DataFrame({"name": list("abcdc"), "value": range(5)})
42
- ds = bi.data_source(df)
38
+ .. code-block:: python
39
+ df = pd.DataFrame({"name": list("abcdc"), "value": range(5)})
40
+ ds = bi.data_source(df)
43
41
 
44
- @bi.data_source
45
- def ds_other():
46
- # ds.filtered_data is DataFrame after filtering
47
- where = ds.filtered_data[''].isin(['b','c','d'])
48
- return ds.filtered_data[where]
42
+ @bi.data_source
43
+ def ds_other():
44
+ # ds.filtered_data is DataFrame after filtering
45
+ where = ds.filtered_data[''].isin(['b','c','d'])
46
+ return ds.filtered_data[where]
49
47
 
50
- ```
51
48
 
52
49
  ---
53
50
 
54
51
  Now, when `ds` changes, it will trigger changes to `ds_other` and thus drive the related interface components to change.
55
52
 
56
- ```python
57
- # select box of data source 'ds'
58
- # it change will trigger changes to table
59
- ds.ui_select('name')
53
+ .. code-block:: python
54
+ # select box of data source 'ds'
55
+ # it change will trigger changes to table
56
+ ds.ui_select('name')
57
+
58
+ # table of data 'ds_other'
59
+ ds_other.ui_aggrid()
60
60
 
61
- # table of data 'ds_other'
62
- ds_other.ui_aggrid()
63
- ```
64
61
 
65
62
 
66
63
  """
@@ -1,4 +1,4 @@
1
- from typing import Callable, TypeVar, Generic
1
+ from typing import Callable, Optional, TypeVar, Generic
2
2
  from nicegui import ui, Client
3
3
  from weakref import WeakKeyDictionary
4
4
 
@@ -16,16 +16,16 @@ class ClientInstanceLocker(Generic[_T]):
16
16
  self._client_instances: WeakKeyDictionary[Client, _T] = WeakKeyDictionary()
17
17
  self._factory = factory
18
18
 
19
- def get_object(self):
19
+ def get_object(self, client: Optional[Client] = None):
20
20
  if not ui.context.slot_stack:
21
21
  return None
22
22
 
23
- client = ui.context.client
23
+ client = client or ui.context.client
24
24
  if client not in self._client_instances:
25
25
  self._client_instances[client] = self._factory()
26
26
 
27
27
  @client.on_disconnect
28
28
  def _():
29
- del self._client_instances[client]
29
+ self._client_instances.pop(client, None)
30
30
 
31
31
  return self._client_instances[client]
@@ -64,14 +64,15 @@ class echarts(Element, component="ECharts.js", libraries=libraries): # type: ig
64
64
  Args:
65
65
  options (dict): chart setting options dict
66
66
  opts (Optional[dict], optional): update options. Defaults to None.
67
- ```python
68
- {
69
- 'notMerge':False,
70
- 'lazyUpdate':False,
71
- 'silent':False,
72
- 'replaceMerge': None,
73
- }
74
- ```
67
+
68
+ .. code-block:: python
69
+ {
70
+ 'notMerge':False,
71
+ 'lazyUpdate':False,
72
+ 'silent':False,
73
+ 'replaceMerge': None,
74
+ }
75
+
75
76
  [open echarts setOption docs](https://echarts.apache.org/zh/api.html#echartsInstance.setOption)
76
77
 
77
78
  """
@@ -51,7 +51,10 @@ from .officials.tab import TabBindableUi as tab
51
51
  from .officials.tab_panels import TabPanelsBindableUi as tab_panels
52
52
  from .officials.tab_panel import TabPanelBindableUi as tab_panel
53
53
  from .officials.element import ElementBindableUi as element
54
+ from .officials.tab_panels import LazyTabPanelsBindableUi as lazy_tab_panels
54
55
  from .q_pagination import PaginationBindableUi as q_pagination
56
+ from .officials.chip import ChipBindableUi as chip
57
+ from .officials.tooltip import TooltipBindableUi as tooltip
55
58
 
56
59
  from .local_file_picker import local_file_picker
57
60
  from .UseDraggable.UseDraggable import use_draggable
@@ -61,8 +64,10 @@ from .usePagination import PaginationRef as use_pagination
61
64
  from .dropZone.dropZone import use_drag_zone
62
65
  from .fileWatcher import FilesWatcher
63
66
  from .mermaid.mermaid import Mermaid as mermaid
67
+ from .officials.dialog import DialogBindableUi as dialog
64
68
  from .vfor import vfor, VforStore
65
69
  from .vmodel import vmodel
70
+ from .view_model import ViewModel, var, cached_var
66
71
 
67
72
  pagination = q_pagination
68
73
 
@@ -70,6 +75,7 @@ pagination = q_pagination
70
75
  __all__ = [
71
76
  "element",
72
77
  "tab_panels",
78
+ "lazy_tab_panels",
73
79
  "tab_panel",
74
80
  "tabs",
75
81
  "tab",
@@ -85,6 +91,9 @@ __all__ = [
85
91
  "vfor",
86
92
  "VforStore",
87
93
  "vmodel",
94
+ "ViewModel",
95
+ "var",
96
+ "cached_var",
88
97
  "html",
89
98
  "aggrid",
90
99
  "button",
@@ -120,4 +129,7 @@ __all__ = [
120
129
  "q_pagination",
121
130
  "pagination",
122
131
  "mermaid",
132
+ "chip",
133
+ "dialog",
134
+ "tooltip",
123
135
  ]
@@ -0,0 +1,433 @@
1
+ from __future__ import annotations
2
+
3
+ from pathlib import Path
4
+ from typing import (
5
+ Any,
6
+ Callable,
7
+ Dict,
8
+ List,
9
+ Optional,
10
+ TypeVar,
11
+ Generic,
12
+ Union,
13
+ cast,
14
+ overload,
15
+ )
16
+
17
+ from typing_extensions import Self
18
+ from ex4nicegui.utils.apiEffect import ui_effect
19
+ from ex4nicegui.utils.signals import (
20
+ TGetterOrReadonlyRef,
21
+ to_value,
22
+ is_ref,
23
+ WatchedState,
24
+ on,
25
+ )
26
+ from ex4nicegui.utils.clientScope import new_scope
27
+ from ex4nicegui.utils.types import _TMaybeRef as TMaybeRef
28
+ from nicegui import Tailwind, ui
29
+ from nicegui.elements.mixins.text_element import TextElement
30
+ from ex4nicegui.reactive.services.reactive_service import inject_handle_delete
31
+ from ex4nicegui.reactive.scopedStyle import ScopedStyle
32
+ from ex4nicegui.reactive.mixins.disableable import DisableableMixin
33
+ from functools import partial
34
+
35
+ T = TypeVar("T")
36
+
37
+
38
+ TWidget = TypeVar("TWidget", bound=ui.element)
39
+
40
+ _T_bind_classes_type_dict = Dict[str, TGetterOrReadonlyRef[bool]]
41
+ _T_bind_classes_type_ref_dict = TGetterOrReadonlyRef[Dict[str, bool]]
42
+ _T_bind_classes_type_single = TGetterOrReadonlyRef[str]
43
+ _T_bind_classes_type_array = List[_T_bind_classes_type_single]
44
+
45
+
46
+ _T_bind_classes_type = Union[
47
+ _T_bind_classes_type_dict,
48
+ _T_bind_classes_type_ref_dict,
49
+ _T_bind_classes_type_single,
50
+ _T_bind_classes_type_array,
51
+ ]
52
+
53
+
54
+ class BindableUi(Generic[TWidget]):
55
+ def __init__(self, element: TWidget) -> None:
56
+ self._element = element
57
+ inject_handle_delete(self.element, self._on_element_delete)
58
+ self.tailwind = Tailwind(cast(ui.element, self._element))
59
+ self._effect_scope = new_scope()
60
+ self.__used_scope_style = False
61
+
62
+ def _on_element_delete(self):
63
+ self._effect_scope.dispose()
64
+
65
+ if self.__used_scope_style:
66
+ scope_style = ScopedStyle.get()
67
+ if scope_style:
68
+ scope_style.remove_style(self.element)
69
+
70
+ @property
71
+ def _ui_effect(self):
72
+ return partial(ui_effect, scope=self._effect_scope)
73
+
74
+ @property
75
+ def _ui_signal_on(self):
76
+ return partial(on, scope=self._effect_scope)
77
+
78
+ def props(self, add: Optional[str] = None, *, remove: Optional[str] = None):
79
+ cast(ui.element, self.element).props(add, remove=remove)
80
+ return self
81
+
82
+ def classes(
83
+ self,
84
+ add: Optional[str] = None,
85
+ *,
86
+ remove: Optional[str] = None,
87
+ replace: Optional[str] = None,
88
+ ):
89
+ cast(ui.element, self.element).classes(add, remove=remove, replace=replace)
90
+ return self
91
+
92
+ def style(
93
+ self,
94
+ add: Optional[str] = None,
95
+ *,
96
+ remove: Optional[str] = None,
97
+ replace: Optional[str] = None,
98
+ ):
99
+ cast(ui.element, self.element).style(add, remove=remove, replace=replace)
100
+ return self
101
+
102
+ def __enter__(self) -> Self:
103
+ self.element.__enter__()
104
+ return self
105
+
106
+ def __exit__(self, *_):
107
+ self.element.default_slot.__exit__(*_)
108
+
109
+ def tooltip(self, text: TMaybeRef[str]) -> Self:
110
+ from ex4nicegui.reactive.officials.tooltip import TooltipBindableUi
111
+
112
+ with self:
113
+ TooltipBindableUi(text)
114
+
115
+ return self
116
+
117
+ def add_slot(self, name: str, template: Optional[str] = None):
118
+ """Add a slot to the element.
119
+
120
+ :param name: name of the slot
121
+ :param template: Vue template of the slot
122
+ :return: the slot
123
+ """
124
+ return cast(ui.element, self.element).add_slot(name, template)
125
+
126
+ @property
127
+ def element(self):
128
+ return self._element
129
+
130
+ def delete(self) -> None:
131
+ """Delete the element."""
132
+ self.element.delete()
133
+
134
+ def move(
135
+ self, target_container: Optional[ui.element] = None, target_index: int = -1
136
+ ):
137
+ """Move the element to another container.
138
+
139
+ :param target_container: container to move the element to (default: the parent container)
140
+ :param target_index: index within the target slot (default: append to the end)
141
+ """
142
+ return self.element.move(target_container, target_index)
143
+
144
+ def remove(self, element: Union[ui.element, int]) -> None:
145
+ """Remove a child element.
146
+
147
+ :param element: either the element instance or its ID
148
+ """
149
+ return self.element.remove(element)
150
+
151
+ def bind_prop(self, prop: str, value: TGetterOrReadonlyRef[Any]):
152
+ """data binding is manipulating an element's property
153
+
154
+ @see - https://github.com/CrystalWindSnake/ex4nicegui/blob/main/README.en.md#bind_prop
155
+ @中文文档 - https://gitee.com/carson_add/ex4nicegui/tree/main/#bind_prop
156
+
157
+ Args:
158
+ prop (str): property name
159
+ value (TGetterOrReadonlyRef[Any]): a reference to the value to bind to
160
+
161
+ """
162
+ if prop == "visible":
163
+ return self.bind_visible(value)
164
+
165
+ if prop == "text" and isinstance(self.element, TextElement):
166
+
167
+ @self._ui_effect
168
+ def _():
169
+ cast(TextElement, self.element).set_text(to_value(value))
170
+ self.element.update()
171
+
172
+ @self._ui_effect
173
+ def _():
174
+ element = cast(ui.element, self.element)
175
+ element._props[prop] = to_value(value)
176
+ element.update()
177
+
178
+ return self
179
+
180
+ def bind_visible(self, value: TGetterOrReadonlyRef[bool]):
181
+ @self._ui_effect
182
+ def _():
183
+ element = cast(ui.element, self.element)
184
+ element.set_visibility(to_value(value))
185
+
186
+ return self
187
+
188
+ def bind_not_visible(self, value: TGetterOrReadonlyRef[bool]):
189
+ return self.bind_visible(lambda: not to_value(value))
190
+
191
+ def on(
192
+ self,
193
+ type: str,
194
+ handler: Optional[Callable[..., Any]] = None,
195
+ args: Optional[List[str]] = None,
196
+ *,
197
+ throttle: float = 0.0,
198
+ leading_events: bool = True,
199
+ trailing_events: bool = True,
200
+ ):
201
+ ele = cast(ui.element, self.element)
202
+ ele.on(
203
+ type,
204
+ handler,
205
+ args,
206
+ throttle=throttle,
207
+ leading_events=leading_events,
208
+ trailing_events=trailing_events,
209
+ )
210
+
211
+ return self
212
+
213
+ def clear(self) -> None:
214
+ cast(ui.element, self.element).clear()
215
+
216
+ @overload
217
+ def bind_classes(self, classes: Dict[str, TGetterOrReadonlyRef[bool]]) -> Self:
218
+ ...
219
+
220
+ @overload
221
+ def bind_classes(self, classes: TGetterOrReadonlyRef[Dict[str, bool]]) -> Self:
222
+ ...
223
+
224
+ @overload
225
+ def bind_classes(self, classes: List[TGetterOrReadonlyRef[str]]) -> Self:
226
+ ...
227
+
228
+ @overload
229
+ def bind_classes(self, classes: TGetterOrReadonlyRef[str]) -> Self:
230
+ ...
231
+
232
+ def bind_classes(self, classes: _T_bind_classes_type) -> Self:
233
+ """data binding is manipulating an element's class list
234
+
235
+ @see - https://github.com/CrystalWindSnake/ex4nicegui/blob/main/README.en.md#bind-class-names
236
+ @中文文档 - https://gitee.com/carson_add/ex4nicegui/tree/main/#%E7%BB%91%E5%AE%9A%E7%B1%BB%E5%90%8D
237
+
238
+ Args:
239
+ classes (_T_bind_classes_type): dict of refs | ref to dict | str ref | list of refs
240
+
241
+ ## usage
242
+
243
+ bind class names with dict,value is bool ref, for example:
244
+
245
+ .. code-block:: python
246
+ bg_color = to_ref(True)
247
+ has_error = to_ref(False)
248
+
249
+ rxui.label('Hello').bind_classes({'bg-blue':bg_color, 'text-red':has_error})
250
+
251
+
252
+ bind list of class names with ref
253
+
254
+ .. code-block:: python
255
+ color = to_ref('red')
256
+ bg_color = lambda: f"bg-{color.value}"
257
+
258
+ rxui.label('Hello').bind_classes([bg_color])
259
+
260
+
261
+ bind single class name with ref
262
+
263
+ .. code-block:: python
264
+ color = to_ref('red')
265
+ bg_color = lambda: f"bg-{color.value}"
266
+
267
+ rxui.label('Hello').bind_classes(bg_color)
268
+
269
+
270
+ """
271
+ if isinstance(classes, dict):
272
+ for name, ref_obj in classes.items():
273
+
274
+ @self._ui_effect
275
+ def _(name=name, ref_obj=ref_obj):
276
+ if to_value(ref_obj):
277
+ self.classes(add=name)
278
+ else:
279
+ self.classes(remove=name)
280
+
281
+ elif is_ref(classes) or isinstance(classes, Callable):
282
+ ref_obj = to_value(classes) # type: ignore
283
+
284
+ if isinstance(ref_obj, dict):
285
+
286
+ @self._ui_effect
287
+ def _():
288
+ for name, value in cast(Dict, to_value(classes)).items(): # type: ignore
289
+ if value:
290
+ self.classes(add=name)
291
+ else:
292
+ self.classes(remove=name)
293
+ else:
294
+ self._bind_single_class(cast(_T_bind_classes_type_single, classes))
295
+
296
+ elif isinstance(classes, list):
297
+ for ref_name in classes:
298
+ self._bind_single_class(ref_name)
299
+
300
+ return self
301
+
302
+ def _bind_single_class(self, class_name: _T_bind_classes_type_single):
303
+ if is_ref(class_name) or isinstance(class_name, Callable):
304
+
305
+ @on(class_name)
306
+ def _(state: WatchedState):
307
+ self.classes(add=state.current, remove=state.previous)
308
+ else:
309
+ self.classes(class_name) # type: ignore
310
+
311
+ return self
312
+
313
+ def bind_style(self, style: Dict[str, TGetterOrReadonlyRef[Any]]):
314
+ """data binding is manipulating an element's style
315
+
316
+ @see - https://github.com/CrystalWindSnake/ex4nicegui/blob/main/README.en.md#bind-style
317
+ @中文文档 - https://gitee.com/carson_add/ex4nicegui/tree/main/#bind-style
318
+
319
+ Args:
320
+ style (Dict[str, Union[ReadonlyRef[str], Ref[str]]]): dict of style name and ref value
321
+
322
+
323
+ ## usage
324
+
325
+ .. code-block:: python
326
+ bg_color = to_ref("blue")
327
+ text_color = to_ref("red")
328
+
329
+ rxui.label("test").bind_style(
330
+ {
331
+ "background-color": bg_color,
332
+ "color": text_color,
333
+ }
334
+ )
335
+
336
+ """
337
+ if isinstance(style, dict):
338
+ for name, ref_obj in style.items():
339
+ if is_ref(ref_obj) or isinstance(ref_obj, Callable):
340
+
341
+ @self._ui_effect
342
+ def _(name=name, ref_obj=ref_obj):
343
+ self.element._style[name] = str(to_value(ref_obj))
344
+ self.element.update()
345
+
346
+ return self
347
+
348
+ def scoped_style(self, selector: str, style: Union[str, Path]):
349
+ """add scoped style to the element
350
+
351
+ @see - https://github.com/CrystalWindSnake/ex4nicegui/blob/main/README.en.md#scoped_style
352
+ @中文文档 - https://gitee.com/carson_add/ex4nicegui/tree/main/#scoped_style
353
+
354
+ Args:
355
+ selector (str): css selector
356
+ style (Union[str, Path]): path to css file or inline style string
357
+
358
+ ## usage
359
+
360
+ .. code-block:: python
361
+ # all children of the element will have red outline, excluding itself
362
+ with rxui.row().scoped_style("*", "outline: 1px solid red;") as row:
363
+ ui.label("Hello")
364
+ ui.label("World")
365
+
366
+ # all children of the element will have red outline, including the element itself
367
+ with rxui.row().scoped_style(":self *", "outline: 1px solid red;") as row:
368
+ ui.label("Hello")
369
+ ui.label("World")
370
+
371
+ # all children of the element will have red outline when element is hovered
372
+ with rxui.row().scoped_style(":hover *", "outline: 1px solid red;") as row:
373
+ ui.label("Hello")
374
+ ui.label("World")
375
+
376
+ # all children of the element and itself will have red outline when element is hovered
377
+ with rxui.row().scoped_style(":self:hover *", "outline: 1px solid red;") as row:
378
+ ui.label("Hello")
379
+ ui.label("World")
380
+
381
+ """
382
+
383
+ is_css_file = isinstance(style, Path)
384
+
385
+ if is_css_file:
386
+ style = style.read_text(encoding="utf-8")
387
+
388
+ id = f"c{self.element.id}"
389
+ selector_with_self = _utils._parent_id_with_selector(id, selector, is_css_file)
390
+ css = ""
391
+ if is_css_file:
392
+ css = f"{selector_with_self} {style}"
393
+ else:
394
+ css = f"{selector_with_self}{{{style}}}"
395
+
396
+ scope_style = ScopedStyle.get()
397
+ assert scope_style, "can not find scope style"
398
+ scope_style.create_style(self.element, css)
399
+ self.__used_scope_style = True
400
+ return self
401
+
402
+ def update(self):
403
+ """Update the element on the client side."""
404
+ self.element.update()
405
+
406
+
407
+ class _utils:
408
+ @staticmethod
409
+ def _parent_id_with_selector(
410
+ parent_id: str, selector: str, is_css_file=False
411
+ ) -> str:
412
+ selector_with_self = f"#{parent_id}"
413
+
414
+ selector = selector.strip()
415
+ if (not selector) and (not is_css_file):
416
+ selector = "* "
417
+
418
+ if selector.startswith(":self"):
419
+ selector = selector[5:].lstrip()
420
+ parent_selector = f"#{parent_id}"
421
+ if selector.startswith(":"):
422
+ parent_selector = f"{parent_selector}{selector.split()[0]}"
423
+
424
+ selector_with_self = f"{parent_selector},{selector_with_self}"
425
+
426
+ if not selector.startswith(":"):
427
+ selector_with_self = selector_with_self + " "
428
+
429
+ selector_with_self = selector_with_self + selector
430
+ return selector_with_self
431
+
432
+
433
+ DisableableBindableUi = DisableableMixin
@@ -14,10 +14,11 @@ class DeferredTask:
14
14
  for task in self._tasks:
15
15
  task()
16
16
 
17
- self._tasks.clear()
18
-
19
17
  # Avoid events becoming ineffective due to page refresh when sharing the client.
20
18
  if not client.shared:
19
+ # In a shared page, execution is required with every refresh.
20
+ # note:https://github.com/CrystalWindSnake/ex4nicegui/issues/227
21
+ self._tasks.clear()
21
22
  client.connect_handlers.remove(on_client_connect) # type: ignore
22
23
 
23
24
  ui.context.client.on_connect(on_client_connect)