reflex/reflex/components/core/clipboard.py
Thomas Brandého 1444421766
add deps and position field in VarData (#4518)
* fix memoized event trigger order

* allow to declare deps in event signature for memoized event triggers

* clean up the code to pass tests

* handle position of hooks

* clean up code

* revert test changes

* add future annotations

* remove non-necessary stuff

* reuse data_callback name if already set during first call to add_hooks

* remove HookVar and use Var with VarData instead

* remove test change

* readd removed line

* fix order of stmt for cleaner code

* fix typing

* something broke during the merge I guess

* remove hack and pass proper const for position

* oops, bad syntax in jinja

* use "hook_position" instead of "hook_positions"

match the name of the enum

---------

Co-authored-by: Masen Furer <m_github@0x26.net>
2024-12-13 13:28:55 -08:00

99 lines
3.3 KiB
Python

"""Global on_paste handling for Reflex app."""
from __future__ import annotations
from typing import Dict, List, Tuple, Union
from reflex.components.base.fragment import Fragment
from reflex.components.tags.tag import Tag
from reflex.constants.compiler import Hooks
from reflex.event import EventChain, EventHandler, passthrough_event_spec
from reflex.utils.format import format_prop, wrap
from reflex.utils.imports import ImportVar
from reflex.vars import get_unique_variable_name
from reflex.vars.base import Var, VarData
class Clipboard(Fragment):
"""Clipboard component."""
# The element ids to attach the event listener to. Defaults to all child components or the document.
targets: Var[List[str]]
# 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.
on_paste: EventHandler[passthrough_event_spec(List[Tuple[str, str]])]
# Save the original event actions for the on_paste event.
on_paste_event_actions: Var[Dict[str, Union[bool, int]]]
@classmethod
def create(cls, *children, **props):
"""Create a Clipboard component.
Args:
*children: The children of the component.
**props: The properties of the component.
Returns:
The Clipboard Component.
"""
if "targets" not in props:
# Add all children as targets if not specified.
targets = props.setdefault("targets", [])
for c in children:
if c.id is None:
c.id = f"clipboard_{get_unique_variable_name()}"
targets.append(c.id)
if "on_paste" in props:
# Capture the event actions for the on_paste handler if not specified.
props.setdefault("on_paste_event_actions", props["on_paste"].event_actions)
return super().create(*children, **props)
def _exclude_props(self) -> list[str]:
return [*super()._exclude_props(), "on_paste", "on_paste_event_actions"]
def _render(self) -> Tag:
tag = super()._render()
tag.remove_props("targets")
# Ensure a different Fragment component is created whenever targets differ
tag.add_props(key=self.targets)
return tag
def add_imports(self) -> dict[str, ImportVar]:
"""Add the imports for the Clipboard component.
Returns:
The import dict for the component.
"""
return {
"$/utils/helpers/paste.js": ImportVar(
tag="usePasteHandler", is_default=True
),
}
def add_hooks(self) -> list[str | Var[str]]:
"""Add hook to register paste event listener.
Returns:
The hooks to add to the component.
"""
on_paste = self.event_triggers["on_paste"]
if on_paste is None:
return []
if isinstance(on_paste, EventChain):
on_paste = wrap(str(format_prop(on_paste)).strip("{}"), "(")
hook_expr = f"usePasteHandler({self.targets!s}, {self.on_paste_event_actions!s}, {on_paste!s})"
return [
Var(
hook_expr,
_var_type="str",
_var_data=VarData(position=Hooks.HookPosition.POST_TRIGGER),
),
]
clipboard = Clipboard.create