debounce.py 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113
  1. """Wrapper around react-debounce-input."""
  2. from __future__ import annotations
  3. from typing import Any
  4. from reflex.components import Component
  5. from reflex.components.tags import Tag
  6. from reflex.vars import Var
  7. class DebounceInput(Component):
  8. """The DebounceInput component is used to buffer input events on the client side.
  9. It is intended to wrap various form controls and should be used whenever a
  10. fully-controlled input is needed to prevent lost input data when the backend
  11. is experiencing high latency.
  12. """
  13. library = "react-debounce-input@^3.3.0"
  14. tag = "DebounceInput"
  15. # Minimum input characters before triggering the on_change event
  16. min_length: Var[int]
  17. # Time to wait between end of input and triggering on_change
  18. debounce_timeout: Var[int]
  19. # If true, notify when Enter key is pressed
  20. force_notify_by_enter: Var[bool]
  21. # If true, notify when form control loses focus
  22. force_notify_on_blur: Var[bool]
  23. # If provided, create a fully-controlled input
  24. value: Var[str]
  25. def _render(self) -> Tag:
  26. """Carry first child props directly on this tag.
  27. Since react-debounce-input wants to create and manage the underlying
  28. input component itself, we carry all props, events, and styles from
  29. the child, and then neuter the child's render method so it produces no output.
  30. Returns:
  31. The rendered debounce element wrapping the first child element.
  32. Raises:
  33. RuntimeError: unless exactly one child element is provided.
  34. ValueError: if the child element does not have an on_change handler.
  35. """
  36. child, props = _collect_first_child_and_props(self)
  37. if isinstance(child, type(self)) or len(self.children) > 1:
  38. raise RuntimeError(
  39. "Provide a single child for DebounceInput, such as rx.input() or "
  40. "rx.text_area()",
  41. )
  42. if "on_change" not in child.event_triggers:
  43. raise ValueError("DebounceInput child requires an on_change handler")
  44. child_ref = child.get_ref()
  45. if child_ref and not props.get("ref"):
  46. props["input_ref"] = Var.create(child_ref, is_local=False)
  47. self.children = []
  48. tag = super()._render()
  49. tag.add_props(
  50. **props,
  51. **child.event_triggers,
  52. sx=child.style,
  53. id=child.id,
  54. class_name=child.class_name,
  55. element=Var.create("{%s}" % child.tag, is_local=False, is_string=False),
  56. )
  57. # do NOT render the child, DebounceInput will create it
  58. object.__setattr__(child, "render", lambda: "")
  59. return tag
  60. def props_not_none(c: Component) -> dict[str, Any]:
  61. """Get all properties of the component that are not None.
  62. Args:
  63. c: the component to get_props from
  64. Returns:
  65. dict of all props that are not None.
  66. """
  67. cdict = {a: getattr(c, a) for a in c.get_props() if getattr(c, a, None) is not None}
  68. return cdict
  69. def _collect_first_child_and_props(c: Component) -> tuple[Component, dict[str, Any]]:
  70. """Recursively find the first child of a different type than `c` with props.
  71. This function is used to collapse nested DebounceInput components by
  72. applying props from each level. Parent props take precedent over child
  73. props. The first child component that differs in type will be returned
  74. along with all combined parent props seen along the way.
  75. Args:
  76. c: the component to get_props from
  77. Returns:
  78. tuple containing the first nested child of a different type and the collected
  79. props from each component traversed.
  80. """
  81. props = props_not_none(c)
  82. if not c.children:
  83. return c, props
  84. child = c.children[0]
  85. if not isinstance(child, type(c)):
  86. return child, {**props_not_none(child), **props}
  87. # carry props from nested DebounceInput components
  88. recursive_child, child_props = _collect_first_child_and_props(child)
  89. return recursive_child, {**child_props, **props}