Przeglądaj źródła

Use DebounceInput wrapper for fully controlled Editable (#1650)

Martin Xu 1 rok temu
rodzic
commit
d884b7ba96

+ 25 - 0
reflex/components/forms/editable.py

@@ -2,6 +2,8 @@
 
 from typing import Dict
 
+from reflex.components.component import Component
+from reflex.components.forms.debounce import DebounceInput
 from reflex.components.libs.chakra import ChakraComponent
 from reflex.event import EVENT_ARG
 from reflex.vars import Var
@@ -36,6 +38,29 @@ class Editable(ChakraComponent):
     # The initial value of the Editable in both edit and preview mode.
     default_value: Var[str]
 
+    @classmethod
+    def create(cls, *children, **props) -> Component:
+        """Create an Editable component.
+
+        Args:
+            children: The children of the component.
+            props: The properties of the component.
+
+        Returns:
+            The component.
+        """
+        if (
+            isinstance(props.get("value"), Var) and props.get("on_change")
+        ) or "debounce_timeout" in props:
+            # Create a debounced input if the user requests full control to avoid typing jank
+            # Currently default to 50ms, which appears to be a good balance
+            debounce_timeout = props.pop("debounce_timeout", 50)
+            return DebounceInput.create(
+                super().create(*children, **props),
+                debounce_timeout=debounce_timeout,
+            )
+        return super().create(*children, **props)
+
     def get_controlled_triggers(self) -> Dict[str, Var]:
         """Get the event triggers that pass the component's value to the handler.
 

+ 3 - 3
tests/components/forms/test_debounce.py

@@ -89,7 +89,7 @@ def test_render_child_props_recursive():
     )
     assert tag.props["forceNotifyOnBlur"].name == "false"
     assert tag.props["forceNotifyByEnter"].name == "false"
-    assert tag.props["debounceTimeout"] == 42
+    assert tag.props["debounceTimeout"].name == "42"
     assert len(tag.props["onChange"].events) == 1
     assert tag.props["onChange"].events[0].handler == S.on_change
     assert tag.contents == ""
@@ -101,7 +101,7 @@ def test_full_control_implicit_debounce():
         value=S.value,
         on_change=S.on_change,
     )._render()
-    assert tag.props["debounceTimeout"] == 0
+    assert tag.props["debounceTimeout"].name == "50"
     assert len(tag.props["onChange"].events) == 1
     assert tag.props["onChange"].events[0].handler == S.on_change
     assert tag.contents == ""
@@ -113,7 +113,7 @@ def test_full_control_implicit_debounce_text_area():
         value=S.value,
         on_change=S.on_change,
     )._render()
-    assert tag.props["debounceTimeout"] == 0
+    assert tag.props["debounceTimeout"].name == "50"
     assert len(tag.props["onChange"].events) == 1
     assert tag.props["onChange"].events[0].handler == S.on_change
     assert tag.contents == ""

+ 48 - 0
tests/components/forms/test_editable.py

@@ -0,0 +1,48 @@
+import reflex as rx
+
+
+class S(rx.State):
+    """Example state for debounce tests."""
+
+    value: str = ""
+
+    def on_change(self, v: str):
+        """Dummy on_change handler.
+
+        Args:
+            v: The changed value.
+        """
+        pass
+
+
+def test_full_control_implicit_debounce_editable():
+    """DebounceInput is used when value and on_change are used together."""
+    tag = rx.editable(
+        value=S.value,
+        on_change=S.on_change,
+    )._render()
+    assert tag.props["debounceTimeout"].name == "50"
+    assert len(tag.props["onChange"].events) == 1
+    assert tag.props["onChange"].events[0].handler == S.on_change
+    assert tag.contents == ""
+
+
+def test_full_control_explicit_debounce_editable():
+    """DebounceInput is used when user specifies `debounce_timeout`."""
+    tag = rx.editable(
+        on_change=S.on_change,
+        debounce_timeout=33,
+    )._render()
+    assert tag.props["debounceTimeout"].name == "33"
+    assert len(tag.props["onChange"].events) == 1
+    assert tag.props["onChange"].events[0].handler == S.on_change
+    assert tag.contents == ""
+
+
+def test_editable_no_debounce():
+    """DebounceInput is not used for regular editable."""
+    tag = rx.editable(
+        placeholder=S.value,
+    )._render()
+    assert "debounceTimeout" not in tag.props
+    assert tag.contents == ""