Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [UNRELEASED]

### New features

* Added a new `input_submit_textarea()` input element, which is similar to `input_text_area()`, but includes a submit button to only submit the text changes to the server on click. This is especially useful when the input text change triggers a long-running operation and/or the user wants to type longer-form input and review it before submitting it. (#2099)

### Bug fixes

* Fixed `ui.tooltip()`'s `options` parameter to properly pass Bootstrap tooltip options to the underlying web component. (#2101)
Expand Down
2 changes: 2 additions & 0 deletions docs/_quartodoc-core.yml
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ quartodoc:
- ui.input_numeric
- ui.input_text
- ui.input_text_area
- ui.input_submit_textarea
- ui.input_password
- ui.input_action_button
- ui.input_action_link
Expand Down Expand Up @@ -166,6 +167,7 @@ quartodoc:
- ui.update_text
- name: ui.update_text_area
dynamic: "shiny.ui.update_text"
- ui.update_submit_textarea
- ui.update_navset
- ui.update_action_button
- ui.update_action_link
Expand Down
2 changes: 2 additions & 0 deletions docs/_quartodoc-express.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ quartodoc:
- express.ui.input_numeric
- express.ui.input_text
- express.ui.input_text_area
- express.ui.input_submit_textarea
- express.ui.input_password
- express.ui.input_action_button
- express.ui.input_action_link
Expand Down Expand Up @@ -126,6 +127,7 @@ quartodoc:
- express.ui.update_numeric
- express.ui.update_text
- express.ui.update_text_area
- express.ui.update_submit_textarea
- express.ui.update_navset
- express.ui.update_action_button
- express.ui.update_action_link
Expand Down
1 change: 1 addition & 0 deletions docs/_quartodoc-testing.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ quartodoc:
- playwright.controller.InputSelectize
- playwright.controller.InputSlider
- playwright.controller.InputSliderRange
- playwright.controller.InputSubmitTextarea
- playwright.controller.InputSwitch
- playwright.controller.InputTaskButton
- playwright.controller.InputText
Expand Down
2 changes: 1 addition & 1 deletion shiny/_versions.py
Original file line number Diff line number Diff line change
@@ -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"
Expand Down
22 changes: 22 additions & 0 deletions shiny/api-examples/input_submit_textarea/app-core.py
Original file line number Diff line number Diff line change
@@ -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)
15 changes: 15 additions & 0 deletions shiny/api-examples/input_submit_textarea/app-express.py
Original file line number Diff line number Diff line change
@@ -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."
45 changes: 45 additions & 0 deletions shiny/api-examples/update_submit_textarea/app-core.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
from shiny import App, Inputs, Outputs, Session, reactive, render, ui

app_ui = ui.page_fluid(
ui.input_submit_textarea(
"comment",
label="Your Comment",
placeholder="Type your comment here...",
rows=2,
toolbar=[
ui.input_action_button("clear", "Clear", class_="btn-sm btn-danger"),
ui.input_action_button("template", "Use Template", class_="btn-sm"),
],
),
ui.output_text("submitted_comment"),
)


def server(input: Inputs, output: Outputs, session: Session):
@reactive.effect
@reactive.event(input.clear)
def _():
ui.update_submit_textarea(
"comment",
value="",
placeholder="Type your comment here...",
)

@reactive.effect
@reactive.event(input.template)
def _():
ui.update_submit_textarea(
"comment",
value="Thank you for your feedback. We appreciate your input!",
placeholder="",
label="Your Comment (Template Applied)",
)

@render.text
def submitted_comment():
if "comment" in input:
return f"Submitted: {input.comment()}"
return "No comment submitted yet."


app = App(app_ui, server)
41 changes: 41 additions & 0 deletions shiny/api-examples/update_submit_textarea/app-express.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
from shiny import reactive, render
from shiny.express import input, ui

ui.input_submit_textarea(
"comment",
label="Your Comment",
placeholder="Type your comment here...",
rows=2,
toolbar=[
ui.input_action_button("clear", "Clear", class_="btn-sm btn-danger"),
ui.input_action_button("template", "Use Template", class_="btn-sm"),
],
)


@reactive.effect
@reactive.event(input.clear)
def _():
ui.update_submit_textarea(
"comment",
value="",
placeholder="Type your comment here...",
)


@reactive.effect
@reactive.event(input.template)
def _():
ui.update_submit_textarea(
"comment",
value="Thank you for your feedback. We appreciate your input!",
placeholder="",
label="Your Comment (Template Applied)",
)


@render.text
def submitted_comment():
if "comment" in input:
return f"Submitted: {input.comment()}"
return "No comment submitted yet."
4 changes: 4 additions & 0 deletions shiny/express/ui/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@
input_select,
input_selectize,
input_slider,
input_submit_textarea,
input_switch,
input_task_button,
input_text,
Expand Down Expand Up @@ -102,6 +103,7 @@
update_selectize,
update_sidebar,
update_slider,
update_submit_textarea,
update_switch,
update_task_button,
update_text,
Expand Down Expand Up @@ -220,6 +222,7 @@
"input_select",
"input_selectize",
"input_slider",
"input_submit_textarea",
"bind_task_button",
"input_task_button",
"input_text",
Expand All @@ -243,6 +246,7 @@
"update_select",
"update_selectize",
"update_slider",
"update_submit_textarea",
"update_task_button",
"update_text",
"update_text_area",
Expand Down
2 changes: 2 additions & 0 deletions shiny/playwright/controller/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
InputDateRange,
InputNumeric,
InputPassword,
InputSubmitTextarea,
InputText,
InputTextArea,
)
Expand Down Expand Up @@ -83,6 +84,7 @@
"InputSelectize",
"InputSlider",
"InputSliderRange",
"InputSubmitTextarea",
"InputSwitch",
"InputTaskButton",
"InputText",
Expand Down
131 changes: 118 additions & 13 deletions shiny/playwright/controller/_input_fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,28 @@ def expect_autocomplete(
)


class _ExpectRowsAttrM:
"""A mixin class for the rows attribute."""

def expect_rows(
self: UiBaseP,
value: AttrValue,
*,
timeout: Timeout = None,
) -> None:
"""
Expect the `rows` attribute to have a specific value.

Parameters
----------
value
The expected value of the `rows` attribute.
timeout
The maximum time to wait for the expectation to be fulfilled. Defaults to `None`.
"""
_expect_attribute_to_have_value(self.loc, "rows", value=value, timeout=timeout)


class InputText(
_SetTextM,
_ExpectTextInputValueM,
Expand Down Expand Up @@ -296,6 +318,7 @@ class InputTextArea(
_ExpectPlaceholderAttrM,
_ExpectAutocompleteAttrM,
_ExpectSpellcheckAttrM,
_ExpectRowsAttrM,
UiWithLabel,
):
"""Controller for :func:`shiny.ui.input_text_area`."""
Expand Down Expand Up @@ -368,19 +391,6 @@ def expect_cols(self, value: AttrValue, *, timeout: Timeout = None) -> None:
"""
_expect_attribute_to_have_value(self.loc, "cols", value=value, timeout=timeout)

def expect_rows(self, value: AttrValue, *, timeout: Timeout = None) -> None:
"""
Expect the `rows` attribute of the input text area to have a specific value.

Parameters
----------
value
The expected value of the `rows` attribute.
timeout
The maximum time to wait for the expectation to be fulfilled. Defaults to `None`.
"""
_expect_attribute_to_have_value(self.loc, "rows", value=value, timeout=timeout)

def expect_resize(
self,
value: Resize | None,
Expand Down Expand Up @@ -966,3 +976,98 @@ def expect_autoclose(
# TODO-future; Composable expectations
self.date_start.expect_autoclose(value, timeout=timeout)
self.date_end.expect_autoclose(value, timeout=timeout)


class InputSubmitTextarea(
_SetTextM,
WidthContainerStyleM,
_ExpectTextInputValueM,
_ExpectPlaceholderAttrM,
_ExpectRowsAttrM,
UiWithLabel,
):
"""Controller for :func:`shiny.ui.input_submit_textarea`."""

loc_button: Locator
"""Playwright `Locator` for the submit button."""

def __init__(self, page: Page, id: str) -> None:
"""
Initializes the input submit textarea.

Parameters
----------
page
The page where the input submit textarea is located.
id
The id of the input submit textarea.
"""
super().__init__(
page,
id=id,
loc=f"textarea#{id}.form-control",
)
self.loc_button = self.loc_container.locator(".bslib-submit-textarea-btn")

def set(self, value: str, *, submit: bool = False, timeout: Timeout = None) -> None:
"""
Sets the text value in the textarea.

Parameters
----------
value
The text to set.
submit
Whether to click the submit button after setting the text. Defaults to `False`.
timeout
The maximum time to wait for the text to be set. Defaults to `None`.
"""
set_text(self.loc, value, timeout=timeout)
if submit:
self.loc_button.click(timeout=timeout)

def submit(self, *, timeout: Timeout = None) -> None:
"""
Clicks the submit button.

Parameters
----------
timeout
The maximum time to wait for the click. Defaults to `None`.
"""
self.loc_button.click(timeout=timeout)

def expect_data_needs_modifier(
self, value: bool, *, timeout: Timeout = None
) -> None:
"""
Expect the `data-needs-modifier` attribute to be present or absent.

Parameters
----------
value
If `True`, expects the attribute to be present. If `False`, expects it to be absent.
timeout
The maximum time to wait for the expectation to be fulfilled. Defaults to `None`.
"""
_expect_attribute_to_have_value(
self.loc,
"data-needs-modifier",
value="" if value else None,
timeout=timeout,
)

def expect_button_label(
self, value: PatternOrStr, *, timeout: Timeout = None
) -> None:
"""
Expect the submit button to have a specific label.

Parameters
----------
value
The expected label text.
timeout
The maximum time to wait for the expectation to be fulfilled. Defaults to `None`.
"""
playwright_expect(self.loc_button).to_contain_text(value, timeout=timeout)
Loading
Loading