From 3ab0e3f15e6d19e47d37f700579ae93f2668f693 Mon Sep 17 00:00:00 2001 From: CrystalWindSnake <36034954+CrystalWindSnake@users.noreply.github.com> Date: Tue, 22 Oct 2024 22:36:20 +0800 Subject: [PATCH] fix rxui.toggle (#265) --- __tests/test_toggle.py | 21 ++++++++++++++++ ex4nicegui/reactive/officials/radio.py | 1 + ex4nicegui/reactive/officials/select.py | 1 + ex4nicegui/reactive/officials/toggle.py | 1 + .../reactive/services/reactive_service.py | 24 ++++++++++++++++++- 5 files changed, 47 insertions(+), 1 deletion(-) diff --git a/__tests/test_toggle.py b/__tests/test_toggle.py index 8982db4..f7191bb 100644 --- a/__tests/test_toggle.py +++ b/__tests/test_toggle.py @@ -125,3 +125,24 @@ def onclick(): button.click() target.expect_selected("m") + + +def test_value_not_in_options(browser: BrowserManager, page_path: str): + + @ui.page(page_path) + def _(): + name = to_ref("") + options = ["a", "b"] + + + rxui.toggle(options, value=name) + rxui.label(text=name).classes("label") + ui.button("change", on_click=lambda: name.set_value("other")).classes("btn") + + page = browser.open(page_path) + + label = page.Label(".label") + button = page.Button(".btn") + + button.click() + label.expect_equal_text ("other") \ No newline at end of file diff --git a/ex4nicegui/reactive/officials/radio.py b/ex4nicegui/reactive/officials/radio.py index 025f731..ae131a7 100644 --- a/ex4nicegui/reactive/officials/radio.py +++ b/ex4nicegui/reactive/officials/radio.py @@ -62,6 +62,7 @@ def bind_prop(self, prop: str, value: TGetterOrReadonlyRef): def bind_options(self, options: TGetterOrReadonlyRef): @self._ui_signal_on(options, deep=True) def _(): + ParameterClassifier.mark_event_source_as_internal(self.element) self.element.set_options(to_value(options)) return self diff --git a/ex4nicegui/reactive/officials/select.py b/ex4nicegui/reactive/officials/select.py index 59ac94b..dba67d5 100644 --- a/ex4nicegui/reactive/officials/select.py +++ b/ex4nicegui/reactive/officials/select.py @@ -93,6 +93,7 @@ def _(): def bind_value(self, value: TGetterOrReadonlyRef): @self._ui_signal_on(value, deep=True) def _(): + ParameterClassifier.mark_event_source_as_internal(self.element) cast(ValueElement, self.element).set_value(to_raw(to_value(value)) or None) return self diff --git a/ex4nicegui/reactive/officials/toggle.py b/ex4nicegui/reactive/officials/toggle.py index 162e8fa..932c588 100644 --- a/ex4nicegui/reactive/officials/toggle.py +++ b/ex4nicegui/reactive/officials/toggle.py @@ -83,6 +83,7 @@ def _(): def bind_value(self, value: TGetterOrReadonlyRef): @self._ui_signal_on(value, deep=True) def _(): + ParameterClassifier.mark_event_source_as_internal(self.element) self.element.set_value(to_raw(to_value(value)) or None) return self diff --git a/ex4nicegui/reactive/services/reactive_service.py b/ex4nicegui/reactive/services/reactive_service.py index 0c544f7..1eb6b37 100644 --- a/ex4nicegui/reactive/services/reactive_service.py +++ b/ex4nicegui/reactive/services/reactive_service.py @@ -18,6 +18,9 @@ from ex4nicegui.utils.proxy import is_base_type_proxy +_EVENT_SOURCE_INTERNAL_FLAG = "__ex4ng_set_value_from_signal_" + + class ParameterClassifier: def __init__( self, @@ -71,7 +74,14 @@ def get_values_kws(self) -> Dict: if is_setter_ref(model_value): def inject_on_change(e): - model_value.value = self.v_model_arg_getter(e) # type: ignore + change_from_inner_signal = ( + ParameterClassifier.get_event_source_internal_flag(e.sender) + ) + + if not change_from_inner_signal: + model_value.value = self.v_model_arg_getter(e) # type: ignore + + ParameterClassifier.remove_event_source_internal_flag(e.sender) handle_event(event, e) value_kws.update({event_name: inject_on_change}) @@ -85,6 +95,18 @@ def get_bindings(self) -> Dict: if (k in self.maybeRefs and (is_ref(v) or isinstance(v, Callable))) } + @staticmethod + def mark_event_source_as_internal(element: ui.element): + element.__dict__[_EVENT_SOURCE_INTERNAL_FLAG] = True + + @staticmethod + def get_event_source_internal_flag(element: ui.element): + return element.__dict__.get(_EVENT_SOURCE_INTERNAL_FLAG, False) + + @staticmethod + def remove_event_source_internal_flag(element: ui.element): + element.__dict__.pop(_EVENT_SOURCE_INTERNAL_FLAG, None) + def inject_handle_delete(element: ui.element, on_delete: Callable[[], None]): inject_method(element, "_handle_delete", on_delete)