Skip to content

Commit 811efb8

Browse files
committed
First pass at input_submit_textarea() port
1 parent 631be6f commit 811efb8

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

58 files changed

+431
-32
lines changed

shiny/_versions.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
shiny_html_deps = "1.11.1.9000"
1+
shiny_html_deps = "1.11.1.9001"
22
bslib = "0.9.0.9000"
33
htmltools = "0.5.8.9000"
44
bootstrap = "5.3.1"
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import time
2+
3+
from shiny import App, Inputs, Outputs, Session, render, ui
4+
5+
app_ui = ui.page_fluid(
6+
ui.input_submit_textarea("text", placeholder="Enter some input..."),
7+
ui.output_text("value"),
8+
)
9+
10+
11+
def server(input: Inputs, output: Outputs, session: Session):
12+
@render.text
13+
def value():
14+
if "text" in input:
15+
# Simulate processing time
16+
time.sleep(2)
17+
return f"You entered: {input.text()}"
18+
else:
19+
return "Submit some input to see it here."
20+
21+
22+
app = App(app_ui, server)
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import time
2+
3+
from shiny.express import input, render, ui
4+
5+
ui.input_submit_textarea("text", placeholder="Enter some input...")
6+
7+
8+
@render.text
9+
def value():
10+
if "text" in input:
11+
# Simulate processing time
12+
time.sleep(2)
13+
return f"You entered: {input.text()}"
14+
else:
15+
return "Submit some input to see it here."

shiny/express/ui/__init__.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@
6464
input_select,
6565
input_selectize,
6666
input_slider,
67+
input_submit_textarea,
6768
input_switch,
6869
input_task_button,
6970
input_text,
@@ -102,6 +103,7 @@
102103
update_selectize,
103104
update_sidebar,
104105
update_slider,
106+
update_submit_textarea,
105107
update_switch,
106108
update_task_button,
107109
update_text,
@@ -220,6 +222,7 @@
220222
"input_select",
221223
"input_selectize",
222224
"input_slider",
225+
"input_submit_textarea",
223226
"bind_task_button",
224227
"input_task_button",
225228
"input_text",
@@ -243,6 +246,7 @@
243246
"update_select",
244247
"update_selectize",
245248
"update_slider",
249+
"update_submit_textarea",
246250
"update_task_button",
247251
"update_text",
248252
"update_text_area",

shiny/ui/__init__.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@
8484
from ._input_password import input_password
8585
from ._input_select import input_select, input_selectize
8686
from ._input_slider import AnimationOptions, SliderStepArg, SliderValueArg, input_slider
87+
from ._input_submit_textarea import input_submit_textarea, update_submit_textarea
8788
from ._input_task_button import bind_task_button, input_task_button
8889
from ._input_text import input_text, input_text_area
8990
from ._input_update import (
@@ -251,6 +252,9 @@
251252
"SliderValueArg",
252253
"SliderStepArg",
253254
"AnimationOptions",
255+
# _input_submit_textarea
256+
"input_submit_textarea",
257+
"update_submit_textarea",
254258
# _input_task_button
255259
"bind_task_button",
256260
"input_task_button",

shiny/ui/_input_submit_textarea.py

Lines changed: 218 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,218 @@
1+
from __future__ import annotations
2+
3+
__all__ = ("input_submit_textarea", "update_submit_textarea")
4+
5+
import copy
6+
from typing import Literal, Optional
7+
8+
from htmltools import Tag, TagAttrValue, TagChild, css, div, span, tags
9+
10+
from .._docstring import add_example
11+
from .._utils import drop_none
12+
from ..bookmark import restore_input
13+
from ..module import resolve_id
14+
from ..session import Session, require_active_session
15+
from ._html_deps_shinyverse import components_dependencies
16+
from ._input_task_button import input_task_button
17+
from ._utils import shiny_input_label
18+
19+
20+
@add_example()
21+
def input_submit_textarea(
22+
id: str,
23+
label: TagChild = None,
24+
*,
25+
placeholder: Optional[str] = None,
26+
value: str = "",
27+
width: str = "min(680px, 100%)",
28+
rows: int = 1,
29+
button: Optional[Tag] = None,
30+
toolbar: TagChild | TagAttrValue = None,
31+
submit_key: Literal["enter+modifier", "enter"] = "enter+modifier",
32+
**kwargs: TagAttrValue,
33+
) -> Tag:
34+
"""
35+
Create a textarea input control with explicit submission.
36+
37+
Creates a textarea input where users can enter multi-line text and submit
38+
their input using a dedicated button or keyboard shortcut. This control is
39+
ideal when you want to capture finalized input, rather than reacting to every
40+
keystroke, making it useful for chat boxes, comments, or other scenarios
41+
where users may compose and review their text before submitting.
42+
43+
Parameters
44+
----------
45+
id
46+
The input ID.
47+
label
48+
The label to display above the input control. If `None`, no label is displayed.
49+
placeholder
50+
The placeholder text to display when the input is empty. This can be used to
51+
provide a hint or example of the expected input.
52+
value
53+
The initial input text. Note that, unlike :func:`~shiny.ui.input_text_area`,
54+
this won't set a server-side value until the value is explicitly submitted.
55+
width
56+
Any valid CSS unit (e.g., `width="100%"`).
57+
rows
58+
The number of rows (i.e., height) of the textarea. This essentially sets the
59+
minimum height -- the textarea can grow taller as the user enters more text.
60+
button
61+
A :class:`~htmltools.Tag` element to use for the submit button. It's recommended
62+
that this be an :func:`~shiny.ui.input_task_button` since it will automatically
63+
provide a busy indicator (and disable) until the next flush occurs. Note also
64+
that if the submit button launches an :class:`~shiny.reactive.ExtendedTask`,
65+
this button can also be bound to the task (:func:`~shiny.ui.bind_task_button`)
66+
and/or manually updated for more accurate progress reporting
67+
(:func:`~shiny.ui.update_task_button`).
68+
toolbar
69+
UI elements to include alongside the submit button (e.g., help text, links, etc.).
70+
submit_key
71+
A string indicating what keyboard event should trigger the submit button.
72+
The default is `"enter+modifier"`, which requires the user to hold down
73+
Ctrl (or Cmd on Mac) before pressing Enter to submit. This helps prevent
74+
accidental submissions. To allow submission with just the Enter key, use
75+
`"enter"`. In this case, the user can still insert new lines using
76+
Shift+Enter or Alt+Enter.
77+
**kwargs
78+
Additional attributes to apply to the underlying `<textarea>` element
79+
(e.g., spellcheck, autocomplete, etc).
80+
81+
Returns
82+
-------
83+
:
84+
A textarea input control that can be added to a UI definition.
85+
86+
Notes
87+
------
88+
::: {.callout-note title="Server value"}
89+
A character string containing the user's text input.
90+
91+
**Important:** The server isn't sent a value until the user explicitly submits the
92+
input. This means that reading the input value results in a
93+
:class:`~shiny.types.SilentException` until the user actually submits input. After
94+
that, the server will only see updated values when the user submits the input again.
95+
For this reason, if you want to avoid the exception and return a value, check for
96+
the input ID using `if "input_id" in input` before reading the value. See the
97+
examples for a demonstration.
98+
:::
99+
100+
See Also
101+
--------
102+
* :func:`~shiny.ui.update_submit_textarea`
103+
* :func:`~shiny.ui.input_task_button`
104+
* :func:`~shiny.ui.input_text_area`
105+
"""
106+
resolved_id = resolve_id(id)
107+
value = restore_input(resolved_id, default=value)
108+
if not isinstance(value, str):
109+
raise TypeError("`value` must be a string")
110+
111+
needs_modifier = submit_key == "enter+modifier"
112+
113+
if button is None:
114+
button = input_task_button(
115+
id=f"{resolved_id}_submit",
116+
class_="btn-sm",
117+
label=span("\u23ce", class_="bslib-submit-key"),
118+
icon="Submit",
119+
label_busy=div(
120+
span("Processing...", class_="visually-hidden"),
121+
class_="spinner-border spinner-border-sm ms-2",
122+
role="status",
123+
),
124+
icon_busy="Submit",
125+
title="Press Enter to Submit",
126+
aria_label="Press Enter to Submit",
127+
)
128+
129+
if not is_button_tag(button):
130+
raise TypeError("`button` must be a button tag")
131+
132+
button2 = copy.copy(button)
133+
button2.add_class("bslib-submit-textarea-btn")
134+
135+
return div(
136+
{
137+
"class": "bslib-input-submit-textarea shiny-input-container bslib-mb-spacing",
138+
"style": css(width=width),
139+
},
140+
shiny_input_label(resolved_id, label),
141+
div(
142+
tags.textarea(
143+
value,
144+
{"class": "form-control", "style": css(width="100%")},
145+
id=resolved_id,
146+
placeholder=placeholder,
147+
data_needs_modifier="" if needs_modifier else None,
148+
rows=rows,
149+
**kwargs,
150+
),
151+
tags.footer(
152+
div(toolbar, class_="bslib-toolbar"),
153+
button2,
154+
),
155+
class_="bslib-submit-textarea-container",
156+
),
157+
components_dependencies(),
158+
)
159+
160+
161+
def is_button_tag(x: object) -> bool:
162+
if not isinstance(x, Tag):
163+
return False
164+
return x.name == "button" or x.attrs.get("type") == "button"
165+
166+
167+
@add_example()
168+
def update_submit_textarea(
169+
id: str,
170+
*,
171+
value: Optional[str] = None,
172+
placeholder: Optional[str] = None,
173+
label: Optional[TagChild] = None,
174+
submit: bool = False,
175+
focus: bool = False,
176+
session: Optional[Session] = None,
177+
) -> None:
178+
"""
179+
Update a submit textarea input on the client.
180+
181+
Parameters
182+
----------
183+
id
184+
The input ID.
185+
value
186+
The value to set the user input to.
187+
placeholder
188+
The placeholder text for the user input.
189+
label
190+
The label for the input.
191+
submit
192+
Whether to automatically submit the text for the user. Requires `value`.
193+
focus
194+
Whether to move focus to the input element. Requires `value`.
195+
session
196+
A :class:`~shiny.Session` instance. If not provided, it is inferred via
197+
:func:`~shiny.session.get_current_session`.
198+
199+
See Also
200+
--------
201+
* :func:`~shiny.ui.input_submit_textarea`
202+
"""
203+
if value is None and (submit or focus):
204+
raise ValueError(
205+
"An input `value` must be provided when `submit` or `focus` are `True`."
206+
)
207+
208+
session = require_active_session(session)
209+
210+
msg = {
211+
"value": value,
212+
"placeholder": placeholder,
213+
"label": session._process_ui(label) if label is not None else None,
214+
"submit": submit,
215+
"focus": focus,
216+
}
217+
218+
session.send_input_message(id, drop_none(msg))

shiny/www/shared/_version.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
22
"note!": "Generated by scripts/htmlDependencies.R: do not edit by hand",
33
"package": "shiny",
4-
"version": "1.11.1.9000 (rstudio/shiny@0e355ed25cc1066d6894733f04f4b511a27acc53)"
4+
"version": "1.11.1.9001 (rstudio/shiny@48d255a235d8c2646d2a975569edf2617f131634)"
55
}
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"note!": "Generated by scripts/htmlDependencies.R: do not edit by hand",
3-
"shiny_version": "1.11.1.9000 (rstudio/shiny@0e355ed25cc1066d6894733f04f4b511a27acc53)",
4-
"bslib_version": "0.9.0.9000 (rstudio/bslib@9562108e40a0bffb4a7c8709c2963509435c5c0f)",
3+
"shiny_version": "1.11.1.9001 (rstudio/shiny@48d255a235d8c2646d2a975569edf2617f131634)",
4+
"bslib_version": "0.9.0.9000 (rstudio/bslib@ba1be9aebb477a92672f99c60ce2af579e700e2c)",
55
"htmltools_version": "0.5.8.9000 (rstudio/htmltools@487aa0bed7313d7597b6edd5810e53cab0061198)",
66
"bootstrap_version": "5.3.1"
77
}

shiny/www/shared/bootstrap/bootstrap.min.css

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Binary file not shown.

0 commit comments

Comments
 (0)