diff --git a/DESCRIPTION b/DESCRIPTION index fe924ac8d..c20469d01 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -52,7 +52,7 @@ Suggests: magrittr, rappdirs, rmarkdown (>= 2.7), - shiny (> 1.8.1), + shiny (>= 1.11.1), testthat, thematic, tools, @@ -99,6 +99,7 @@ Collate: 'fill.R' 'imports.R' 'input-dark-mode.R' + 'input-submit.R' 'input-switch.R' 'layout.R' 'nav-items.R' diff --git a/NAMESPACE b/NAMESPACE index 562078182..27d4445aa 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -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) @@ -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) diff --git a/NEWS.md b/NEWS.md index 1480a0e71..b9d05c483 100644 --- a/NEWS.md +++ b/NEWS.md @@ -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) diff --git a/R/input-submit.R b/R/input-submit.R new file mode 100644 index 000000000..1b7745f28 --- /dev/null +++ b/R/input-submit.R @@ -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 ` + + + + +--- + + Code + input_submit_textarea("test", label = "Enter text", placeholder = "Type here...", + value = "Initial value", width = 300, rows = 3, button = tags$button(id = "custom_submit", + class = "btn btn-primary", "Send"), toolbar = tagList(tags$span("Press"), + tags$kbd("Enter"), tags$span("to submit")), submit_key = "enter", spellcheck = "false", + autocomplete = "off", ) + Output +
+ +
+ + +
+
+ +# input_submit_textarea() validation errors + + Code + input_submit_textarea("test", "With Children", div()) + Condition + Error in `input_submit_textarea()`: + ! All `...` arguments must be named + i Did you mean to pass UI elements to `toolbar`? + +--- + + Code + input_submit_textarea("test", value = 123) + Condition + Error in `input_submit_textarea()`: + ! `value` must be a character string + +--- + + Code + input_submit_textarea("test", value = c("a", "b")) + Condition + Error in `input_submit_textarea()`: + ! `value` must be a character string + +--- + + Code + input_submit_textarea("test", button = tags$div("Not a button")) + Condition + Error in `input_submit_textarea()`: + ! `button` must be a `tags$button()` + +--- + + Code + input_submit_textarea("test", submit_key = "invalid") + Condition + Error in `input_submit_textarea()`: + ! `submit_key` must be one of "enter+modifier" or "enter", not "invalid". + diff --git a/tests/testthat/test-input-submit.R b/tests/testthat/test-input-submit.R new file mode 100644 index 000000000..d2e09302c --- /dev/null +++ b/tests/testthat/test-input-submit.R @@ -0,0 +1,54 @@ +test_that("input_submit_textarea() markup snapshots", { + expect_snapshot(input_submit_textarea("test")) + + expect_snapshot( + input_submit_textarea( + "test", + label = "Enter text", + placeholder = "Type here...", + value = "Initial value", + width = 300, + rows = 3, + button = tags$button( + id = "custom_submit", + class = "btn btn-primary", + "Send" + ), + toolbar = tagList( + tags$span("Press"), + tags$kbd("Enter"), + tags$span("to submit") + ), + submit_key = "enter", + spellcheck = "false", + autocomplete = "off", + ) + ) +}) + +test_that("input_submit_textarea() validation errors", { + expect_snapshot( + error = TRUE, + input_submit_textarea("test", "With Children", div()) + ) + + expect_snapshot( + error = TRUE, + input_submit_textarea("test", value = 123) + ) + + expect_snapshot( + error = TRUE, + input_submit_textarea("test", value = c("a", "b")) + ) + + expect_snapshot( + error = TRUE, + input_submit_textarea("test", button = tags$div("Not a button")) + ) + + expect_snapshot( + error = TRUE, + input_submit_textarea("test", submit_key = "invalid") + ) +}) diff --git a/yarn.lock b/yarn.lock index 191452d46..560844ee2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -302,10 +302,10 @@ dependencies: "@types/sizzle" "*" -"@types/jquery@3.5.14": - version "3.5.14" - resolved "https://registry.yarnpkg.com/@types/jquery/-/jquery-3.5.14.tgz#ac8e11ee591e94d4d58da602cb3a5a8320dee577" - integrity sha512-X1gtMRMbziVQkErhTQmSe2jFwwENA/Zr+PprCkF63vFq+Yt5PZ4AlKqgmeNlwgn7dhsXEK888eIW2520EpC+xg== +"@types/jquery@3.5.32": + version "3.5.32" + resolved "https://registry.yarnpkg.com/@types/jquery/-/jquery-3.5.32.tgz#3eb0da20611b92c7c49ebed6163b52a4fdc57def" + integrity sha512-b9Xbf4CkMqS02YH8zACqN1xzdxc3cO735Qe5AbSUFmyOiaWAbcpqh9Wna+Uk0vgACvoQHpWDg2rGdHkYPLmCiQ== dependencies: "@types/sizzle" "*" @@ -325,14 +325,14 @@ integrity sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw== "@types/rstudio-shiny@https://github.com/rstudio/shiny#main": - version "1.8.1-alpha.9001" - resolved "https://github.com/rstudio/shiny#15b5fa6c01197a1c9fca33ff103eeb7250ffa870" + version "1.10.0-alpha.9001" + resolved "https://github.com/rstudio/shiny#b25e6feabb04805c3e65ef41604db2b7726a0293" dependencies: "@types/bootstrap" "3.4.0" "@types/bootstrap-datepicker" "0.0.14" "@types/datatables.net" "^1.10.19" "@types/ion-rangeslider" "2.3.0" - "@types/jquery" "3.5.14" + "@types/jquery" "3.5.32" "@types/selectize" "0.12.34" lit "^3.0.0"