textarea: expose auto_height and enter_key_submit props (#2884)

* textarea: expose auto_height and enter_key_submit props

These two props improve the workflow for chat apps and other situations where
we want multiline input.

auto_height: resize the textarea based on its contents

enter_key_submit: pressing enter submits the enclosing form (shift+enter
inserts new lines)

Fix #1231

* Update pyi
This commit is contained in:
Masen Furer 2024-03-28 17:17:30 -07:00 committed by GitHub
parent 68c56a9811
commit 8ef193809c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 91 additions and 2 deletions

View File

@ -2,7 +2,7 @@
from __future__ import annotations
from hashlib import md5
from typing import Any, Dict, Iterator, Union
from typing import Any, Dict, Iterator, Set, Union
from jinja2 import Environment
@ -500,6 +500,36 @@ class Select(BaseHTML):
}
AUTO_HEIGHT_JS = """
const autoHeightOnInput = (e, is_enabled) => {
if (is_enabled) {
const el = e.target;
el.style.overflowY = "hidden";
el.style.height = "auto";
el.style.height = (e.target.scrollHeight) + "px";
if (el.form && !el.form.data_resize_on_reset) {
el.form.addEventListener("reset", () => window.setTimeout(() => autoHeightOnInput(e, is_enabled), 0))
el.form.data_resize_on_reset = true;
}
}
}
"""
ENTER_KEY_SUBMIT_JS = """
const enterKeySubmitOnKeyDown = (e, is_enabled) => {
if (is_enabled && e.which === 13 && !e.shiftKey) {
e.preventDefault();
if (!e.repeat) {
if (e.target.form) {
e.target.form.requestSubmit();
}
}
}
}
"""
class Textarea(BaseHTML):
"""Display the textarea element."""
@ -511,6 +541,9 @@ class Textarea(BaseHTML):
# Automatically focuses the textarea when the page loads
auto_focus: Var[Union[str, int, bool]]
# Automatically fit the content height to the text (use min-height with this prop)
auto_height: Var[bool]
# Visible width of the text control, in average character widths
cols: Var[Union[str, int, bool]]
@ -520,6 +553,9 @@ class Textarea(BaseHTML):
# Disables the textarea
disabled: Var[Union[str, int, bool]]
# Enter key submits form (shift-enter adds new line)
enter_key_submit: Var[bool]
# Associates the textarea with a form (by id)
form: Var[Union[str, int, bool]]
@ -550,6 +586,47 @@ class Textarea(BaseHTML):
# How the text in the textarea is to be wrapped when submitting the form
wrap: Var[Union[str, int, bool]]
def _exclude_props(self) -> list[str]:
return super()._exclude_props() + [
"auto_height",
"enter_key_submit",
]
def get_custom_code(self) -> Set[str]:
"""Include the custom code for auto_height and enter_key_submit functionality.
Returns:
The custom code for the component.
"""
custom_code = super().get_custom_code()
if self.auto_height is not None:
custom_code.add(AUTO_HEIGHT_JS)
if self.enter_key_submit is not None:
custom_code.add(ENTER_KEY_SUBMIT_JS)
return custom_code
def _render(self) -> Tag:
tag = super()._render()
if self.enter_key_submit is not None:
if "on_key_down" in self.event_triggers:
raise ValueError(
"Cannot combine `enter_key_submit` with `on_key_down`.",
)
tag.add_props(
on_key_down=Var.create_safe(
f"(e) => enterKeySubmitOnKeyDown(e, {self.enter_key_submit._var_name_unwrapped})",
_var_is_local=False,
)._replace(merge_var_data=self.enter_key_submit._var_data),
)
if self.auto_height is not None:
tag.add_props(
on_input=Var.create_safe(
f"(e) => autoHeightOnInput(e, {self.auto_height._var_name_unwrapped})",
_var_is_local=False,
)._replace(merge_var_data=self.auto_height._var_data),
)
return tag
def get_event_triggers(self) -> Dict[str, Any]:
"""Get the event triggers that pass the component's value to the handler.

View File

@ -8,7 +8,7 @@ from reflex.vars import Var, BaseVar, ComputedVar
from reflex.event import EventChain, EventHandler, EventSpec
from reflex.style import Style
from hashlib import md5
from typing import Any, Dict, Iterator, Union
from typing import Any, Dict, Iterator, Set, Union
from jinja2 import Environment
from reflex.components.el.element import Element
from reflex.components.tags.tag import Tag
@ -2018,7 +2018,11 @@ class Select(BaseHTML):
"""
...
AUTO_HEIGHT_JS = '\nconst autoHeightOnInput = (e, is_enabled) => {\n if (is_enabled) {\n const el = e.target;\n el.style.overflowY = "hidden";\n el.style.height = "auto";\n el.style.height = (e.target.scrollHeight) + "px";\n if (el.form && !el.form.data_resize_on_reset) {\n el.form.addEventListener("reset", () => window.setTimeout(() => autoHeightOnInput(e, is_enabled), 0))\n el.form.data_resize_on_reset = true;\n }\n }\n}\n'
ENTER_KEY_SUBMIT_JS = "\nconst enterKeySubmitOnKeyDown = (e, is_enabled) => {\n if (is_enabled && e.which === 13 && !e.shiftKey) {\n e.preventDefault();\n if (!e.repeat) {\n if (e.target.form) {\n e.target.form.requestSubmit();\n }\n }\n }\n}\n"
class Textarea(BaseHTML):
def get_custom_code(self) -> Set[str]: ...
def get_event_triggers(self) -> Dict[str, Any]: ...
@overload
@classmethod
@ -2031,6 +2035,7 @@ class Textarea(BaseHTML):
auto_focus: Optional[
Union[Var[Union[str, int, bool]], Union[str, int, bool]]
] = None,
auto_height: Optional[Union[Var[bool], bool]] = None,
cols: Optional[Union[Var[Union[str, int, bool]], Union[str, int, bool]]] = None,
dirname: Optional[
Union[Var[Union[str, int, bool]], Union[str, int, bool]]
@ -2038,6 +2043,7 @@ class Textarea(BaseHTML):
disabled: Optional[
Union[Var[Union[str, int, bool]], Union[str, int, bool]]
] = None,
enter_key_submit: Optional[Union[Var[bool], bool]] = None,
form: Optional[Union[Var[Union[str, int, bool]], Union[str, int, bool]]] = None,
max_length: Optional[
Union[Var[Union[str, int, bool]], Union[str, int, bool]]
@ -2168,9 +2174,11 @@ class Textarea(BaseHTML):
*children: The children of the component.
auto_complete: Whether the form control should have autocomplete enabled
auto_focus: Automatically focuses the textarea when the page loads
auto_height: Automatically fit the content height to the text (use min-height with this prop)
cols: Visible width of the text control, in average character widths
dirname: Name part of the textarea to submit in 'dir' and 'name' pair when form is submitted
disabled: Disables the textarea
enter_key_submit: Enter key submits form (shift-enter adds new line)
form: Associates the textarea with a form (by id)
max_length: Maximum number of characters allowed in the textarea
min_length: Minimum number of characters required in the textarea

View File

@ -108,7 +108,9 @@ class TextArea(RadixThemesComponent, el.Textarea):
rows: Optional[Union[Var[str], str]] = None,
value: Optional[Union[Var[str], str]] = None,
wrap: Optional[Union[Var[str], str]] = None,
auto_height: Optional[Union[Var[bool], bool]] = None,
cols: Optional[Union[Var[Union[str, int, bool]], Union[str, int, bool]]] = None,
enter_key_submit: Optional[Union[Var[bool], bool]] = None,
access_key: Optional[
Union[Var[Union[str, int, bool]], Union[str, int, bool]]
] = None,
@ -232,7 +234,9 @@ class TextArea(RadixThemesComponent, el.Textarea):
rows: Visible number of lines in the text control
value: The controlled value of the textarea, read only unless used with on_change
wrap: How the text in the textarea is to be wrapped when submitting the form
auto_height: Automatically fit the content height to the text (use min-height with this prop)
cols: Visible width of the text control, in average character widths
enter_key_submit: Enter key submits form (shift-enter adds new line)
access_key: Provides a hint for generating a keyboard shortcut for the current element.
auto_capitalize: Controls whether and how text input is automatically capitalized as it is entered/edited by the user.
content_editable: Indicates whether the element's content is editable.