Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
Show all changes
86 commits
Select commit Hold shift + click to select a range
693b87c
feat(input_submit_button, input_submit_textarea): Add new input submi…
cpsievert Apr 25, 2025
34cf8ee
`air format` (GitHub Actions)
cpsievert Apr 25, 2025
75d73fb
Resave distributed files (GitHub Action)
cpsievert Apr 25, 2025
3f98b68
Resave data (GitHub Action)
cpsievert Apr 25, 2025
4f2ab2b
Update to latest Shiny
cpsievert Jun 19, 2025
924d98d
Merge branch 'main' into feat/submit-input
cpsievert Jul 25, 2025
1d69765
shiny remote no longer needed
cpsievert Jul 25, 2025
27137d2
`usethis::use_tidy_description()` (GitHub Actions)
cpsievert Jul 25, 2025
dcf5a10
Resave distributed files (GitHub Action)
cpsievert Jul 25, 2025
3da38ac
Resave data (GitHub Action)
cpsievert Jul 25, 2025
7d88ff7
Update website deps (GitHub Action)
cpsievert Jul 25, 2025
8afa5db
Merge branch 'main' into feat/submit-input
cpsievert Sep 30, 2025
8ee990f
Bump shiny requirement
cpsievert Sep 30, 2025
981dd96
Pass on adding input_submit_button() (at least for now)
cpsievert Sep 30, 2025
b6294cd
Make function signature more consistent with other inputs
cpsievert Sep 30, 2025
cc43168
Support HTML label updates
cpsievert Sep 30, 2025
d7036a1
Add autoresize logic
cpsievert Sep 30, 2025
57a41f8
Fix shiny requirement
cpsievert Sep 30, 2025
aa5cb10
`usethis::use_tidy_description()` (GitHub Actions)
cpsievert Sep 30, 2025
2a6995f
Resave data (GitHub Action)
cpsievert Sep 30, 2025
168c332
Update website deps (GitHub Action)
cpsievert Sep 30, 2025
9bfa28a
Avoid non-ASCII characters
cpsievert Sep 30, 2025
9cd0726
CSS tweaks to better align with chat input styles
cpsievert Sep 30, 2025
f1c06ea
`usethis::use_tidy_description()` (GitHub Actions)
cpsievert Sep 30, 2025
53eb8f8
`devtools::document()` (GitHub Actions)
cpsievert Sep 30, 2025
4f4cc18
Update website deps (GitHub Action)
cpsievert Sep 30, 2025
ddf78a2
Doc improvements
cpsievert Sep 30, 2025
141e27f
Strict equality check
cpsievert Sep 30, 2025
bf27042
Better scss file name
cpsievert Sep 30, 2025
4c889ea
`usethis::use_tidy_description()` (GitHub Actions)
cpsievert Sep 30, 2025
da98f13
Update website deps (GitHub Action)
cpsievert Sep 30, 2025
e4443ff
Platform specific modifier in button label
cpsievert Sep 30, 2025
7155c53
Get check passing
cpsievert Sep 30, 2025
bac1e68
`usethis::use_tidy_description()` (GitHub Actions)
cpsievert Sep 30, 2025
0351b64
`yarn build` (GitHub Actions)
cpsievert Sep 30, 2025
2511d40
Update website deps (GitHub Action)
cpsievert Sep 30, 2025
cfe0a04
Add to reference
cpsievert Sep 30, 2025
6bdf0d2
`usethis::use_tidy_description()` (GitHub Actions)
cpsievert Sep 30, 2025
7529734
Update website deps (GitHub Action)
cpsievert Sep 30, 2025
ef1e0f7
Update R/input-submit.R
cpsievert Oct 2, 2025
c46e452
Update inst/components/scss/input_submit_textarea.scss
cpsievert Oct 2, 2025
18c9476
`usethis::use_tidy_description()` (GitHub Actions)
cpsievert Oct 2, 2025
b93ea6a
`devtools::document()` (GitHub Actions)
cpsievert Oct 2, 2025
cbec5c3
Resave distributed files (GitHub Action)
cpsievert Oct 2, 2025
106f5c3
Update website deps (GitHub Action)
cpsievert Oct 2, 2025
1e8a61a
Import label markup logic from shiny
cpsievert Oct 2, 2025
86eec98
Simplify markup
cpsievert Oct 2, 2025
b47b227
`air format` (GitHub Actions)
cpsievert Oct 2, 2025
703d6ee
`usethis::use_tidy_description()` (GitHub Actions)
cpsievert Oct 2, 2025
6cedfb5
Update website deps (GitHub Action)
cpsievert Oct 2, 2025
2dc98ef
Introduce a modifier key container; add explicit class to button
cpsievert Oct 2, 2025
e455d82
`usethis::use_tidy_description()` (GitHub Actions)
cpsievert Oct 2, 2025
23242a2
Update website deps (GitHub Action)
cpsievert Oct 2, 2025
61af9f9
Revert "Simplify markup"
cpsievert Oct 7, 2025
5810cba
Change default to enter+modifier
cpsievert Oct 7, 2025
c66c6b6
`usethis::use_tidy_description()` (GitHub Actions)
cpsievert Oct 7, 2025
8544f5f
Resave data (GitHub Action)
cpsievert Oct 7, 2025
a21777c
Update website deps (GitHub Action)
cpsievert Oct 7, 2025
cdf4d8d
Move the component identifier CSS class to top-level
cpsievert Oct 7, 2025
331aaba
Update documentation
cpsievert Oct 7, 2025
ff02c93
Manually insert new lines for Alt+Enter; Query submit button everytim…
cpsievert Oct 7, 2025
e5a41e4
Improved styling/logic for submit key hint
cpsievert Oct 7, 2025
e9ef2eb
Allow children/attributes to be passed through ...; add rows argument…
cpsievert Oct 7, 2025
674eb85
Better busy UI
cpsievert Oct 10, 2025
138b83d
Rename top-level .bslib-submit-textarea to .bslib-input-submit-textarea
cpsievert Oct 10, 2025
1640259
`usethis::use_tidy_description()` (GitHub Actions)
cpsievert Oct 10, 2025
0013d15
Resave distributed files (GitHub Action)
cpsievert Oct 10, 2025
d1d1c0d
Resave data (GitHub Action)
cpsievert Oct 10, 2025
b7358fe
Update website deps (GitHub Action)
cpsievert Oct 10, 2025
7ccf8e8
Add toolbar argument; require ... to be named; add gap between toolba…
cpsievert Oct 10, 2025
bff5c54
Focus textarea when (inner) container is clicked
cpsievert Oct 10, 2025
71bca10
Delete unused code
cpsievert Oct 10, 2025
012ac22
Merge branch 'main' into feat/submit-input
cpsievert Oct 10, 2025
f9692f8
`air format` (GitHub Actions)
cpsievert Oct 10, 2025
29375f3
`usethis::use_tidy_description()` (GitHub Actions)
cpsievert Oct 10, 2025
74479dc
Update website deps (GitHub Action)
cpsievert Oct 10, 2025
e3080a7
Address feedback
cpsievert Oct 14, 2025
e2553ea
chore: organize Rd content
cpsievert Oct 14, 2025
104b788
fix: support trailing comma
cpsievert Oct 14, 2025
a340934
Add some basic snapshot tests
cpsievert Oct 14, 2025
f02252a
Apply suggestions from code review
cpsievert Oct 14, 2025
693cf19
Merge branch 'main' into feat/submit-input
cpsievert Oct 14, 2025
f663f60
`air format` (GitHub Actions)
cpsievert Oct 14, 2025
23cb0e5
`devtools::document()` (GitHub Actions)
cpsievert Oct 14, 2025
c35e47a
`yarn build` (GitHub Actions)
cpsievert Oct 14, 2025
96ab310
Resave data (GitHub Action)
cpsievert Oct 14, 2025
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
5 changes: 4 additions & 1 deletion DESCRIPTION
Original file line number Diff line number Diff line change
Expand Up @@ -51,13 +51,15 @@ Suggests:
magrittr,
rappdirs,
rmarkdown (>= 2.7),
shiny (> 1.8.1),
shiny (> 1.10.0.9000),
testthat,
thematic,
tools,
utils,
withr,
yaml
Remotes:
rstudio/shiny@feat/inputSubmitText
Config/Needs/deploy:
BH,
chiflights22,
Expand Down Expand Up @@ -138,6 +140,7 @@ Collate:
'fill.R'
'imports.R'
'input-dark-mode.R'
'input-submit.R'
'input-switch.R'
'layout.R'
'nav-items.R'
Expand Down
3 changes: 3 additions & 0 deletions NAMESPACE
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,8 @@ export(font_face)
export(font_google)
export(font_link)
export(input_dark_mode)
export(input_submit_button)
export(input_submit_textarea)
export(input_switch)
export(input_task_button)
export(is.card_item)
Expand Down Expand Up @@ -153,6 +155,7 @@ export(toggle_switch)
export(toggle_tooltip)
export(tooltip)
export(update_popover)
export(update_submit_textarea)
export(update_switch)
export(update_task_button)
export(update_tooltip)
Expand Down
6 changes: 6 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# bslib (development version)

## New features

* Added a new `input_submit_textarea()` function. This input is similar in nature to `shiny::textAreaInput()`, 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.

* Added a new `input_submit_button()` function. This input is similar in nature to `shiny::submitButton()`, but is more flexible and provides more visual feedback (i.e., progress). More specifically, it enables multiple submit buttons (each targetting a different set of input controls) in a single Shiny app.

## Improvements and bug fixes

* `bs_theme_dependencies()` now avoids unecessarily copying internal package files to R's temporary directory more than once when preparing precompiled theme dependencies (e.g. for a standard `bs_theme()` theme). (#1184)
Expand Down
225 changes: 225 additions & 0 deletions R/input-submit.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,225 @@
#' Suspend input changes until a button is clicked
#'
#' Suspend changes to a particular set of input controls until a submit button
#' is clicked. This is particularly useful for allowing the user to review their
#' input(s) before sending them to the server for a potentially expensive
#' operation. Note that, by default, all inputs that are children of the
#' button's parent are deferred until the button is clicked. This can be changed
#' by setting the `scope` argument to a CSS selector that matches the container
#' of inputs you wish to suspend.
#'
#' @param id The input ID.
#' @param label A label to place on the button.
#' @param ... Arguments passed along to [input_task_button()].
#' @param scope The scope of the submit button. Can be one of the following:
#' - `NULL`: Inputs that are children of the button's parent are
#' deferred until the button is clicked.
#' - A CSS selector: Only inputs that are within the element matching the
#' selector are deferred until the button is clicked.
#'
#' @seealso [input_submit_textarea()], [input_task_button()]
#' @export
input_submit_button <- function(id, label, ..., scope = NULL) {
if (!is_installed("shiny", "1.10.0.9001")) {
rlang::abort("shiny v1.10.0.9001 or higher is required")
}

btn <- input_task_button(id, label, ...)

# Change type from "button" to "submit"
btn$attribs$type <- "submit"

tagAppendAttributes(
btn,
class = "bslib-submit-button",
`data-submit-scope` = scope
)
}


# TODO: maybe update_task_button() should gain label/icon arguments
# and then we can just call that here? Or just tell people to use
# update_task_button() directly?

## @param id The input ID.
## @param ... Currently ignored.
## @param label The label of the button.
## @param icon An optional icon to display next to the label while the button
## is in ready state. See [fontawesome::fa_i()].
## @param session The `session` object; using the default is recommended.
## @rdname input_submit_button
## @export
#update_submit_button <- function(
# id,
# ...,
# label = NULL,
# icon = NULL,
# session = get_current_session()
#) {
#
#}

#' 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.
#'
#' @param id The input ID.
#' @param placeholder A character string giving the user a hint as to what can
#' be entered into the control.
#' @param value The initial input text. Note that, unlike [textAreaInput()],
#' this won't set a server-side value until the value is submitted.
#' @param button A [tags] element to use for the submit button. It's recommended
#' that this be a [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 a [ExtendedTask], this button can also be bound
#' to the task ([bind_task_button()]) and/or manually updated for more
#' accurate progress reporting ([update_task_button()]).
#' @param submit_key A character string indicating what keyboard event should
#' trigger the submit button. The default is `enter`, which will submit the
#' input when the user presses the Enter/Return key. The `enter+modifier`
#' option will submit the input when the user presses the Enter key while
#' holding down Ctrl/Cmd.
#'
#' @return A textarea input control that can be added to a UI definition.
#'
#' @seealso [input_submit_button()], [input_task_button()]
#'
#' @examplesIf rlang::is_interactive()
#'
#' ui <- page_fluid(
#' input_submit_textarea("text", "Enter some input..."),
#' verbatimTextOutput("value")
#' )
#' server <- function(input, output) {
#' output$value <- renderText({
#' req(input$text)
#' Sys.sleep(2)
#' paste("You entered:", input$text)
#' })
#' }
#' shinyApp(ui, server)
#'
#' @section Server value:
#' A character string of the text input. The default value is `""` even if
#' `value` is provided. The value will only be set/updated when the user submits
#' the input by pressing the Enter key or clicking the submit button.
#'
#' @export
input_submit_textarea <- function(
id,
placeholder,
value = "",
...,
button = NULL,
label = NULL,
width = "min(600px, 100%)",
submit_key = c("enter", "enter+modifier")
) {
if (!is_installed("shiny", "1.10.0.9001")) {
rlang::abort("shiny v1.10.0.9001 or higher is required")
}

rlang::check_dots_empty()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just realized we're not making it easy to add classes or styles anywhere in this component. Suppose, e.g. I want a tighter border radius on the text input, or to adjust the font in the text input component.

Since users can provide the button component as an argument, it might be nice to allow attributes in ... to be passed to tags$textarea()

Copy link
Collaborator Author

@cpsievert cpsievert Oct 7, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I ended up taking this in a direction that overlaps somewhat with the toolbar concept we discussed earlier (e9ef2eb), so you can now do something like:

input_submit_textarea(
  "text",
  "Your story",
  placeholder = "Once upon a time...",
  actionLink("about", "About")
)

to get

Screenshot 2025-10-07 at 5 12 10 PM


value <- shiny::restoreInput(id = id, default = value)
if (length(value) != 1 || !is.character(value)) {
stop("`value` must be a character string", call. = FALSE)
}

submit_key <- rlang::arg_match(submit_key)
needs_modifier <- isTRUE(submit_key == "enter+modifier")

if (is.null(button)) {
if (needs_modifier) {
btn_label <- "Submit ⌘ ⏎"
btn_title <- "Press ⌘ + Enter to Submit"
} else {
btn_label <- "Submit ⏎"
btn_title <- "Press Enter to Submit"
}

button <- input_task_button(
id = paste0(id, "_submit"),
class = "btn-sm",
label = btn_label,
title = btn_title,
`aria-label` = btn_title
)
}

if (!is_button_tag(button)) {
stop("`button` must be a `tags$button()`", call. = FALSE)
}

div(
class = "shiny-input-container bslib-mb-spacing",
style = css(
# TODO: validateCssUnit() needs to handle more complex CSS
width = if (is.numeric(width)) paste0(width, "px") else width,
),
shiny_input_label(id, label),
div(
class = "bslib-input-textsubmit",
tags$textarea(
id = id,
class = "textarea-autoresize form-control",
style = css(width = if (!is.null(width)) "100%"),
placeholder = placeholder,
`data-needs-modifier` = if (needs_modifier) "",
rows = 1,
value
),
button
)
)
}

is_button_tag <- function(x) {
if (!inherits(x, "shiny.tag")) {
return(FALSE)
}

isTRUE(x$name == "button") ||
isTRUE(x$attribs$type == "button")
}

#' @param value The value to set the user input to.
#' @param placeholder The placeholder text for the user input.
#' @param submit Whether to automatically submit the text for the user. Requires `value`.
#' @param focus Whether to move focus to the input element. Requires `value`.
#'
#' @rdname input_submit_textarea
#' @export
update_submit_textarea <- function(
id,
...,
value = NULL,
placeholder = NULL,
label = NULL,
submit = FALSE,
focus = FALSE,
session = get_current_session()
) {
rlang::check_dots_empty()

if (is.null(value) && (submit || focus)) {
stop(
"An input `value` must be provided when `submit` or `focus` are `TRUE`.",
call. = FALSE
)
}

message <- dropNulls(list(
value = value,
placeholder = placeholder,
label = label,
submit = submit,
focus = focus
))

session$sendInputMessage(id, message)
}
Binary file modified R/sysdata.rda
Binary file not shown.
12 changes: 12 additions & 0 deletions R/utils-shiny.R
Original file line number Diff line number Diff line change
Expand Up @@ -56,3 +56,15 @@ anyNamed <- function(x) {
if (is.null(nms)) return(FALSE)
any(nzchar(nms))
}


# Copy of shiny:::shinyInputLabel()
shiny_input_label <- function(id, label = NULL) {
tags$label(
label,
class = "control-label",
class = if (is.null(label)) "shiny-label-null",
id = paste0(id, "-label"),
`for` = id
)
}
2 changes: 1 addition & 1 deletion inst/components/dist/components.css

Large diffs are not rendered by default.

Loading