diff --git a/reflex/components/forms/editable.py b/reflex/components/forms/editable.py index cf004d2ff..1d0e303a4 100644 --- a/reflex/components/forms/editable.py +++ b/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. diff --git a/tests/components/forms/test_debounce.py b/tests/components/forms/test_debounce.py index e2d5cab74..b235fc50b 100644 --- a/tests/components/forms/test_debounce.py +++ b/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 == "" diff --git a/tests/components/forms/test_editable.py b/tests/components/forms/test_editable.py new file mode 100644 index 000000000..833239a5e --- /dev/null +++ b/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 == ""