From 811efb8b1eb6a108689845293e86bd6bb81e1ec6 Mon Sep 17 00:00:00 2001 From: Carson Date: Fri, 10 Oct 2025 15:59:44 -0500 Subject: [PATCH 1/6] First pass at input_submit_textarea() port --- shiny/_versions.py | 2 +- .../input_submit_textarea/app-core.py | 22 ++ .../input_submit_textarea/app-express.py | 15 ++ shiny/express/ui/__init__.py | 4 + shiny/ui/__init__.py | 4 + shiny/ui/_input_submit_textarea.py | 218 ++++++++++++++++++ shiny/www/shared/_version.json | 2 +- shiny/www/shared/bootstrap/_version.json | 4 +- shiny/www/shared/bootstrap/bootstrap.min.css | 2 +- ...126MiZpBA-UFUIcVXSCEkx2cmqvXlWqWuU6F.woff2 | Bin 50240 -> 50216 bytes ...MiZpBA-UFUIcVXSCEkx2cmqvXlWqWxU6F15M.woff2 | Bin 56268 -> 56528 bytes ...Gs126MiZpBA-UvWbX2vVnXBbObj2OVTS-muw.woff2 | Bin 48332 -> 48320 bytes ...126MiZpBA-UvWbX2vVnXBbObj2OVTVOmu1aB.woff2 | Bin 52028 -> 51912 bytes shiny/www/shared/bslib/_version.json | 2 +- .../shared/bslib/components/components.css | 2 +- .../shared/bslib/components/components.min.js | 5 +- .../bslib/components/components.min.js.map | 8 +- .../busy-indicators/busy-indicators.css | 2 +- .../scss/input_submit_textarea.scss | 72 ++++++ .../sass/preset/bootstrap/_04_rules.scss | 1 + .../sass/preset/bootstrap/bootstrap.min.css | 2 +- .../sass/preset/cerulean/_04_rules.scss | 1 + .../shared/sass/preset/cosmo/_04_rules.scss | 1 + .../shared/sass/preset/cyborg/_04_rules.scss | 1 + .../shared/sass/preset/darkly/_04_rules.scss | 1 + .../shared/sass/preset/flatly/_04_rules.scss | 1 + .../shared/sass/preset/journal/_04_rules.scss | 1 + .../shared/sass/preset/litera/_04_rules.scss | 1 + .../shared/sass/preset/lumen/_04_rules.scss | 1 + .../www/shared/sass/preset/lux/_04_rules.scss | 1 + .../shared/sass/preset/materia/_04_rules.scss | 1 + .../shared/sass/preset/minty/_04_rules.scss | 1 + .../shared/sass/preset/morph/_04_rules.scss | 1 + .../shared/sass/preset/pulse/_04_rules.scss | 1 + .../shared/sass/preset/quartz/_04_rules.scss | 1 + .../sass/preset/sandstone/_04_rules.scss | 1 + .../shared/sass/preset/shiny/_04_rules.scss | 1 + .../sass/preset/shiny/bootstrap.min.css | 2 +- .../shared/sass/preset/simplex/_04_rules.scss | 1 + .../shared/sass/preset/sketchy/_04_rules.scss | 1 + .../shared/sass/preset/slate/_04_rules.scss | 1 + .../shared/sass/preset/solar/_04_rules.scss | 1 + .../sass/preset/spacelab/_04_rules.scss | 1 + .../sass/preset/superhero/_04_rules.scss | 1 + .../shared/sass/preset/united/_04_rules.scss | 1 + .../shared/sass/preset/vapor/_04_rules.scss | 1 + .../shared/sass/preset/yeti/_04_rules.scss | 1 + .../shared/sass/preset/zephyr/_04_rules.scss | 1 + .../shiny/www/shared/shiny_scss/shiny.scss | 7 + shiny/www/shared/shiny-autoreload.js | 2 +- shiny/www/shared/shiny-showcase.css | 2 +- shiny/www/shared/shiny-showcase.js | 2 +- shiny/www/shared/shiny-testmode.js | 2 +- shiny/www/shared/shiny.js | 33 ++- shiny/www/shared/shiny.js.map | 4 +- shiny/www/shared/shiny.min.css | 4 +- shiny/www/shared/shiny.min.js | 6 +- shiny/www/shared/shiny.min.js.map | 6 +- 58 files changed, 431 insertions(+), 32 deletions(-) create mode 100644 shiny/api-examples/input_submit_textarea/app-core.py create mode 100644 shiny/api-examples/input_submit_textarea/app-express.py create mode 100644 shiny/ui/_input_submit_textarea.py create mode 100644 shiny/www/shared/sass/bslib/components/scss/input_submit_textarea.scss diff --git a/shiny/_versions.py b/shiny/_versions.py index 68010e7f2..729876e85 100644 --- a/shiny/_versions.py +++ b/shiny/_versions.py @@ -1,4 +1,4 @@ -shiny_html_deps = "1.11.1.9000" +shiny_html_deps = "1.11.1.9001" bslib = "0.9.0.9000" htmltools = "0.5.8.9000" bootstrap = "5.3.1" diff --git a/shiny/api-examples/input_submit_textarea/app-core.py b/shiny/api-examples/input_submit_textarea/app-core.py new file mode 100644 index 000000000..eac5d7f5d --- /dev/null +++ b/shiny/api-examples/input_submit_textarea/app-core.py @@ -0,0 +1,22 @@ +import time + +from shiny import App, Inputs, Outputs, Session, render, ui + +app_ui = ui.page_fluid( + ui.input_submit_textarea("text", placeholder="Enter some input..."), + ui.output_text("value"), +) + + +def server(input: Inputs, output: Outputs, session: Session): + @render.text + def value(): + if "text" in input: + # Simulate processing time + time.sleep(2) + return f"You entered: {input.text()}" + else: + return "Submit some input to see it here." + + +app = App(app_ui, server) diff --git a/shiny/api-examples/input_submit_textarea/app-express.py b/shiny/api-examples/input_submit_textarea/app-express.py new file mode 100644 index 000000000..b108fbcfd --- /dev/null +++ b/shiny/api-examples/input_submit_textarea/app-express.py @@ -0,0 +1,15 @@ +import time + +from shiny.express import input, render, ui + +ui.input_submit_textarea("text", placeholder="Enter some input...") + + +@render.text +def value(): + if "text" in input: + # Simulate processing time + time.sleep(2) + return f"You entered: {input.text()}" + else: + return "Submit some input to see it here." diff --git a/shiny/express/ui/__init__.py b/shiny/express/ui/__init__.py index 98ee3f325..0b414aa6e 100644 --- a/shiny/express/ui/__init__.py +++ b/shiny/express/ui/__init__.py @@ -64,6 +64,7 @@ input_select, input_selectize, input_slider, + input_submit_textarea, input_switch, input_task_button, input_text, @@ -102,6 +103,7 @@ update_selectize, update_sidebar, update_slider, + update_submit_textarea, update_switch, update_task_button, update_text, @@ -220,6 +222,7 @@ "input_select", "input_selectize", "input_slider", + "input_submit_textarea", "bind_task_button", "input_task_button", "input_text", @@ -243,6 +246,7 @@ "update_select", "update_selectize", "update_slider", + "update_submit_textarea", "update_task_button", "update_text", "update_text_area", diff --git a/shiny/ui/__init__.py b/shiny/ui/__init__.py index f43d80f13..d2395dfd2 100644 --- a/shiny/ui/__init__.py +++ b/shiny/ui/__init__.py @@ -84,6 +84,7 @@ from ._input_password import input_password from ._input_select import input_select, input_selectize from ._input_slider import AnimationOptions, SliderStepArg, SliderValueArg, input_slider +from ._input_submit_textarea import input_submit_textarea, update_submit_textarea from ._input_task_button import bind_task_button, input_task_button from ._input_text import input_text, input_text_area from ._input_update import ( @@ -251,6 +252,9 @@ "SliderValueArg", "SliderStepArg", "AnimationOptions", + # _input_submit_textarea + "input_submit_textarea", + "update_submit_textarea", # _input_task_button "bind_task_button", "input_task_button", diff --git a/shiny/ui/_input_submit_textarea.py b/shiny/ui/_input_submit_textarea.py new file mode 100644 index 000000000..a226b2ac6 --- /dev/null +++ b/shiny/ui/_input_submit_textarea.py @@ -0,0 +1,218 @@ +from __future__ import annotations + +__all__ = ("input_submit_textarea", "update_submit_textarea") + +import copy +from typing import Literal, Optional + +from htmltools import Tag, TagAttrValue, TagChild, css, div, span, tags + +from .._docstring import add_example +from .._utils import drop_none +from ..bookmark import restore_input +from ..module import resolve_id +from ..session import Session, require_active_session +from ._html_deps_shinyverse import components_dependencies +from ._input_task_button import input_task_button +from ._utils import shiny_input_label + + +@add_example() +def input_submit_textarea( + id: str, + label: TagChild = None, + *, + placeholder: Optional[str] = None, + value: str = "", + width: str = "min(680px, 100%)", + rows: int = 1, + button: Optional[Tag] = None, + toolbar: TagChild | TagAttrValue = None, + submit_key: Literal["enter+modifier", "enter"] = "enter+modifier", + **kwargs: TagAttrValue, +) -> Tag: + """ + Create a textarea input control with explicit submission. + + Creates a textarea input where users can enter multi-line text and submit + their input using a dedicated button or keyboard shortcut. This control is + ideal when you want to capture finalized input, rather than reacting to every + keystroke, making it useful for chat boxes, comments, or other scenarios + where users may compose and review their text before submitting. + + Parameters + ---------- + id + The input ID. + label + The label to display above the input control. If `None`, no label is displayed. + placeholder + The placeholder text to display when the input is empty. This can be used to + provide a hint or example of the expected input. + value + The initial input text. Note that, unlike :func:`~shiny.ui.input_text_area`, + this won't set a server-side value until the value is explicitly submitted. + width + Any valid CSS unit (e.g., `width="100%"`). + rows + The number of rows (i.e., height) of the textarea. This essentially sets the + minimum height -- the textarea can grow taller as the user enters more text. + button + A :class:`~htmltools.Tag` element to use for the submit button. It's recommended + that this be an :func:`~shiny.ui.input_task_button` since it will automatically + provide a busy indicator (and disable) until the next flush occurs. Note also + that if the submit button launches an :class:`~shiny.reactive.ExtendedTask`, + this button can also be bound to the task (:func:`~shiny.ui.bind_task_button`) + and/or manually updated for more accurate progress reporting + (:func:`~shiny.ui.update_task_button`). + toolbar + UI elements to include alongside the submit button (e.g., help text, links, etc.). + submit_key + A string indicating what keyboard event should trigger the submit button. + The default is `"enter+modifier"`, which requires the user to hold down + Ctrl (or Cmd on Mac) before pressing Enter to submit. This helps prevent + accidental submissions. To allow submission with just the Enter key, use + `"enter"`. In this case, the user can still insert new lines using + Shift+Enter or Alt+Enter. + **kwargs + Additional attributes to apply to the underlying `