|
@@ -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.
|
|
|
|