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
@@ -0,0 +1,41 @@
1
+ from typing import (
2
+ Any,
3
+ Callable,
4
+ Protocol,
5
+ )
6
+
7
+ import signe
8
+ from ex4nicegui.utils.signals import (
9
+ TGetterOrReadonlyRef,
10
+ WatchedState,
11
+ )
12
+ from nicegui import ui
13
+ from ex4nicegui.reactive.systems import color_system
14
+
15
+
16
+ class BackgroundColorableMixin(Protocol):
17
+ _ui_signal_on: Callable[[Callable[..., Any]], signe.Effect[None]]
18
+
19
+ @property
20
+ def element(self) -> ui.element:
21
+ ...
22
+
23
+ def _bind_background_color(self, value: TGetterOrReadonlyRef[str]):
24
+ @self._ui_signal_on(value) # type: ignore
25
+ def _(state: WatchedState):
26
+ if state.previous is not None:
27
+ color_system.remove_background_color(self.element, state.previous)
28
+
29
+ color_system.add_background_color(self.element, state.current)
30
+
31
+ self.element.update()
32
+
33
+ def bind_color(self, color: TGetterOrReadonlyRef[str]):
34
+ """bind color to the element
35
+
36
+ Args:
37
+ color (TGetterOrReadonlyRef[str]): a reference to the color value
38
+
39
+ """
40
+ self._bind_background_color(color)
41
+ return self
@@ -0,0 +1,44 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import (
4
+ Any,
5
+ Callable,
6
+ Protocol,
7
+ TypeVar,
8
+ )
9
+
10
+ import signe
11
+ from ex4nicegui.utils.signals import (
12
+ TGetterOrReadonlyRef,
13
+ to_value,
14
+ )
15
+ from nicegui.elements.mixins.disableable_element import DisableableElement
16
+
17
+
18
+ _T_DisableableBinder = TypeVar("_T_DisableableBinder", bound=DisableableElement)
19
+
20
+
21
+ class DisableableMixin(Protocol):
22
+ _ui_effect: Callable[[Callable[..., Any]], signe.Effect[None]]
23
+
24
+ @property
25
+ def element(self) -> DisableableElement:
26
+ ...
27
+
28
+ def bind_enabled(self, value: TGetterOrReadonlyRef[bool]):
29
+ @self._ui_effect
30
+ def _():
31
+ raw_value = to_value(value)
32
+ self.element.set_enabled(raw_value)
33
+ self.element._handle_enabled_change(raw_value)
34
+
35
+ return self
36
+
37
+ def bind_disable(self, value: TGetterOrReadonlyRef[bool]):
38
+ @self._ui_effect
39
+ def _():
40
+ raw_value = not to_value(value)
41
+ self.element.set_enabled(raw_value)
42
+ self.element._handle_enabled_change(raw_value)
43
+
44
+ return self
@@ -0,0 +1,66 @@
1
+ from typing import (
2
+ Any,
3
+ Callable,
4
+ Dict,
5
+ Protocol,
6
+ )
7
+
8
+ import signe
9
+ from ex4nicegui.utils.signals import (
10
+ TGetterOrReadonlyRef,
11
+ WatchedState,
12
+ )
13
+ from nicegui import ui
14
+ from ex4nicegui.reactive.systems import color_system
15
+
16
+
17
+ class TextColorableMixin(Protocol):
18
+ _ui_signal_on: Callable[[Callable[[TGetterOrReadonlyRef[str]], Any]], signe.Effect]
19
+
20
+ @property
21
+ def element(self) -> ui.element:
22
+ ...
23
+
24
+ def _bind_text_color(self, color: TGetterOrReadonlyRef[str]):
25
+ @self._ui_signal_on(color) # type: ignore
26
+ def _(state: WatchedState):
27
+ if state.previous is not None:
28
+ color_system.remove_text_color(self.element, state.previous)
29
+
30
+ color_system.add_text_color(self.element, state.current)
31
+
32
+ self.element.update()
33
+
34
+ def bind_color(self, color: TGetterOrReadonlyRef[str]):
35
+ """bind text color to the element
36
+
37
+ Args:
38
+ color (TGetterOrReadonlyRef[str]): a reference to the color value
39
+
40
+ """
41
+ self._bind_text_color(color)
42
+ return self
43
+
44
+
45
+ class HtmlTextColorableMixin(Protocol):
46
+ _ui_signal_on: Callable[[Callable[[TGetterOrReadonlyRef[str]], Any]], signe.Effect]
47
+
48
+ @property
49
+ def element(self) -> ui.element:
50
+ ...
51
+
52
+ def bind_style(self, style: Dict[str, TGetterOrReadonlyRef[Any]]):
53
+ ...
54
+
55
+ def _bind_text_color(self, value: TGetterOrReadonlyRef[str]):
56
+ return self.bind_style({"color": value})
57
+
58
+ def bind_color(self, color: TGetterOrReadonlyRef[str]):
59
+ """bind text color to the element
60
+
61
+ Args:
62
+ color (TGetterOrReadonlyRef[str]): a reference to the color value
63
+
64
+ """
65
+ self._bind_text_color(color)
66
+ return self
@@ -78,17 +78,17 @@ 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: TGetterOrReadonlyRef[Any]):
81
+ def bind_prop(self, prop: str, value: TGetterOrReadonlyRef[Any]):
82
82
  if prop == "options":
83
- return self.bind_options(ref_ui)
83
+ return self.bind_options(value)
84
84
 
85
- return super().bind_prop(prop, ref_ui)
85
+ return super().bind_prop(prop, value)
86
86
 
87
- def bind_options(self, ref_ui: TGetterOrReadonlyRef[List[Dict]]):
87
+ def bind_options(self, options: TGetterOrReadonlyRef[List[Dict]]):
88
88
  @self._ui_effect
89
89
  def _():
90
90
  ele = self.element
91
- data = to_value(ref_ui)
91
+ data = to_value(options)
92
92
  ele._props["options"].update(data)
93
93
  ele.update()
94
94
 
@@ -1,454 +1,5 @@
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
- Protocol,
11
- TypeVar,
12
- Generic,
13
- Union,
14
- cast,
15
- overload,
1
+ from ex4nicegui.reactive.base import (
2
+ BindableUi, # noqa: F401
3
+ DisableableMixin, # noqa: F401
4
+ DisableableBindableUi, # noqa: F401
16
5
  )
17
-
18
- from typing_extensions import Self
19
- from ex4nicegui.utils.apiEffect import ui_effect
20
- import signe
21
- from ex4nicegui.utils.signals import (
22
- TGetterOrReadonlyRef,
23
- to_value,
24
- is_ref,
25
- WatchedState,
26
- on,
27
- )
28
- from ex4nicegui.utils.clientScope import new_scope
29
- from nicegui import Tailwind, ui
30
- from nicegui.elements.mixins.text_element import TextElement
31
- from nicegui.elements.mixins.disableable_element import DisableableElement
32
- from ex4nicegui.reactive.services.reactive_service import inject_handle_delete
33
- from ex4nicegui.reactive.scopedStyle import ScopedStyle
34
- from functools import partial
35
-
36
- T = TypeVar("T")
37
-
38
-
39
- TWidget = TypeVar("TWidget", bound=ui.element)
40
-
41
- _T_bind_classes_type_dict = Dict[str, TGetterOrReadonlyRef[bool]]
42
- _T_bind_classes_type_ref_dict = TGetterOrReadonlyRef[Dict[str, bool]]
43
- _T_bind_classes_type_single = TGetterOrReadonlyRef[str]
44
- _T_bind_classes_type_array = List[_T_bind_classes_type_single]
45
-
46
-
47
- _T_bind_classes_type = Union[
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,
52
- ]
53
-
54
-
55
- class BindableUi(Generic[TWidget]):
56
- def __init__(self, element: TWidget) -> None:
57
- self._element = element
58
- inject_handle_delete(self.element, self._on_element_delete)
59
- self.tailwind = Tailwind(cast(ui.element, self._element))
60
- self._effect_scope = new_scope()
61
-
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)
75
-
76
- def props(self, add: Optional[str] = None, *, remove: Optional[str] = None):
77
- cast(ui.element, self.element).props(add, remove=remove)
78
- return self
79
-
80
- def classes(
81
- self,
82
- add: Optional[str] = None,
83
- *,
84
- remove: Optional[str] = None,
85
- replace: Optional[str] = None,
86
- ):
87
- cast(ui.element, self.element).classes(add, remove=remove, replace=replace)
88
- return self
89
-
90
- def style(
91
- self,
92
- add: Optional[str] = None,
93
- *,
94
- remove: Optional[str] = None,
95
- replace: Optional[str] = None,
96
- ):
97
- cast(ui.element, self.element).style(add, remove=remove, replace=replace)
98
- return self
99
-
100
- def __enter__(self) -> Self:
101
- self.element.__enter__()
102
- return self
103
-
104
- def __exit__(self, *_):
105
- self.element.default_slot.__exit__(*_)
106
-
107
- def tooltip(self, text: str) -> Self:
108
- cast(ui.element, self.element).tooltip(text)
109
- return self
110
-
111
- def add_slot(self, name: str, template: Optional[str] = None):
112
- """Add a slot to the element.
113
-
114
- :param name: name of the slot
115
- :param template: Vue template of the slot
116
- :return: the slot
117
- """
118
- return cast(ui.element, self.element).add_slot(name, template)
119
-
120
- @property
121
- def element(self):
122
- return self._element
123
-
124
- def delete(self) -> None:
125
- """Delete the element."""
126
- self.element.delete()
127
-
128
- def move(
129
- self, target_container: Optional[ui.element] = None, target_index: int = -1
130
- ):
131
- """Move the element to another container.
132
-
133
- :param target_container: container to move the element to (default: the parent container)
134
- :param target_index: index within the target slot (default: append to the end)
135
- """
136
- return self.element.move(target_container, target_index)
137
-
138
- def remove(self, element: Union[ui.element, int]) -> None:
139
- """Remove a child element.
140
-
141
- :param element: either the element instance or its ID
142
- """
143
- return self.element.remove(element)
144
-
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
- """
156
- if prop == "visible":
157
- return self.bind_visible(ref_ui)
158
-
159
- if prop == "text" and isinstance(self.element, TextElement):
160
-
161
- @self._ui_effect
162
- def _():
163
- cast(TextElement, self.element).set_text(to_value(ref_ui))
164
- self.element.update()
165
-
166
- @self._ui_effect
167
- def _():
168
- element = cast(ui.element, self.element)
169
- element._props[prop] = to_value(ref_ui)
170
- element.update()
171
-
172
- return self
173
-
174
- def bind_visible(self, ref_ui: TGetterOrReadonlyRef[bool]):
175
- @self._ui_effect
176
- def _():
177
- element = cast(ui.element, self.element)
178
- element.set_visibility(to_value(ref_ui))
179
-
180
- return self
181
-
182
- def bind_not_visible(self, ref_ui: TGetterOrReadonlyRef[bool]):
183
- return self.bind_visible(lambda: not to_value(ref_ui))
184
-
185
- def on(
186
- self,
187
- type: str,
188
- handler: Optional[Callable[..., Any]] = None,
189
- args: Optional[List[str]] = None,
190
- *,
191
- throttle: float = 0.0,
192
- leading_events: bool = True,
193
- trailing_events: bool = True,
194
- ):
195
- ele = cast(ui.element, self.element)
196
- ele.on(
197
- type,
198
- handler,
199
- args,
200
- throttle=throttle,
201
- leading_events=leading_events,
202
- trailing_events=trailing_events,
203
- )
204
-
205
- return self
206
-
207
- def clear(self) -> None:
208
- cast(ui.element, self.element).clear()
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
-
226
- def bind_classes(self, classes: _T_bind_classes_type):
227
- """data binding is manipulating an element's class list
228
-
229
- @see - https://github.com/CrystalWindSnake/ex4nicegui/blob/main/README.en.md#bind-class-names
230
- @中文文档 - https://gitee.com/carson_add/ex4nicegui/tree/main/#%E7%BB%91%E5%AE%9A%E7%B1%BB%E5%90%8D
231
-
232
- Args:
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
-
264
- """
265
- if isinstance(classes, dict):
266
- for name, ref_obj in classes.items():
267
-
268
- @self._ui_effect
269
- def _(name=name, ref_obj=ref_obj):
270
- if to_value(ref_obj):
271
- self.classes(add=name)
272
- else:
273
- self.classes(remove=name)
274
-
275
- elif is_ref(classes) or isinstance(classes, Callable):
276
- ref_obj = to_value(classes) # type: ignore
277
-
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
-
290
- elif isinstance(classes, list):
291
- for ref_name in classes:
292
- self._bind_single_class(ref_name)
293
-
294
- return self
295
-
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
304
-
305
- return self
306
-
307
- def bind_style(self, style: Dict[str, TGetterOrReadonlyRef[Any]]):
308
- """data binding is manipulating an element's style
309
-
310
- @see - https://github.com/CrystalWindSnake/ex4nicegui/blob/main/README.en.md#bind-style
311
- @中文文档 - https://gitee.com/carson_add/ex4nicegui/tree/main/#bind-style
312
-
313
- Args:
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
- ```
329
- """
330
- if isinstance(style, dict):
331
- for name, ref_obj in style.items():
332
- if is_ref(ref_obj) or isinstance(ref_obj, Callable):
333
-
334
- @self._ui_effect
335
- def _(name=name, ref_obj=ref_obj):
336
- self.element._style[name] = str(to_value(ref_obj))
337
- self.element.update()
338
-
339
- return self
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
-
394
- def update(self):
395
- """Update the element on the client side."""
396
- self.element.update()
397
-
398
-
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]}"
415
-
416
- selector_with_self = f"{parent_selector},{selector_with_self}"
417
-
418
- if not selector.startswith(":"):
419
- selector_with_self = selector_with_self + " "
420
-
421
- selector_with_self = selector_with_self + selector
422
- return selector_with_self
423
-
424
-
425
- _T_DisableableBinder = TypeVar("_T_DisableableBinder", bound=DisableableElement)
426
-
427
-
428
- class DisableableMixin(Protocol):
429
- _ui_effect: Callable[[Callable[..., Any]], signe.Effect[None]]
430
-
431
- @property
432
- def element(self) -> DisableableElement:
433
- ...
434
-
435
- def bind_enabled(self, ref_ui: TGetterOrReadonlyRef[bool]):
436
- @self._ui_effect
437
- def _():
438
- value = to_value(ref_ui)
439
- self.element.set_enabled(value)
440
- self.element._handle_enabled_change(value)
441
-
442
- return self
443
-
444
- def bind_disable(self, ref_ui: TGetterOrReadonlyRef[bool]):
445
- @self._ui_effect
446
- def _():
447
- value = not to_value(ref_ui)
448
- self.element.set_enabled(value)
449
- self.element._handle_enabled_change(value)
450
-
451
- return self
452
-
453
-
454
- DisableableBindableUi = DisableableMixin