Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
3 changes: 2 additions & 1 deletion DESCRIPTION
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ Suggests:
magrittr,
rappdirs,
rmarkdown (>= 2.7),
shiny (> 1.8.1),
shiny (>= 1.11.1),
testthat,
thematic,
tools,
Expand Down Expand Up @@ -99,6 +99,7 @@ Collate:
'fill.R'
'imports.R'
'input-dark-mode.R'
'input-submit.R'
'input-switch.R'
'layout.R'
'nav-items.R'
Expand Down
2 changes: 2 additions & 0 deletions NAMESPACE
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ export(font_face)
export(font_google)
export(font_link)
export(input_dark_mode)
export(input_submit_textarea)
export(input_switch)
export(input_task_button)
export(is.card_item)
Expand Down Expand Up @@ -153,6 +154,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
4 changes: 4 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# bslib (development version)

## New features

* Added a new `input_submit_textarea()` input element, which is similar 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.

## Improvements and bug fixes

* `bs_theme_dependencies()` now avoids unnecessarily 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
191 changes: 191 additions & 0 deletions R/input-submit.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
#' 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.
#'
#' @section Server value:
#' The server receives a character string containing the user's text input.
#'
#' **Important:** The initial server value is always `""` (empty string),
#' regardless of any `value` parameter provided to `input_submit_textarea()`.
#' The server value updates only when the user explicitly submits the input by
#' either pressing the Enter key (possibly with a modifier key) or clicking the
#' submit button.
#'
#' @param id The input ID.
#' @param label The label to display above the input control. If `NULL`, no
#' label is displayed.
#' @param ... Additional attributes to apply to the underlying `<textarea>`
#' element (e.g., spellcheck, autocomplete, etc).
#' @param 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.
#' @param value The initial input text. Note that, unlike [shiny::textAreaInput()],
#' this won't set a server-side value until the value is explicitly submitted.
#' @param width Any valid CSS unit (e.g., `width="100%"`).
#' @param 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.
#' @param button A [htmltools::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 [shiny::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 toolbar A list of optional UI elements (e.g., links, icons) to
#' display next to the submit button.
#' @param submit_key A character 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.
#'
#' @return A textarea input control that can be added to a UI definition.
#' @seealso [update_submit_textarea()], [input_task_button()]
#'
#' @examplesIf rlang::is_interactive()
#' library(shiny)
#' library(bslib)
#'
#' ui <- page_fluid(
#' input_submit_textarea("text", placeholder = "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)
#'
#' @export
input_submit_textarea <- function(
id,
label = NULL,
...,
placeholder = NULL,
value = "",
width = "min(680px, 100%)",
rows = 1,
button = NULL,
toolbar = NULL,
submit_key = c("enter+modifier", "enter")
) {
rlang::check_installed("shiny", version = "1.11.1")
args <- rlang::list2(...)
if (any_unnamed(args)) {
abort(c(
"All `...` arguments must be named",
"i" = "Did you mean to pass UI elements to `toolbar`?"
))
}

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

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

if (is.null(button)) {
button <- input_task_button(
id = paste0(id, "_submit"),
class = "btn-sm",
label = span(class = "bslib-submit-key", "\U23CE"),
icon = "Submit",
label_busy = div(
class = "spinner-border spinner-border-sm ms-2",
role = "status",
span(class = "visually-hidden", "Processing...")
),
icon_busy = "Submit",
title = "Press Enter to Submit",
`aria-label` = "Press Enter to Submit"
)
}

if (!is_button_tag(button)) {
abort("`button` must be a `tags$button()`")
}

button <- tagAppendAttributes(button, class = "bslib-submit-textarea-btn")

div(
class = "bslib-input-submit-textarea 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-submit-textarea-container",
tags$textarea(
id = id,
class = "form-control",
style = css(width = if (!is.null(width)) "100%"),
placeholder = placeholder,
`data-needs-modifier` = if (needs_modifier) "",
rows = rows,
!!!args,
value
),
tags$footer(
div(toolbar, class = "bslib-toolbar"),
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`.
#' @param session The `session` object; using the default is recommended.
#'
#' @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 = if (!is.null(label)) processDeps(label, session),
submit = submit,
focus = focus
))

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


shiny_input_label <- function(id, label = NULL) {
getFromNamespace("shinyInputLabel", "shiny")(id, label)
}
2 changes: 1 addition & 1 deletion inst/components/dist/components.css

Large diffs are not rendered by default.

Loading