123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113 |
- """Wrapper around react-debounce-input."""
- from __future__ import annotations
- from typing import Any
- from reflex.components import Component
- from reflex.components.tags import Tag
- from reflex.vars import Var
- class DebounceInput(Component):
- """The DebounceInput component is used to buffer input events on the client side.
- It is intended to wrap various form controls and should be used whenever a
- fully-controlled input is needed to prevent lost input data when the backend
- is experiencing high latency.
- """
- library = "react-debounce-input@^3.3.0"
- tag = "DebounceInput"
- # Minimum input characters before triggering the on_change event
- min_length: Var[int]
- # Time to wait between end of input and triggering on_change
- debounce_timeout: Var[int]
- # If true, notify when Enter key is pressed
- force_notify_by_enter: Var[bool]
- # If true, notify when form control loses focus
- force_notify_on_blur: Var[bool]
- # If provided, create a fully-controlled input
- value: Var[str]
- def _render(self) -> Tag:
- """Carry first child props directly on this tag.
- Since react-debounce-input wants to create and manage the underlying
- input component itself, we carry all props, events, and styles from
- the child, and then neuter the child's render method so it produces no output.
- Returns:
- The rendered debounce element wrapping the first child element.
- Raises:
- RuntimeError: unless exactly one child element is provided.
- ValueError: if the child element does not have an on_change handler.
- """
- child, props = _collect_first_child_and_props(self)
- if isinstance(child, type(self)) or len(self.children) > 1:
- raise RuntimeError(
- "Provide a single child for DebounceInput, such as rx.input() or "
- "rx.text_area()",
- )
- if "on_change" not in child.event_triggers:
- raise ValueError("DebounceInput child requires an on_change handler")
- child_ref = child.get_ref()
- if child_ref and not props.get("ref"):
- props["input_ref"] = Var.create(child_ref, is_local=False)
- self.children = []
- tag = super()._render()
- tag.add_props(
- **props,
- **child.event_triggers,
- sx=child.style,
- id=child.id,
- class_name=child.class_name,
- element=Var.create("{%s}" % child.tag, is_local=False, is_string=False),
- )
- # do NOT render the child, DebounceInput will create it
- object.__setattr__(child, "render", lambda: "")
- return tag
- def props_not_none(c: Component) -> dict[str, Any]:
- """Get all properties of the component that are not None.
- Args:
- c: the component to get_props from
- Returns:
- dict of all props that are not None.
- """
- cdict = {a: getattr(c, a) for a in c.get_props() if getattr(c, a, None) is not None}
- return cdict
- def _collect_first_child_and_props(c: Component) -> tuple[Component, dict[str, Any]]:
- """Recursively find the first child of a different type than `c` with props.
- This function is used to collapse nested DebounceInput components by
- applying props from each level. Parent props take precedent over child
- props. The first child component that differs in type will be returned
- along with all combined parent props seen along the way.
- Args:
- c: the component to get_props from
- Returns:
- tuple containing the first nested child of a different type and the collected
- props from each component traversed.
- """
- props = props_not_none(c)
- if not c.children:
- return c, props
- child = c.children[0]
- if not isinstance(child, type(c)):
- return child, {**props_not_none(child), **props}
- # carry props from nested DebounceInput components
- recursive_child, child_props = _collect_first_child_and_props(child)
- return recursive_child, {**child_props, **props}
|