mirror of
https://github.com/rstudio/shiny.git
synced 2026-01-10 07:28:01 -05:00
textInput(), textAreaInput(), numericInput(), passwordInput(): allow updating value on blur (#4183)
* textInput: Add updateOn parameter and allow setting debounce delay * `devtools::document()` (GitHub Actions) * `yarn build` (GitHub Actions) * Update news * Remove debounce parameter * `devtools::document()` (GitHub Actions) * `yarn build` (GitHub Actions) * Add updateOn parameter to numericInput, passwordInput * Add updateOn to textAreaInput() * `devtools::document()` (GitHub Actions) * feat: Ignore change events unless from server messages when `updateOn="blur"` * refactor: `updateOn="change"` instead of `"input"` * feat: Update inputs on Enter or Cmd/Ctrl+Enter (textarea) * chore: Document `...` and ensure they are empty * chore: Use `rlang::arg_match()` * chore: code style (air format) * fix: textAreaInput, not inputTextArea * docs(NEWS): Minor edit * chore: If element has focus, ignore change event --------- Co-authored-by: wch <wch@users.noreply.github.com> Co-authored-by: Garrick Aden-Buie <garrick@adenbuie.com>
This commit is contained in:
3
NEWS.md
3
NEWS.md
@@ -11,6 +11,9 @@
|
||||
|
||||
* Shiny's Typescript assets are now compiled to ES2021 instead of ES5. (#4066)
|
||||
|
||||
* When `textInput()` is called with `updateOn="blur"`, instead of updating as the user types, the input value will update only when the text input loses focus or when the user presses Enter (or Cmd/Ctrl + Enter for `textAreaInput()`). (#4183)
|
||||
|
||||
|
||||
## Bug fixes
|
||||
|
||||
* Fixed a bug with modals where calling `removeModal()` too quickly after `showModal()` would fail to remove the modal if the remove modal message was received while the modal was in the process of being revealed. (#4173)
|
||||
|
||||
@@ -29,22 +29,36 @@
|
||||
#' A numeric vector of length 1.
|
||||
#'
|
||||
#' @export
|
||||
numericInput <- function(inputId, label, value, min = NA, max = NA, step = NA,
|
||||
width = NULL) {
|
||||
numericInput <- function(
|
||||
inputId,
|
||||
label,
|
||||
value,
|
||||
min = NA,
|
||||
max = NA,
|
||||
step = NA,
|
||||
width = NULL,
|
||||
...,
|
||||
updateOn = c("change", "blur")
|
||||
) {
|
||||
rlang::check_dots_empty()
|
||||
updateOn <- rlang::arg_match(updateOn)
|
||||
|
||||
value <- restoreInput(id = inputId, default = value)
|
||||
|
||||
# build input tag
|
||||
inputTag <- tags$input(id = inputId, type = "number", class="shiny-input-number form-control",
|
||||
value = formatNoSci(value))
|
||||
if (!is.na(min))
|
||||
inputTag$attribs$min = min
|
||||
if (!is.na(max))
|
||||
inputTag$attribs$max = max
|
||||
if (!is.na(step))
|
||||
inputTag$attribs$step = step
|
||||
inputTag <- tags$input(
|
||||
id = inputId,
|
||||
type = "number",
|
||||
class = "shiny-input-number form-control",
|
||||
value = formatNoSci(value),
|
||||
`data-update-on` = updateOn
|
||||
)
|
||||
if (!is.na(min)) inputTag$attribs$min = min
|
||||
if (!is.na(max)) inputTag$attribs$max = max
|
||||
if (!is.na(step)) inputTag$attribs$step = step
|
||||
|
||||
div(class = "form-group shiny-input-container",
|
||||
div(
|
||||
class = "form-group shiny-input-container",
|
||||
style = css(width = validateCssUnit(width)),
|
||||
shinyInputLabel(inputId, label),
|
||||
inputTag
|
||||
|
||||
@@ -30,12 +30,29 @@
|
||||
#' shinyApp(ui, server)
|
||||
#' }
|
||||
#' @export
|
||||
passwordInput <- function(inputId, label, value = "", width = NULL,
|
||||
placeholder = NULL) {
|
||||
div(class = "form-group shiny-input-container",
|
||||
passwordInput <- function(
|
||||
inputId,
|
||||
label,
|
||||
value = "",
|
||||
width = NULL,
|
||||
placeholder = NULL,
|
||||
...,
|
||||
updateOn = c("change", "blur")
|
||||
) {
|
||||
rlang::check_dots_empty()
|
||||
updateOn <- rlang::arg_match(updateOn)
|
||||
|
||||
div(
|
||||
class = "form-group shiny-input-container",
|
||||
style = css(width = validateCssUnit(width)),
|
||||
shinyInputLabel(inputId, label),
|
||||
tags$input(id = inputId, type="password", class="shiny-input-password form-control", value=value,
|
||||
placeholder = placeholder)
|
||||
tags$input(
|
||||
id = inputId,
|
||||
type = "password",
|
||||
class = "shiny-input-password form-control",
|
||||
value = value,
|
||||
placeholder = placeholder,
|
||||
`data-update-on` = updateOn
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -10,6 +10,14 @@
|
||||
#' @param placeholder A character string giving the user a hint as to what can
|
||||
#' be entered into the control. Internet Explorer 8 and 9 do not support this
|
||||
#' option.
|
||||
#' @param ... Ignored, included to require named arguments and for future
|
||||
#' feature expansion.
|
||||
#' @param updateOn A character vector specifying when the input should be
|
||||
#' updated. Options are `"change"` (default) and `"blur"`. Use `"change"` to
|
||||
#' update the input immediately whenever the value changes. Use `"blur"`to
|
||||
#' delay the input update until the input loses focus (the user moves away
|
||||
#' from the input), or when Enter is pressed (or Cmd/Ctrl + Enter for
|
||||
#' [textAreaInput()]).
|
||||
#' @return A text input control that can be added to a UI definition.
|
||||
#'
|
||||
#' @family input elements
|
||||
@@ -34,15 +42,31 @@
|
||||
#' unless `value` is provided.
|
||||
#'
|
||||
#' @export
|
||||
textInput <- function(inputId, label, value = "", width = NULL,
|
||||
placeholder = NULL) {
|
||||
textInput <- function(
|
||||
inputId,
|
||||
label,
|
||||
value = "",
|
||||
width = NULL,
|
||||
placeholder = NULL,
|
||||
...,
|
||||
updateOn = c("change", "blur")
|
||||
) {
|
||||
rlang::check_dots_empty()
|
||||
updateOn <- rlang::arg_match(updateOn)
|
||||
|
||||
value <- restoreInput(id = inputId, default = value)
|
||||
|
||||
div(class = "form-group shiny-input-container",
|
||||
div(
|
||||
class = "form-group shiny-input-container",
|
||||
style = css(width = validateCssUnit(width)),
|
||||
shinyInputLabel(inputId, label),
|
||||
tags$input(id = inputId, type="text", class="shiny-input-text form-control", value=value,
|
||||
placeholder = placeholder)
|
||||
tags$input(
|
||||
id = inputId,
|
||||
type = "text",
|
||||
class = "shiny-input-text form-control",
|
||||
value = value,
|
||||
placeholder = placeholder,
|
||||
`data-update-on` = updateOn
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -41,8 +41,21 @@
|
||||
#' unless `value` is provided.
|
||||
#'
|
||||
#' @export
|
||||
textAreaInput <- function(inputId, label, value = "", width = NULL, height = NULL,
|
||||
cols = NULL, rows = NULL, placeholder = NULL, resize = NULL) {
|
||||
textAreaInput <- function(
|
||||
inputId,
|
||||
label,
|
||||
value = "",
|
||||
width = NULL,
|
||||
height = NULL,
|
||||
cols = NULL,
|
||||
rows = NULL,
|
||||
placeholder = NULL,
|
||||
resize = NULL,
|
||||
...,
|
||||
updateOn = c("change", "blur")
|
||||
) {
|
||||
rlang::check_dots_empty()
|
||||
updateOn <- rlang::arg_match(updateOn)
|
||||
|
||||
value <- restoreInput(id = inputId, default = value)
|
||||
|
||||
@@ -57,7 +70,8 @@ textAreaInput <- function(inputId, label, value = "", width = NULL, height = NUL
|
||||
resize = resize
|
||||
)
|
||||
|
||||
div(class = "form-group shiny-input-container",
|
||||
div(
|
||||
class = "form-group shiny-input-container",
|
||||
shinyInputLabel(inputId, label),
|
||||
style = if (!is.null(width)) paste0("width: ", validateCssUnit(width), ";"),
|
||||
tags$textarea(
|
||||
@@ -67,6 +81,7 @@ textAreaInput <- function(inputId, label, value = "", width = NULL, height = NUL
|
||||
style = style,
|
||||
rows = rows,
|
||||
cols = cols,
|
||||
`data-update-on` = updateOn,
|
||||
value
|
||||
)
|
||||
)
|
||||
|
||||
@@ -1453,18 +1453,35 @@
|
||||
value;
|
||||
}
|
||||
subscribe(el, callback) {
|
||||
(0, import_jquery13.default)(el).on(
|
||||
"keyup.textInputBinding input.textInputBinding",
|
||||
function() {
|
||||
callback(true);
|
||||
}
|
||||
);
|
||||
(0, import_jquery13.default)(el).on(
|
||||
"change.textInputBinding",
|
||||
function() {
|
||||
const $el = (0, import_jquery13.default)(el);
|
||||
const updateOn = $el.data("update-on") || "change";
|
||||
if (updateOn === "change") {
|
||||
$el.on(
|
||||
"keyup.textInputBinding input.textInputBinding",
|
||||
function() {
|
||||
callback(true);
|
||||
}
|
||||
);
|
||||
} else if (updateOn === "blur") {
|
||||
$el.on("blur.textInputBinding", function() {
|
||||
callback(false);
|
||||
});
|
||||
$el.on("keydown.textInputBinding", function(event) {
|
||||
if (event.key !== "Enter")
|
||||
return;
|
||||
if ($el.is("textarea")) {
|
||||
if (!(event.ctrlKey || event.metaKey))
|
||||
return;
|
||||
}
|
||||
callback(false);
|
||||
});
|
||||
}
|
||||
$el.on("change.textInputBinding", function() {
|
||||
if (updateOn === "blur" && $el.is(":focus")) {
|
||||
return;
|
||||
}
|
||||
);
|
||||
callback(false);
|
||||
});
|
||||
}
|
||||
unsubscribe(el) {
|
||||
(0, import_jquery13.default)(el).off(".textInputBinding");
|
||||
|
||||
File diff suppressed because one or more lines are too long
30
inst/www/shared/shiny.min.js
vendored
30
inst/www/shared/shiny.min.js
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -11,7 +11,9 @@ numericInput(
|
||||
min = NA,
|
||||
max = NA,
|
||||
step = NA,
|
||||
width = NULL
|
||||
width = NULL,
|
||||
...,
|
||||
updateOn = c("change", "blur")
|
||||
)
|
||||
}
|
||||
\arguments{
|
||||
@@ -29,6 +31,16 @@ numericInput(
|
||||
|
||||
\item{width}{The width of the input, e.g. \code{'400px'}, or \code{'100\%'};
|
||||
see \code{\link[=validateCssUnit]{validateCssUnit()}}.}
|
||||
|
||||
\item{...}{Ignored, included to require named arguments and for future
|
||||
feature expansion.}
|
||||
|
||||
\item{updateOn}{A character vector specifying when the input should be
|
||||
updated. Options are \code{"change"} (default) and \code{"blur"}. Use \code{"change"} to
|
||||
update the input immediately whenever the value changes. Use \code{"blur"}to
|
||||
delay the input update until the input loses focus (the user moves away
|
||||
from the input), or when Enter is pressed (or Cmd/Ctrl + Enter for
|
||||
\code{\link[=textAreaInput]{textAreaInput()}}).}
|
||||
}
|
||||
\value{
|
||||
A numeric input control that can be added to a UI definition.
|
||||
|
||||
@@ -4,7 +4,15 @@
|
||||
\alias{passwordInput}
|
||||
\title{Create a password input control}
|
||||
\usage{
|
||||
passwordInput(inputId, label, value = "", width = NULL, placeholder = NULL)
|
||||
passwordInput(
|
||||
inputId,
|
||||
label,
|
||||
value = "",
|
||||
width = NULL,
|
||||
placeholder = NULL,
|
||||
...,
|
||||
updateOn = c("change", "blur")
|
||||
)
|
||||
}
|
||||
\arguments{
|
||||
\item{inputId}{The \code{input} slot that will be used to access the value.}
|
||||
@@ -19,6 +27,16 @@ see \code{\link[=validateCssUnit]{validateCssUnit()}}.}
|
||||
\item{placeholder}{A character string giving the user a hint as to what can
|
||||
be entered into the control. Internet Explorer 8 and 9 do not support this
|
||||
option.}
|
||||
|
||||
\item{...}{Ignored, included to require named arguments and for future
|
||||
feature expansion.}
|
||||
|
||||
\item{updateOn}{A character vector specifying when the input should be
|
||||
updated. Options are \code{"change"} (default) and \code{"blur"}. Use \code{"change"} to
|
||||
update the input immediately whenever the value changes. Use \code{"blur"}to
|
||||
delay the input update until the input loses focus (the user moves away
|
||||
from the input), or when Enter is pressed (or Cmd/Ctrl + Enter for
|
||||
\code{\link[=textAreaInput]{textAreaInput()}}).}
|
||||
}
|
||||
\value{
|
||||
A text input control that can be added to a UI definition.
|
||||
|
||||
@@ -13,7 +13,9 @@ textAreaInput(
|
||||
cols = NULL,
|
||||
rows = NULL,
|
||||
placeholder = NULL,
|
||||
resize = NULL
|
||||
resize = NULL,
|
||||
...,
|
||||
updateOn = c("change", "blur")
|
||||
)
|
||||
}
|
||||
\arguments{
|
||||
@@ -46,6 +48,16 @@ option.}
|
||||
\item{resize}{Which directions the textarea box can be resized. Can be one of
|
||||
\code{"both"}, \code{"none"}, \code{"vertical"}, and \code{"horizontal"}. The default, \code{NULL},
|
||||
will use the client browser's default setting for resizing textareas.}
|
||||
|
||||
\item{...}{Ignored, included to require named arguments and for future
|
||||
feature expansion.}
|
||||
|
||||
\item{updateOn}{A character vector specifying when the input should be
|
||||
updated. Options are \code{"change"} (default) and \code{"blur"}. Use \code{"change"} to
|
||||
update the input immediately whenever the value changes. Use \code{"blur"}to
|
||||
delay the input update until the input loses focus (the user moves away
|
||||
from the input), or when Enter is pressed (or Cmd/Ctrl + Enter for
|
||||
\code{\link[=textAreaInput]{textAreaInput()}}).}
|
||||
}
|
||||
\value{
|
||||
A textarea input control that can be added to a UI definition.
|
||||
|
||||
@@ -4,7 +4,15 @@
|
||||
\alias{textInput}
|
||||
\title{Create a text input control}
|
||||
\usage{
|
||||
textInput(inputId, label, value = "", width = NULL, placeholder = NULL)
|
||||
textInput(
|
||||
inputId,
|
||||
label,
|
||||
value = "",
|
||||
width = NULL,
|
||||
placeholder = NULL,
|
||||
...,
|
||||
updateOn = c("change", "blur")
|
||||
)
|
||||
}
|
||||
\arguments{
|
||||
\item{inputId}{The \code{input} slot that will be used to access the value.}
|
||||
@@ -19,6 +27,16 @@ see \code{\link[=validateCssUnit]{validateCssUnit()}}.}
|
||||
\item{placeholder}{A character string giving the user a hint as to what can
|
||||
be entered into the control. Internet Explorer 8 and 9 do not support this
|
||||
option.}
|
||||
|
||||
\item{...}{Ignored, included to require named arguments and for future
|
||||
feature expansion.}
|
||||
|
||||
\item{updateOn}{A character vector specifying when the input should be
|
||||
updated. Options are \code{"change"} (default) and \code{"blur"}. Use \code{"change"} to
|
||||
update the input immediately whenever the value changes. Use \code{"blur"}to
|
||||
delay the input update until the input loses focus (the user moves away
|
||||
from the input), or when Enter is pressed (or Cmd/Ctrl + Enter for
|
||||
\code{\link[=textAreaInput]{textAreaInput()}}).}
|
||||
}
|
||||
\value{
|
||||
A text input control that can be added to a UI definition.
|
||||
|
||||
@@ -50,21 +50,38 @@ class TextInputBindingBase extends InputBinding {
|
||||
}
|
||||
|
||||
subscribe(el: TextHTMLElement, callback: (x: boolean) => void): void {
|
||||
$(el).on(
|
||||
"keyup.textInputBinding input.textInputBinding",
|
||||
// event: Event
|
||||
function () {
|
||||
callback(true);
|
||||
}
|
||||
);
|
||||
$(el).on(
|
||||
"change.textInputBinding",
|
||||
// event: Event
|
||||
function () {
|
||||
const $el = $(el);
|
||||
const updateOn = $el.data("update-on") || "change";
|
||||
|
||||
if (updateOn === "change") {
|
||||
$el.on(
|
||||
"keyup.textInputBinding input.textInputBinding",
|
||||
// event: Event
|
||||
function () {
|
||||
callback(true);
|
||||
}
|
||||
);
|
||||
} else if (updateOn === "blur") {
|
||||
$el.on("blur.textInputBinding", function () {
|
||||
callback(false);
|
||||
});
|
||||
$el.on("keydown.textInputBinding", function (event: JQuery.Event) {
|
||||
if (event.key !== "Enter") return;
|
||||
if ($el.is("textarea")) {
|
||||
if (!(event.ctrlKey || event.metaKey)) return;
|
||||
}
|
||||
callback(false);
|
||||
});
|
||||
}
|
||||
|
||||
$el.on("change.textInputBinding", function () {
|
||||
if (updateOn === "blur" && $el.is(":focus")) {
|
||||
return;
|
||||
}
|
||||
);
|
||||
callback(false);
|
||||
});
|
||||
}
|
||||
|
||||
unsubscribe(el: TextHTMLElement): void {
|
||||
$(el).off(".textInputBinding");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user