From 0bd8f6acfc75fc97acd4832e22760aead409bd02 Mon Sep 17 00:00:00 2001 From: Nikhil Rao Date: Sun, 12 Feb 2023 13:12:57 -0800 Subject: [PATCH] Add keypress event handlers (#523) --- pynecone/components/component.py | 22 +++++++--------------- pynecone/components/forms/checkbox.py | 19 ++++++------------- pynecone/components/forms/editable.py | 15 ++++++++------- pynecone/components/forms/input.py | 24 +++++++++++------------- pynecone/components/forms/numberinput.py | 11 +++++++---- pynecone/components/forms/pininput.py | 12 ++++++++---- pynecone/components/forms/radio.py | 11 +++++++---- pynecone/components/forms/rangeslider.py | 13 +++++++------ pynecone/components/forms/select.py | 19 ++++++------------- pynecone/components/forms/slider.py | 13 +++++++------ pynecone/components/forms/switch.py | 19 ++++++------------- pynecone/components/forms/textarea.py | 24 +++++++++++------------- pynecone/event.py | 1 + tests/components/test_component.py | 15 +++++++++------ 14 files changed, 101 insertions(+), 117 deletions(-) diff --git a/pynecone/components/component.py b/pynecone/components/component.py index a24c33ce8..1075862c8 100644 --- a/pynecone/components/component.py +++ b/pynecone/components/component.py @@ -166,7 +166,8 @@ class Component(Base, ABC): ValueError: If the value is not a valid event chain. """ # Check if the trigger is a controlled event. - is_controlled_event = event_trigger in self.get_controlled_triggers() + controlled_triggers = self.get_controlled_triggers() + is_controlled_event = event_trigger in controlled_triggers # If it's an event chain var, return it. if isinstance(value, Var): @@ -174,7 +175,7 @@ class Component(Base, ABC): raise ValueError(f"Invalid event chain: {value}") return value - arg = self.get_controlled_value() + arg = controlled_triggers.get(event_trigger, EVENT_ARG) # If the input is a single event handler, wrap it in a list. if isinstance(value, EventHandler): @@ -231,25 +232,16 @@ class Component(Base, ABC): Returns: The event triggers. """ - return EVENT_TRIGGERS | cls.get_controlled_triggers() + return EVENT_TRIGGERS | set(cls.get_controlled_triggers()) @classmethod - def get_controlled_triggers(cls) -> Set[str]: + def get_controlled_triggers(cls) -> Dict[str, Var]: """Get the event triggers that pass the component's value to the handler. Returns: - The controlled event triggers. + A dict mapping the event trigger to the var that is passed to the handler. """ - return set() - - @classmethod - def get_controlled_value(cls) -> Var: - """Get the var that is passed to the event handler for controlled triggers. - - Returns: - The controlled value. - """ - return EVENT_ARG + return {} @classmethod def get_alias(cls) -> Optional[str]: diff --git a/pynecone/components/forms/checkbox.py b/pynecone/components/forms/checkbox.py index ea287b9d6..18b3a5bd5 100644 --- a/pynecone/components/forms/checkbox.py +++ b/pynecone/components/forms/checkbox.py @@ -1,6 +1,6 @@ """A checkbox component.""" -from typing import Set +from typing import Dict from pynecone.components.component import EVENT_ARG from pynecone.components.libs.chakra import ChakraComponent @@ -46,22 +46,15 @@ class Checkbox(ChakraComponent): spacing: Var[str] @classmethod - def get_controlled_triggers(cls) -> Set[str]: + def get_controlled_triggers(cls) -> Dict[str, Var]: """Get the event triggers that pass the component's value to the handler. Returns: - The controlled event triggers. + A dict mapping the event trigger to the var that is passed to the handler. """ - return {"on_change"} - - @classmethod - def get_controlled_value(cls) -> Var: - """Get the var that is passed to the event handler for controlled triggers. - - Returns: - The controlled value. - """ - return EVENT_ARG.target.checked + return { + "on_change": EVENT_ARG.target.checked, + } class CheckboxGroup(ChakraComponent): diff --git a/pynecone/components/forms/editable.py b/pynecone/components/forms/editable.py index 44fe273ab..417c49c44 100644 --- a/pynecone/components/forms/editable.py +++ b/pynecone/components/forms/editable.py @@ -1,8 +1,9 @@ """An editable component.""" -from typing import Set +from typing import Dict from pynecone.components.libs.chakra import ChakraComponent +from pynecone.event import EVENT_ARG from pynecone.var import Var @@ -36,17 +37,17 @@ class Editable(ChakraComponent): default_value: Var[str] @classmethod - def get_controlled_triggers(cls) -> Set[str]: + def get_controlled_triggers(cls) -> Dict[str, Var]: """Get the event triggers that pass the component's value to the handler. Returns: - The controlled event triggers. + A dict mapping the event trigger to the var that is passed to the handler. """ return { - "on_change", - "on_edit", - "on_submit", - "on_cancel", + "on_change": EVENT_ARG, + "on_edit": EVENT_ARG, + "on_submit": EVENT_ARG, + "on_cancel": EVENT_ARG, } diff --git a/pynecone/components/forms/input.py b/pynecone/components/forms/input.py index 266ba9b7d..dd97c463d 100644 --- a/pynecone/components/forms/input.py +++ b/pynecone/components/forms/input.py @@ -1,6 +1,6 @@ """An input component.""" -from typing import Set +from typing import Dict from pynecone.components.component import EVENT_ARG from pynecone.components.libs.chakra import ChakraComponent @@ -49,22 +49,20 @@ class Input(ChakraComponent): size: Var[str] @classmethod - def get_controlled_triggers(cls) -> Set[str]: + def get_controlled_triggers(cls) -> Dict[str, Var]: """Get the event triggers that pass the component's value to the handler. Returns: - The controlled event triggers. + A dict mapping the event trigger to the var that is passed to the handler. """ - return {"on_change", "on_focus", "on_blur"} - - @classmethod - def get_controlled_value(cls) -> Var: - """Get the var that is passed to the event handler for controlled triggers. - - Returns: - The controlled value. - """ - return EVENT_ARG.target.value + return { + "on_change": EVENT_ARG.target.value, + "on_focus": EVENT_ARG.target.value, + "on_blur": EVENT_ARG.target.value, + "on_key_down": EVENT_ARG.key, + "on_key_press": EVENT_ARG.key, + "on_key_up": EVENT_ARG.key, + } class InputGroup(ChakraComponent): diff --git a/pynecone/components/forms/numberinput.py b/pynecone/components/forms/numberinput.py index f1dfe46ba..c237d4fed 100644 --- a/pynecone/components/forms/numberinput.py +++ b/pynecone/components/forms/numberinput.py @@ -1,9 +1,10 @@ """A number input component.""" -from typing import Set +from typing import Dict from pynecone.components.component import Component from pynecone.components.libs.chakra import ChakraComponent +from pynecone.event import EVENT_ARG from pynecone.var import Var @@ -64,13 +65,15 @@ class NumberInput(ChakraComponent): variant: Var[str] @classmethod - def get_controlled_triggers(cls) -> Set[str]: + def get_controlled_triggers(cls) -> Dict[str, Var]: """Get the event triggers that pass the component's value to the handler. Returns: - The controlled event triggers. + A dict mapping the event trigger to the var that is passed to the handler. """ - return {"on_change"} + return { + "on_change": EVENT_ARG, + } @classmethod def create(cls, *children, **props) -> Component: diff --git a/pynecone/components/forms/pininput.py b/pynecone/components/forms/pininput.py index 4f3064a4b..d8ebc2dec 100644 --- a/pynecone/components/forms/pininput.py +++ b/pynecone/components/forms/pininput.py @@ -1,9 +1,10 @@ """A pin input component.""" -from typing import Set +from typing import Dict from pynecone.components.component import Component from pynecone.components.libs.chakra import ChakraComponent +from pynecone.event import EVENT_ARG from pynecone.var import Var @@ -55,13 +56,16 @@ class PinInput(ChakraComponent): variant: Var[str] @classmethod - def get_controlled_triggers(cls) -> Set[str]: + def get_controlled_triggers(cls) -> Dict[str, Var]: """Get the event triggers that pass the component's value to the handler. Returns: - The controlled event triggers. + A dict mapping the event trigger to the var that is passed to the handler. """ - return {"on_change", "on_complete"} + return { + "on_change": EVENT_ARG, + "on_complete": EVENT_ARG, + } @classmethod def create(cls, *children, **props) -> Component: diff --git a/pynecone/components/forms/radio.py b/pynecone/components/forms/radio.py index 540670465..71708c5df 100644 --- a/pynecone/components/forms/radio.py +++ b/pynecone/components/forms/radio.py @@ -1,13 +1,14 @@ """A radio component.""" -from typing import Any, List, Set +from typing import Any, Dict, List from pynecone import utils from pynecone.components.component import Component from pynecone.components.layout.foreach import Foreach from pynecone.components.libs.chakra import ChakraComponent from pynecone.components.typography.text import Text +from pynecone.event import EVENT_ARG from pynecone.var import Var @@ -20,13 +21,15 @@ class RadioGroup(ChakraComponent): value: Var[Any] @classmethod - def get_controlled_triggers(cls) -> Set[str]: + def get_controlled_triggers(cls) -> Dict[str, Var]: """Get the event triggers that pass the component's value to the handler. Returns: - The controlled event triggers. + A dict mapping the event trigger to the var that is passed to the handler. """ - return {"on_change"} + return { + "on_change": EVENT_ARG, + } @classmethod def create(cls, *children, **props) -> Component: diff --git a/pynecone/components/forms/rangeslider.py b/pynecone/components/forms/rangeslider.py index f486beade..c87187c9d 100644 --- a/pynecone/components/forms/rangeslider.py +++ b/pynecone/components/forms/rangeslider.py @@ -1,9 +1,10 @@ """A range slider component.""" -from typing import List, Set +from typing import Dict, List from pynecone.components.component import Component from pynecone.components.libs.chakra import ChakraComponent +from pynecone.event import EVENT_ARG from pynecone.var import Var @@ -43,16 +44,16 @@ class RangeSlider(ChakraComponent): min_steps_between_thumbs: Var[int] @classmethod - def get_controlled_triggers(cls) -> Set[str]: + def get_controlled_triggers(cls) -> Dict[str, Var]: """Get the event triggers that pass the component's value to the handler. Returns: - The controlled event triggers. + A dict mapping the event trigger to the var that is passed to the handler. """ return { - "on_change", - "on_change_end", - "on_change_start", + "on_change": EVENT_ARG, + "on_change_end": EVENT_ARG, + "on_change_start": EVENT_ARG, } @classmethod diff --git a/pynecone/components/forms/select.py b/pynecone/components/forms/select.py index 08c727d7d..3e73617ea 100644 --- a/pynecone/components/forms/select.py +++ b/pynecone/components/forms/select.py @@ -1,6 +1,6 @@ """A select component.""" -from typing import Any, List, Set +from typing import Any, Dict, List from pynecone import utils from pynecone.components.component import EVENT_ARG, Component @@ -46,22 +46,15 @@ class Select(ChakraComponent): variant: Var[str] @classmethod - def get_controlled_triggers(cls) -> Set[str]: + def get_controlled_triggers(cls) -> Dict[str, Var]: """Get the event triggers that pass the component's value to the handler. Returns: - The controlled event triggers. + A dict mapping the event trigger to the var that is passed to the handler. """ - return {"on_change"} - - @classmethod - def get_controlled_value(cls) -> Var: - """Get the var that is passed to the event handler for controlled triggers. - - Returns: - The controlled value. - """ - return EVENT_ARG.target.value + return { + "on_change": EVENT_ARG.target.value, + } @classmethod def create(cls, *children, **props) -> Component: diff --git a/pynecone/components/forms/slider.py b/pynecone/components/forms/slider.py index ff86315c5..9cd45a1af 100644 --- a/pynecone/components/forms/slider.py +++ b/pynecone/components/forms/slider.py @@ -1,9 +1,10 @@ """A slider component.""" -from typing import Set +from typing import Dict from pynecone.components.component import Component from pynecone.components.libs.chakra import ChakraComponent +from pynecone.event import EVENT_ARG from pynecone.var import Var @@ -43,16 +44,16 @@ class Slider(ChakraComponent): min_steps_between_thumbs: Var[int] @classmethod - def get_controlled_triggers(cls) -> Set[str]: + def get_controlled_triggers(cls) -> Dict[str, Var]: """Get the event triggers that pass the component's value to the handler. Returns: - The controlled event triggers. + A dict mapping the event trigger to the var that is passed to the handler. """ return { - "on_change", - "on_change_end", - "on_change_start", + "on_change": EVENT_ARG, + "on_change_end": EVENT_ARG, + "on_change_start": EVENT_ARG, } @classmethod diff --git a/pynecone/components/forms/switch.py b/pynecone/components/forms/switch.py index 0102b0c16..f69c30845 100644 --- a/pynecone/components/forms/switch.py +++ b/pynecone/components/forms/switch.py @@ -1,5 +1,5 @@ """A switch component.""" -from typing import Set +from typing import Dict from pynecone.components.component import EVENT_ARG from pynecone.components.libs.chakra import ChakraComponent @@ -39,19 +39,12 @@ class Switch(ChakraComponent): placeholder: Var[str] @classmethod - def get_controlled_triggers(cls) -> Set[str]: + def get_controlled_triggers(cls) -> Dict[str, Var]: """Get the event triggers that pass the component's value to the handler. Returns: - The controlled event triggers. + A dict mapping the event trigger to the var that is passed to the handler. """ - return {"on_change"} - - @classmethod - def get_controlled_value(cls) -> Var: - """Get the var that is passed to the event handler for controlled triggers. - - Returns: - The controlled value. - """ - return EVENT_ARG.target.checked + return { + "on_change": EVENT_ARG.target.checked, + } diff --git a/pynecone/components/forms/textarea.py b/pynecone/components/forms/textarea.py index 35fa01c39..a47ea3dc3 100644 --- a/pynecone/components/forms/textarea.py +++ b/pynecone/components/forms/textarea.py @@ -1,6 +1,6 @@ """A textarea component.""" -from typing import Set +from typing import Dict from pynecone.components.component import EVENT_ARG from pynecone.components.libs.chakra import ChakraComponent @@ -43,19 +43,17 @@ class TextArea(ChakraComponent): variant: Var[str] @classmethod - def get_controlled_triggers(cls) -> Set[str]: + def get_controlled_triggers(cls) -> Dict[str, Var]: """Get the event triggers that pass the component's value to the handler. Returns: - The controlled event triggers. + A dict mapping the event trigger to the var that is passed to the handler. """ - return {"on_change", "on_focus", "on_blur"} - - @classmethod - def get_controlled_value(cls) -> Var: - """Get the var that is passed to the event handler for controlled triggers. - - Returns: - The controlled value. - """ - return EVENT_ARG.target.value + return { + "on_change": EVENT_ARG.target.value, + "on_focus": EVENT_ARG.target.value, + "on_blur": EVENT_ARG.target.value, + "on_key_down": EVENT_ARG.key, + "on_key_press": EVENT_ARG.key, + "on_key_up": EVENT_ARG.key, + } diff --git a/pynecone/event.py b/pynecone/event.py index 614c84986..6d0b447b7 100644 --- a/pynecone/event.py +++ b/pynecone/event.py @@ -115,6 +115,7 @@ class FrontendEvent(Base): """A Javascript event.""" target: Target = Target() + key: str = "" # The default event argument. diff --git a/tests/components/test_component.py b/tests/components/test_component.py index d21bc1e74..712c9946f 100644 --- a/tests/components/test_component.py +++ b/tests/components/test_component.py @@ -1,10 +1,10 @@ -from typing import List, Set, Type +from typing import Dict, List, Type import pytest from pynecone.components.component import Component, CustomComponent, ImportDict from pynecone.components.layout.box import Box -from pynecone.event import EVENT_TRIGGERS, EventHandler +from pynecone.event import EVENT_ARG, EVENT_TRIGGERS, EventHandler from pynecone.state import State from pynecone.style import Style from pynecone.var import Var @@ -61,13 +61,16 @@ def component2() -> Type[Component]: arr: Var[List[str]] @classmethod - def get_controlled_triggers(cls) -> Set[str]: + def get_controlled_triggers(cls) -> Dict[str, Var]: """Test controlled triggers. Returns: Test controlled triggers. """ - return {"on_open", "on_close"} + return { + "on_open": EVENT_ARG, + "on_close": EVENT_ARG, + } def _get_imports(self) -> ImportDict: return {"react-redux": {"connect"}} @@ -269,8 +272,8 @@ def test_get_controlled_triggers(component1, component2): component1: A test component. component2: A test component. """ - assert component1.get_controlled_triggers() == set() - assert component2.get_controlled_triggers() == {"on_open", "on_close"} + assert component1.get_controlled_triggers() == dict() + assert set(component2.get_controlled_triggers()) == {"on_open", "on_close"} def test_get_triggers(component1, component2):