clipboard.py 3.3 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798
  1. """Global on_paste handling for Reflex app."""
  2. from __future__ import annotations
  3. from typing import Dict, List, Tuple, Union
  4. from reflex.components.base.fragment import Fragment
  5. from reflex.components.tags.tag import Tag
  6. from reflex.constants.compiler import Hooks
  7. from reflex.event import EventChain, EventHandler, passthrough_event_spec
  8. from reflex.utils.format import format_prop, wrap
  9. from reflex.utils.imports import ImportVar
  10. from reflex.vars import get_unique_variable_name
  11. from reflex.vars.base import Var, VarData
  12. class Clipboard(Fragment):
  13. """Clipboard component."""
  14. # The element ids to attach the event listener to. Defaults to all child components or the document.
  15. targets: Var[List[str]]
  16. # Called when the user pastes data into the document. Data is a list of tuples of (mime_type, data). Binary types will be base64 encoded as a data uri.
  17. on_paste: EventHandler[passthrough_event_spec(List[Tuple[str, str]])]
  18. # Save the original event actions for the on_paste event.
  19. on_paste_event_actions: Var[Dict[str, Union[bool, int]]]
  20. @classmethod
  21. def create(cls, *children, **props):
  22. """Create a Clipboard component.
  23. Args:
  24. *children: The children of the component.
  25. **props: The properties of the component.
  26. Returns:
  27. The Clipboard Component.
  28. """
  29. if "targets" not in props:
  30. # Add all children as targets if not specified.
  31. targets = props.setdefault("targets", [])
  32. for c in children:
  33. if c.id is None:
  34. c.id = f"clipboard_{get_unique_variable_name()}"
  35. targets.append(c.id)
  36. if "on_paste" in props:
  37. # Capture the event actions for the on_paste handler if not specified.
  38. props.setdefault("on_paste_event_actions", props["on_paste"].event_actions)
  39. return super().create(*children, **props)
  40. def _exclude_props(self) -> list[str]:
  41. return [*super()._exclude_props(), "on_paste", "on_paste_event_actions"]
  42. def _render(self) -> Tag:
  43. tag = super()._render()
  44. tag.remove_props("targets")
  45. # Ensure a different Fragment component is created whenever targets differ
  46. tag.add_props(key=self.targets)
  47. return tag
  48. def add_imports(self) -> dict[str, ImportVar]:
  49. """Add the imports for the Clipboard component.
  50. Returns:
  51. The import dict for the component.
  52. """
  53. return {
  54. "$/utils/helpers/paste.js": ImportVar(
  55. tag="usePasteHandler", is_default=True
  56. ),
  57. }
  58. def add_hooks(self) -> list[str | Var[str]]:
  59. """Add hook to register paste event listener.
  60. Returns:
  61. The hooks to add to the component.
  62. """
  63. on_paste = self.event_triggers["on_paste"]
  64. if on_paste is None:
  65. return []
  66. if isinstance(on_paste, EventChain):
  67. on_paste = wrap(str(format_prop(on_paste)).strip("{}"), "(")
  68. hook_expr = f"usePasteHandler({self.targets!s}, {self.on_paste_event_actions!s}, {on_paste!s})"
  69. return [
  70. Var(
  71. hook_expr,
  72. _var_type="str",
  73. _var_data=VarData(position=Hooks.HookPosition.POST_TRIGGER),
  74. ),
  75. ]
  76. clipboard = Clipboard.create