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.
- ex4nicegui/__init__.py +2 -0
- ex4nicegui/bi/dataSourceFacade.py +20 -20
- ex4nicegui/bi/index.py +20 -23
- ex4nicegui/helper/client_instance_locker.py +4 -4
- ex4nicegui/reactive/EChartsComponent/ECharts.py +9 -8
- ex4nicegui/reactive/__init__.py +12 -0
- ex4nicegui/reactive/base.py +433 -0
- ex4nicegui/reactive/deferredTask.py +3 -2
- ex4nicegui/reactive/mixins/backgroundColor.py +41 -0
- ex4nicegui/reactive/mixins/disableable.py +44 -0
- ex4nicegui/reactive/mixins/textColor.py +66 -0
- ex4nicegui/reactive/officials/aggrid.py +5 -5
- ex4nicegui/reactive/officials/base.py +4 -453
- ex4nicegui/reactive/officials/button.py +43 -10
- ex4nicegui/reactive/officials/checkbox.py +5 -5
- ex4nicegui/reactive/officials/chip.py +102 -0
- ex4nicegui/reactive/officials/circular_progress.py +9 -7
- ex4nicegui/reactive/officials/color_picker.py +7 -7
- ex4nicegui/reactive/officials/column.py +5 -5
- ex4nicegui/reactive/officials/date.py +5 -5
- ex4nicegui/reactive/officials/dialog.py +49 -0
- ex4nicegui/reactive/officials/echarts.py +49 -51
- ex4nicegui/reactive/officials/expansion.py +5 -5
- ex4nicegui/reactive/officials/grid.py +3 -2
- ex4nicegui/reactive/officials/icon.py +8 -11
- ex4nicegui/reactive/officials/image.py +5 -5
- ex4nicegui/reactive/officials/input.py +8 -8
- ex4nicegui/reactive/officials/knob.py +9 -5
- ex4nicegui/reactive/officials/label.py +8 -15
- ex4nicegui/reactive/officials/linear_progress.py +8 -11
- ex4nicegui/reactive/officials/number.py +9 -9
- ex4nicegui/reactive/officials/radio.py +8 -8
- ex4nicegui/reactive/officials/row.py +5 -5
- ex4nicegui/reactive/officials/select.py +9 -8
- ex4nicegui/reactive/officials/slider.py +6 -6
- ex4nicegui/reactive/officials/switch.py +5 -5
- ex4nicegui/reactive/officials/tab.py +0 -12
- ex4nicegui/reactive/officials/tab_panels.py +113 -7
- ex4nicegui/reactive/officials/table.py +13 -13
- ex4nicegui/reactive/officials/tabs.py +5 -5
- ex4nicegui/reactive/officials/textarea.py +5 -5
- ex4nicegui/reactive/officials/tooltip.py +40 -0
- ex4nicegui/reactive/q_pagination.py +5 -5
- ex4nicegui/reactive/scopedStyle.py +4 -1
- ex4nicegui/reactive/systems/color_system.py +132 -0
- ex4nicegui/reactive/vfor.js +14 -4
- ex4nicegui/reactive/vfor.py +128 -58
- ex4nicegui/reactive/view_model.py +160 -0
- ex4nicegui/reactive/vmodel.py +42 -12
- ex4nicegui/utils/apiEffect.py +5 -1
- ex4nicegui/utils/effect.py +3 -2
- ex4nicegui/utils/scheduler.py +20 -4
- ex4nicegui/utils/signals.py +23 -21
- {ex4nicegui-0.6.8.dist-info → ex4nicegui-0.7.0.dist-info}/METADATA +247 -48
- {ex4nicegui-0.6.8.dist-info → ex4nicegui-0.7.0.dist-info}/RECORD +57 -50
- ex4nicegui/reactive/services/color_service.py +0 -56
- {ex4nicegui-0.6.8.dist-info → ex4nicegui-0.7.0.dist-info}/LICENSE +0 -0
- {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
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
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
|
-
|
|
31
|
-
|
|
32
|
-
|
|
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
|
-
|
|
41
|
-
|
|
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
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
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
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
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
|
-
|
|
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
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
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
|
"""
|
ex4nicegui/reactive/__init__.py
CHANGED
|
@@ -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)
|