ex4nicegui 0.6.8__py3-none-any.whl → 0.6.9__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.
@@ -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
@@ -10,10 +10,21 @@ from ex4nicegui.utils.signals import (
10
10
  to_value,
11
11
  )
12
12
  from nicegui import ui
13
- from .base import BindableUi, DisableableMixin
13
+ from .base import (
14
+ BindableUi,
15
+ DisableableMixin,
16
+ )
17
+
18
+ from ex4nicegui.reactive.mixins.backgroundColor import BackgroundColorableMixin
19
+ from ex4nicegui.reactive.mixins.textColor import TextColorableMixin
14
20
 
15
21
 
16
- class ButtonBindableUi(BindableUi[ui.button], DisableableMixin):
22
+ class ButtonBindableUi(
23
+ BindableUi[ui.button],
24
+ DisableableMixin,
25
+ BackgroundColorableMixin,
26
+ TextColorableMixin,
27
+ ):
17
28
  def __init__(
18
29
  self,
19
30
  text: TMaybeRef[str] = "",
@@ -38,9 +49,31 @@ class ButtonBindableUi(BindableUi[ui.button], DisableableMixin):
38
49
  return self.bind_text(ref_ui)
39
50
  if prop == "icon":
40
51
  return self.bind_icon(ref_ui)
52
+ if prop == "color":
53
+ return self.bind_color(ref_ui)
41
54
 
42
55
  return super().bind_prop(prop, ref_ui)
43
56
 
57
+ def bind_color(self, ref_ui: TGetterOrReadonlyRef[str]):
58
+ """Binds the background color of the button.
59
+
60
+ Args:
61
+ ref_ui (TGetterOrReadonlyRef[str]): Getter or readonly reference to the color.
62
+
63
+ """
64
+ BackgroundColorableMixin.bind_color(self, ref_ui)
65
+ return self
66
+
67
+ def bind_text_color(self, ref_ui: TGetterOrReadonlyRef[str]):
68
+ """Binds the text color of the button.
69
+
70
+ Args:
71
+ ref_ui (TGetterOrReadonlyRef[str]): Getter or readonly reference to the color.
72
+
73
+ """
74
+ TextColorableMixin.bind_color(self, ref_ui)
75
+ return self
76
+
44
77
  def bind_text(self, ref_ui: TGetterOrReadonlyRef):
45
78
  @self._ui_effect
46
79
  def _():
@@ -0,0 +1,102 @@
1
+ from typing import Any, Callable, Optional
2
+ from ex4nicegui.reactive.services.reactive_service import ParameterClassifier
3
+ from ex4nicegui.utils.signals import (
4
+ TGetterOrReadonlyRef,
5
+ to_value,
6
+ _TMaybeRef as TMaybeRef,
7
+ )
8
+ from nicegui import ui
9
+ from .base import BindableUi
10
+ from ex4nicegui.reactive.mixins.backgroundColor import BackgroundColorableMixin
11
+ from ex4nicegui.reactive.mixins.textColor import TextColorableMixin
12
+
13
+
14
+ class ChipBindableUi(
15
+ BindableUi[ui.chip],
16
+ BackgroundColorableMixin,
17
+ TextColorableMixin,
18
+ ):
19
+ def __init__(
20
+ self,
21
+ text: TMaybeRef[str] = "",
22
+ *,
23
+ icon: Optional[TMaybeRef[str]] = None,
24
+ color: Optional[TMaybeRef[str]] = "primary",
25
+ text_color: Optional[TMaybeRef[str]] = None,
26
+ on_click: Optional[Callable[..., Any]] = None,
27
+ selectable: TMaybeRef[bool] = False,
28
+ selected: TMaybeRef[bool] = False,
29
+ on_selection_change: Optional[Callable[..., Any]] = None,
30
+ removable: TMaybeRef[bool] = False,
31
+ on_value_change: Optional[Callable[..., Any]] = None,
32
+ ) -> None:
33
+ pc = ParameterClassifier(
34
+ locals(),
35
+ maybeRefs=[
36
+ "text",
37
+ "icon",
38
+ "color",
39
+ "text_color",
40
+ "selectable",
41
+ "selected",
42
+ "removable",
43
+ ],
44
+ events=["on_click", "on_selection_change", "on_value_change"],
45
+ )
46
+
47
+ element = ui.chip(**pc.get_values_kws())
48
+ super().__init__(element)
49
+
50
+ for key, value in pc.get_bindings().items():
51
+ self.bind_prop(key, value) # type: ignore
52
+
53
+ @property
54
+ def text(self):
55
+ return self.element.text
56
+
57
+ def bind_prop(self, prop: str, ref_ui: TGetterOrReadonlyRef):
58
+ if prop == "text":
59
+ return self.bind_text(ref_ui)
60
+
61
+ if prop == "color":
62
+ return self.bind_color(ref_ui)
63
+ if prop == "text-color":
64
+ return self.bind_text_color(ref_ui)
65
+
66
+ return super().bind_prop(prop, ref_ui)
67
+
68
+ def bind_color(self, color: TGetterOrReadonlyRef):
69
+ """Binds the background color property of the chip to a ui element.
70
+
71
+ Args:
72
+ color (TGetterOrReadonlyRef): background color ui element or getter function
73
+
74
+ """
75
+ BackgroundColorableMixin.bind_color(self, color)
76
+ return self
77
+
78
+ def bind_text_color(self, color: TGetterOrReadonlyRef):
79
+ """Binds the text color property of the chip to a ui element.
80
+
81
+ Args:
82
+ color (TGetterOrReadonlyRef): text color ui element or getter function
83
+
84
+
85
+ """
86
+ TextColorableMixin.bind_color(self, color)
87
+ return self
88
+
89
+ def bind_text(self, text: TGetterOrReadonlyRef):
90
+ """Binds the text property of the chip to a ui element.
91
+
92
+ Args:
93
+ text (TGetterOrReadonlyRef): text ui element or getter function
94
+
95
+ """
96
+
97
+ @self._ui_effect
98
+ def _():
99
+ self.element.set_text(str(to_value(text)))
100
+ self.element.update()
101
+
102
+ return self
@@ -10,11 +10,11 @@ from ex4nicegui.utils.signals import (
10
10
  )
11
11
  from nicegui import ui
12
12
  from .base import BindableUi, DisableableMixin
13
+ from ex4nicegui.reactive.mixins.backgroundColor import BackgroundColorableMixin
13
14
 
14
15
 
15
16
  class CircularProgressBindableUi(
16
- BindableUi[ui.circular_progress],
17
- DisableableMixin,
17
+ BindableUi[ui.circular_progress], DisableableMixin, BackgroundColorableMixin
18
18
  ):
19
19
  def __init__(
20
20
  self,
@@ -52,6 +52,8 @@ class CircularProgressBindableUi(
52
52
  def bind_prop(self, prop: str, ref_ui: TGetterOrReadonlyRef):
53
53
  if prop == "value":
54
54
  return self.bind_value(ref_ui)
55
+ if prop == "color":
56
+ return self.bind_color(ref_ui)
55
57
 
56
58
  return super().bind_prop(prop, ref_ui)
57
59
 
@@ -1,6 +1,7 @@
1
1
  from typing import (
2
2
  Any,
3
3
  Optional,
4
+ Union,
4
5
  )
5
6
 
6
7
  from ex4nicegui.reactive.services.reactive_service import ParameterClassifier
@@ -13,8 +14,8 @@ from ex4nicegui.utils.signals import _TMaybeRef as TMaybeRef
13
14
  class GridBindableUi(BindableUi[ui.grid]):
14
15
  def __init__(
15
16
  self,
16
- rows: Optional[TMaybeRef[int]] = None,
17
- columns: Optional[TMaybeRef[int]] = None,
17
+ rows: Optional[TMaybeRef[Union[int, str]]] = None,
18
+ columns: Optional[TMaybeRef[Union[int, str]]] = None,
18
19
  ) -> None:
19
20
  pc = ParameterClassifier(locals(), maybeRefs=["rows", "columns"], events=[])
20
21
 
@@ -13,10 +13,10 @@ from nicegui.elements.mixins.color_elements import (
13
13
  TextColorElement,
14
14
  )
15
15
  from .base import BindableUi
16
- from ex4nicegui.reactive.services import color_service
16
+ from ex4nicegui.reactive.mixins.textColor import TextColorableMixin
17
17
 
18
18
 
19
- class IconBindableUi(BindableUi[ui.icon]):
19
+ class IconBindableUi(BindableUi[ui.icon], TextColorableMixin):
20
20
  def __init__(
21
21
  self,
22
22
  name: TMaybeRef[str],
@@ -43,9 +43,6 @@ class IconBindableUi(BindableUi[ui.icon]):
43
43
 
44
44
  return super().bind_prop(prop, ref_ui)
45
45
 
46
- def bind_color(self, ref_ui: TGetterOrReadonlyRef):
47
- return color_service.bind_color(self, ref_ui)
48
-
49
46
  def bind_name(self, ref_ui: TGetterOrReadonlyRef):
50
47
  @self._ui_effect
51
48
  def _():
@@ -11,11 +11,13 @@ from ex4nicegui.utils.signals import (
11
11
  )
12
12
  from nicegui import ui
13
13
  from .base import BindableUi, DisableableMixin
14
+ from ex4nicegui.reactive.mixins.textColor import TextColorableMixin
14
15
 
15
16
 
16
17
  class KnobBindableUi(
17
18
  BindableUi[ui.knob],
18
19
  DisableableMixin,
20
+ TextColorableMixin,
19
21
  ):
20
22
  def __init__(
21
23
  self,
@@ -62,6 +64,8 @@ class KnobBindableUi(
62
64
  def bind_prop(self, prop: str, ref_ui: TGetterOrReadonlyRef):
63
65
  if prop == "value":
64
66
  return self.bind_value(ref_ui)
67
+ if prop == "color":
68
+ return self.bind_color(ref_ui)
65
69
 
66
70
  return super().bind_prop(prop, ref_ui)
67
71