clipboard.py 3.3 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798
  1. """Global on_paste handling for Reflex app."""
  2. from __future__ import annotations
  3. from collections.abc import Sequence
  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[Sequence[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, 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