debounce.py 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139
  1. """Wrapper around react-debounce-input."""
  2. from __future__ import annotations
  3. from typing import Any, Type, Union
  4. from reflex.components.component import Component
  5. from reflex.constants import EventTriggers
  6. from reflex.event import EventHandler
  7. from reflex.vars import Var, VarData
  8. DEFAULT_DEBOUNCE_TIMEOUT = 300
  9. class DebounceInput(Component):
  10. """The DebounceInput component is used to buffer input events on the client side.
  11. It is intended to wrap various form controls and should be used whenever a
  12. fully-controlled input is needed to prevent lost input data when the backend
  13. is experiencing high latency.
  14. """
  15. library = "react-debounce-input@3.3.0"
  16. tag = "DebounceInput"
  17. # Minimum input characters before triggering the on_change event
  18. min_length: Var[int]
  19. # Time to wait between end of input and triggering on_change
  20. debounce_timeout: Var[int] = DEFAULT_DEBOUNCE_TIMEOUT # type: ignore
  21. # If true, notify when Enter key is pressed
  22. force_notify_by_enter: Var[bool]
  23. # If true, notify when form control loses focus
  24. force_notify_on_blur: Var[bool]
  25. # If provided, create a fully-controlled input
  26. value: Var[Union[str, int, float]]
  27. # The ref to attach to the created input
  28. input_ref: Var[str]
  29. # The element to wrap
  30. element: Var[Type[Component]]
  31. # Fired when the input value changes
  32. on_change: EventHandler[lambda e0: [e0.value]]
  33. @classmethod
  34. def create(cls, *children: Component, **props: Any) -> Component:
  35. """Create a DebounceInput component.
  36. Carry first child props directly on this tag.
  37. Since react-debounce-input wants to create and manage the underlying
  38. input component itself, we carry all props, events, and styles from
  39. the child, and then neuter the child's render method so it produces no output.
  40. Args:
  41. children: The child component to wrap.
  42. props: The component props.
  43. Returns:
  44. The DebounceInput component.
  45. Raises:
  46. RuntimeError: unless exactly one child element is provided.
  47. ValueError: if the child element does not have an on_change handler.
  48. """
  49. if len(children) != 1:
  50. raise RuntimeError(
  51. "Provide a single child for DebounceInput, such as rx.input() or "
  52. "rx.text_area()",
  53. )
  54. child = children[0]
  55. if "on_change" not in child.event_triggers:
  56. raise ValueError("DebounceInput child requires an on_change handler")
  57. # Carry known props and event_triggers from the child.
  58. props_from_child = {
  59. p: getattr(child, p)
  60. for p in cls.get_props()
  61. if getattr(child, p, None) is not None
  62. }
  63. props[EventTriggers.ON_CHANGE] = child.event_triggers.pop(
  64. EventTriggers.ON_CHANGE
  65. )
  66. props = {**props_from_child, **props}
  67. # Carry all other child props directly via custom_attrs
  68. other_props = {
  69. p: getattr(child, p)
  70. for p in child.get_props()
  71. if p not in props_from_child and getattr(child, p) is not None
  72. }
  73. props.setdefault("custom_attrs", {}).update(other_props, **child.custom_attrs)
  74. # Carry base Component props.
  75. props.setdefault("style", {}).update(child.style)
  76. if child.class_name is not None:
  77. props["class_name"] = f"{props.get('class_name', '')} {child.class_name}"
  78. for field in ("key", "special_props"):
  79. if getattr(child, field) is not None:
  80. props[field] = getattr(child, field)
  81. child_ref = child.get_ref()
  82. if props.get("input_ref") is None and child_ref:
  83. props["input_ref"] = Var.create_safe(
  84. child_ref, _var_is_local=False, _var_is_string=False
  85. )
  86. props["id"] = child.id
  87. # Set the child element to wrap, including any imports/hooks from the child.
  88. props.setdefault(
  89. "element",
  90. Var.create_safe(
  91. "{%s}" % (child.alias or child.tag),
  92. _var_is_local=False,
  93. _var_is_string=False,
  94. _var_data=VarData(
  95. imports=child._get_imports(),
  96. hooks=child._get_hooks_internal(),
  97. ),
  98. ).to(Type[Component]),
  99. )
  100. component = super().create(**props)
  101. component._get_style = child._get_style
  102. component.event_triggers.update(child.event_triggers)
  103. component.children = child.children
  104. component._rename_props = child._rename_props
  105. return component
  106. def _render(self):
  107. return super()._render().remove_props("ref")
  108. debounce_input = DebounceInput.create