mirror of
https://github.com/rstudio/shiny.git
synced 2026-01-12 00:19:06 -05:00
Compare commits
3 Commits
fix-news
...
feat/input
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5d563a00eb | ||
|
|
d4bf6aaab9 | ||
|
|
49c5d29003 |
@@ -60,7 +60,7 @@ Authors@R: c(
|
||||
comment = "showdown.js library"),
|
||||
person("Ivan", "Sagalaev", role = c("ctb", "cph"),
|
||||
comment = "highlight.js library"),
|
||||
person(given = "R Core Team", role = c("ctb", "cph"),
|
||||
person(family = "R Core Team", role = c("ctb", "cph"),
|
||||
comment = "tar implementation from R")
|
||||
)
|
||||
Description: Makes it incredibly easy to build interactive web
|
||||
@@ -107,7 +107,6 @@ Suggests:
|
||||
reactlog (>= 1.0.0),
|
||||
magrittr,
|
||||
yaml,
|
||||
mirai,
|
||||
future,
|
||||
dygraphs,
|
||||
ragg,
|
||||
|
||||
15
NEWS.md
15
NEWS.md
@@ -4,13 +4,13 @@
|
||||
|
||||
* `textInput()`, `textAreaInput()`, `numericInput()` and `passwordInput()` all gain an `updateOn` option. `updateOn = "change"` is the default and previous behavior, where the input value updates immediately whenever the value changes. With `updateOn = "blur"`, 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)
|
||||
|
||||
* `textAreaInput()` gains a `autoresize` option, which automatically resizes the text area to fit its content. (#4210)
|
||||
* `textAreaInput()` gains a `autoresize` option, which automatically resizes the text area to fit its content.
|
||||
|
||||
* The family of `update*Input()` functions can now render HTML content passed to the `label` argument (e.g., `updateInputText(label = tags$b("New label"))`). (#3996)
|
||||
* The `callback` argument of Shiny.js' `InputBinding.subscribe()` method gains support for a value of `"event"`. This makes it possible for an input binding to use event priority when updating the value (i.e., send immediately and always resend, even if the value hasn't changed).
|
||||
|
||||
## Changes
|
||||
|
||||
* Shiny no longer suspends input changes when _any_ `<input type="submit">` or `<button type="submit">` is on the page. Instead, it now only suspends when a `submitButton()` is present. If you have reason for creating a submit button from custom HTML, add a CSS class of `shiny-submit-button` to the button. (#4209)
|
||||
* Shiny no longer suspends input changes when _any_ `<input type="submit">` or `<button type="submit">` is on the page. Instead, it now only suspends when a `submitButton()` is present. If you have reason for creating a submit button from custom HTML, add a CSS class of `shiny-submit-button` to the button.
|
||||
|
||||
## Improvements
|
||||
|
||||
@@ -25,10 +25,6 @@
|
||||
|
||||
* Shiny's Typescript assets are now compiled to ES2021 instead of ES5. (#4066)
|
||||
|
||||
* `ExtendedTask` now catches synchronous values and errors and returns them via `$result()`. Previously, the extended task function was required to always return a promise. This change makes it easier to use `ExtendedTask` with a function that may return early or do some synchronous work before returning a promise. (#4225)
|
||||
|
||||
* `renderPlot()` was updated to accomodate changes in ggplot2 v4.0.0. (#4226)
|
||||
|
||||
## 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)
|
||||
@@ -37,10 +33,6 @@
|
||||
|
||||
* Updated the JavaScript used when inserting a tab to avoid rendering dynamic UI elements twice when adding the new tab via `insertTab()` or `bslib::nav_insert()`. (#4179)
|
||||
|
||||
* Fixed an issue with `ExtendedTask` where synchronous errors would cause an error that would stop the current session. (#4225)
|
||||
|
||||
* `shiny::shinyAppTemplate()` no longer errors without a call to `library(shiny)`. (#3870)
|
||||
|
||||
# shiny 1.10.0
|
||||
|
||||
## New features and improvements
|
||||
@@ -200,6 +192,7 @@ In addition, various properties of the spinners and pulse can be customized with
|
||||
|
||||
* Fixed #3833: When `width` is provided to `textAreaInput()`, we now correctly set the width of the `<textarea>` element. (#3838)
|
||||
|
||||
|
||||
# shiny 1.7.4.1
|
||||
|
||||
## Full changelog
|
||||
|
||||
@@ -231,8 +231,8 @@ utils::globalVariables(".GenericCallEnv", add = TRUE)
|
||||
#' promises, but rather objects provided by the
|
||||
#' \href{https://rstudio.github.io/promises/}{\pkg{promises}} package, which
|
||||
#' are similar to promises in JavaScript. (See [promises::promise()] for more
|
||||
#' information.) You can also use [mirai::mirai()] or [future::future()]
|
||||
#' objects to run code in a separate process or even on a remote machine.
|
||||
#' information.) You can also use [future::future()] objects to run code in a
|
||||
#' separate process or even on a remote machine.
|
||||
#'
|
||||
#' If the value returns a promise, then anything that consumes the cached
|
||||
#' reactive must expect it to return a promise.
|
||||
|
||||
@@ -75,18 +75,6 @@ getCallNames <- function(calls) {
|
||||
})
|
||||
}
|
||||
|
||||
# A stripped down version of getCallNames() that intentionally avoids deparsing expressions.
|
||||
# Instead, it leaves expressions to be directly `rlang::hash()` (for de-duplication), which
|
||||
# is much faster than deparsing then hashing.
|
||||
getCallNamesForHash <- function(calls) {
|
||||
lapply(calls, function(call) {
|
||||
name <- call[[1L]]
|
||||
if (is.function(name)) return("<Anonymous>")
|
||||
if (typeof(name) == "promise") return("<Promise>")
|
||||
name
|
||||
})
|
||||
}
|
||||
|
||||
getLocs <- function(calls) {
|
||||
vapply(calls, function(call) {
|
||||
srcref <- attr(call, "srcref", exact = TRUE)
|
||||
@@ -156,7 +144,7 @@ getCallStackDigest <- function(callStack, warn = FALSE) {
|
||||
)
|
||||
}
|
||||
|
||||
rlang::hash(getCallNamesForHash(callStack))
|
||||
rlang::hash(getCallNames(callStack))
|
||||
}
|
||||
|
||||
saveCallStackDigest <- function(callStack) {
|
||||
|
||||
@@ -41,15 +41,12 @@
|
||||
#' is, a function that quickly returns a promise) and allows even that very
|
||||
#' session to immediately unblock and carry on with other user interactions.
|
||||
#'
|
||||
#' @examplesIf rlang::is_interactive() && rlang::is_installed("mirai")
|
||||
#' @examplesIf rlang::is_interactive() && rlang::is_installed("future")
|
||||
#'
|
||||
#' library(shiny)
|
||||
#' library(bslib)
|
||||
#' library(mirai)
|
||||
#'
|
||||
#' # Set background processes for running tasks
|
||||
#' daemons(1)
|
||||
#' # Reset when the app is stopped
|
||||
#' onStop(function() daemons(0))
|
||||
#' library(future)
|
||||
#' plan(multisession)
|
||||
#'
|
||||
#' ui <- page_fluid(
|
||||
#' titlePanel("Extended Task Demo"),
|
||||
@@ -63,12 +60,13 @@
|
||||
#'
|
||||
#' server <- function(input, output) {
|
||||
#' rand_task <- ExtendedTask$new(function() {
|
||||
#' mirai(
|
||||
#' future(
|
||||
#' {
|
||||
#' # Slow operation goes here
|
||||
#' Sys.sleep(2)
|
||||
#' sample(1:100, 1)
|
||||
#' }
|
||||
#' },
|
||||
#' seed = TRUE
|
||||
#' )
|
||||
#' })
|
||||
#'
|
||||
@@ -102,12 +100,11 @@ ExtendedTask <- R6Class("ExtendedTask", portable = TRUE, cloneable = FALSE,
|
||||
#' @param func The long-running operation to execute. This should be an
|
||||
#' asynchronous function, meaning, it should use the
|
||||
#' [\{promises\}](https://rstudio.github.io/promises/) package, most
|
||||
#' likely in conjunction with the
|
||||
#' [\{mirai\}](https://mirai.r-lib.org) or
|
||||
#' likely in conjuction with the
|
||||
#' [\{future\}](https://rstudio.github.io/promises/articles/promises_04_futures.html)
|
||||
#' package. (In short, the return value of `func` should be a
|
||||
#' [`mirai`][mirai::mirai()], [`Future`][future::future()], `promise`,
|
||||
#' or something else that [promises::as.promise()] understands.)
|
||||
#' [`Future`][future::future()] object, or a `promise`, or something else
|
||||
#' that [promises::as.promise()] understands.)
|
||||
#'
|
||||
#' It's also important that this logic does not read from any
|
||||
#' reactive inputs/sources, as inputs may change after the function is
|
||||
@@ -133,15 +130,14 @@ ExtendedTask <- R6Class("ExtendedTask", portable = TRUE, cloneable = FALSE,
|
||||
#' arguments.
|
||||
invoke = function(...) {
|
||||
args <- rlang::dots_list(..., .ignore_empty = "none")
|
||||
call <- rlang::caller_call(n = 0)
|
||||
|
||||
if (
|
||||
isolate(private$rv_status()) == "running" ||
|
||||
private$invocation_queue$size() > 0
|
||||
) {
|
||||
private$invocation_queue$add(list(args = args, call = call))
|
||||
private$invocation_queue$add(args)
|
||||
} else {
|
||||
private$do_invoke(args, call = call)
|
||||
private$do_invoke(args)
|
||||
}
|
||||
invisible(NULL)
|
||||
},
|
||||
@@ -208,41 +204,44 @@ ExtendedTask <- R6Class("ExtendedTask", portable = TRUE, cloneable = FALSE,
|
||||
rv_error = NULL,
|
||||
invocation_queue = NULL,
|
||||
|
||||
do_invoke = function(args, call = NULL) {
|
||||
do_invoke = function(args) {
|
||||
private$rv_status("running")
|
||||
private$rv_value(NULL)
|
||||
private$rv_error(NULL)
|
||||
|
||||
p <- promises::promise_resolve(
|
||||
maskReactiveContext(do.call(private$func, args))
|
||||
)
|
||||
|
||||
p <- promises::then(
|
||||
p,
|
||||
onFulfilled = function(value, .visible) {
|
||||
private$on_success(list(value = value, visible = .visible))
|
||||
},
|
||||
onRejected = function(error) {
|
||||
private$on_error(error, call = call)
|
||||
}
|
||||
)
|
||||
|
||||
promises::finally(p, onFinally = function() {
|
||||
if (private$invocation_queue$size() > 0) {
|
||||
next_call <- private$invocation_queue$remove()
|
||||
private$do_invoke(next_call$args, next_call$call)
|
||||
}
|
||||
p <- NULL
|
||||
tryCatch({
|
||||
maskReactiveContext({
|
||||
# TODO: Bounce the do.call off of a promise_resolve(), so that the
|
||||
# call to invoke() always returns immediately?
|
||||
result <- do.call(private$func, args)
|
||||
p <- promises::as.promise(result)
|
||||
})
|
||||
}, error = function(e) {
|
||||
private$on_error(e)
|
||||
})
|
||||
|
||||
promises::finally(
|
||||
promises::then(p,
|
||||
onFulfilled = function(value, .visible) {
|
||||
private$on_success(list(value=value, visible=.visible))
|
||||
},
|
||||
onRejected = function(error) {
|
||||
private$on_error(error)
|
||||
}
|
||||
),
|
||||
onFinally = function() {
|
||||
if (private$invocation_queue$size() > 0) {
|
||||
private$do_invoke(private$invocation_queue$remove())
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
invisible(NULL)
|
||||
},
|
||||
|
||||
on_error = function(err, call = NULL) {
|
||||
cli::cli_warn(
|
||||
"ERROR: An error occurred when invoking the ExtendedTask.",
|
||||
parent = err,
|
||||
call = call
|
||||
)
|
||||
on_error = function(err) {
|
||||
private$rv_status("error")
|
||||
private$rv_error(err)
|
||||
},
|
||||
|
||||
@@ -57,7 +57,7 @@ submitButton <- function(text = "Apply Changes", icon = NULL, width = NULL) {
|
||||
div(
|
||||
tags$button(
|
||||
type="submit",
|
||||
class="btn btn-primary shiny-submit-button",
|
||||
class="btn btn-primary submit-button",
|
||||
style = css(width = validateCssUnit(width)),
|
||||
list(icon, text)
|
||||
)
|
||||
|
||||
@@ -66,7 +66,7 @@ textAreaInput <- function(
|
||||
resize <- match.arg(resize, c("both", "none", "vertical", "horizontal"))
|
||||
}
|
||||
|
||||
classes <- "form-control"
|
||||
classes <- c("shiny-input-textarea", "form-control")
|
||||
if (autoresize) {
|
||||
classes <- c(classes, "textarea-autoresize")
|
||||
if (is.null(rows)) {
|
||||
@@ -75,7 +75,7 @@ textAreaInput <- function(
|
||||
}
|
||||
|
||||
div(
|
||||
class = "shiny-input-textarea form-group shiny-input-container",
|
||||
class = "form-group shiny-input-container",
|
||||
style = css(width = validateCssUnit(width)),
|
||||
shinyInputLabel(inputId, label),
|
||||
tags$textarea(
|
||||
|
||||
@@ -266,8 +266,6 @@ drawPlot <- function(name, session, func, width, height, alt, pixelratio, res, .
|
||||
# addition to ggplot, and there's a print method for that class, that we
|
||||
# won't override that method. https://github.com/rstudio/shiny/issues/841
|
||||
print.ggplot <- custom_print.ggplot
|
||||
# For compatibility with ggplot2 >v4.0.0
|
||||
`print.ggplot2::ggplot` <- custom_print.ggplot
|
||||
|
||||
# Use capture.output to squelch printing to the actual console; we
|
||||
# are only interested in plot output
|
||||
|
||||
@@ -37,11 +37,7 @@
|
||||
updateTextInput <- function(session = getDefaultReactiveDomain(), inputId, label = NULL, value = NULL, placeholder = NULL) {
|
||||
validate_session_object(session)
|
||||
|
||||
message <- dropNulls(list(
|
||||
label = processDeps(label, session),
|
||||
value = value,
|
||||
placeholder = placeholder
|
||||
))
|
||||
message <- dropNulls(list(label=label, value=value, placeholder=placeholder))
|
||||
session$sendInputMessage(inputId, message)
|
||||
}
|
||||
|
||||
@@ -115,10 +111,7 @@ updateTextAreaInput <- updateTextInput
|
||||
updateCheckboxInput <- function(session = getDefaultReactiveDomain(), inputId, label = NULL, value = NULL) {
|
||||
validate_session_object(session)
|
||||
|
||||
message <- dropNulls(list(
|
||||
label = processDeps(label, session),
|
||||
value = value
|
||||
))
|
||||
message <- dropNulls(list(label=label, value=value))
|
||||
session$sendInputMessage(inputId, message)
|
||||
}
|
||||
|
||||
@@ -182,17 +175,13 @@ updateActionButton <- function(session = getDefaultReactiveDomain(), inputId, la
|
||||
validate_session_object(session)
|
||||
|
||||
if (!is.null(icon)) icon <- as.character(validateIcon(icon))
|
||||
message <- dropNulls(list(
|
||||
label = processDeps(label, session),
|
||||
icon = icon,
|
||||
disabled = disabled
|
||||
))
|
||||
message <- dropNulls(list(label=label, icon=icon, disabled=disabled))
|
||||
session$sendInputMessage(inputId, message)
|
||||
}
|
||||
#' @rdname updateActionButton
|
||||
#' @export
|
||||
updateActionLink <- function(session = getDefaultReactiveDomain(), inputId, label = NULL, icon = NULL) {
|
||||
updateActionButton(session, inputId = inputId, label = processDeps(label, session), icon = icon)
|
||||
updateActionButton(session, inputId=inputId, label=label, icon=icon)
|
||||
}
|
||||
|
||||
|
||||
@@ -236,12 +225,7 @@ updateDateInput <- function(session = getDefaultReactiveDomain(), inputId, label
|
||||
min <- dateYMD(min, "min")
|
||||
max <- dateYMD(max, "max")
|
||||
|
||||
message <- dropNulls(list(
|
||||
label = processDeps(label, session),
|
||||
value = value,
|
||||
min = min,
|
||||
max = max
|
||||
))
|
||||
message <- dropNulls(list(label=label, value=value, min=min, max=max))
|
||||
session$sendInputMessage(inputId, message)
|
||||
}
|
||||
|
||||
@@ -291,7 +275,7 @@ updateDateRangeInput <- function(session = getDefaultReactiveDomain(), inputId,
|
||||
max <- dateYMD(max, "max")
|
||||
|
||||
message <- dropNulls(list(
|
||||
label = processDeps(label, session),
|
||||
label = label,
|
||||
value = dropNulls(list(start = start, end = end)),
|
||||
min = min,
|
||||
max = max
|
||||
@@ -390,16 +374,13 @@ updateNavlistPanel <- updateTabsetPanel
|
||||
#' }
|
||||
#' @export
|
||||
updateNumericInput <- function(session = getDefaultReactiveDomain(), inputId, label = NULL, value = NULL,
|
||||
min = NULL, max = NULL, step = NULL) {
|
||||
min = NULL, max = NULL, step = NULL) {
|
||||
|
||||
validate_session_object(session)
|
||||
|
||||
message <- dropNulls(list(
|
||||
label = processDeps(label, session),
|
||||
value = formatNoSci(value),
|
||||
min = formatNoSci(min),
|
||||
max = formatNoSci(max),
|
||||
step = formatNoSci(step)
|
||||
label = label, value = formatNoSci(value),
|
||||
min = formatNoSci(min), max = formatNoSci(max), step = formatNoSci(step)
|
||||
))
|
||||
session$sendInputMessage(inputId, message)
|
||||
}
|
||||
@@ -479,7 +460,7 @@ updateSliderInput <- function(session = getDefaultReactiveDomain(), inputId, lab
|
||||
}
|
||||
|
||||
message <- dropNulls(list(
|
||||
label = processDeps(label, session),
|
||||
label = label,
|
||||
value = formatNoSci(value),
|
||||
min = formatNoSci(min),
|
||||
max = formatNoSci(max),
|
||||
@@ -510,11 +491,7 @@ updateInputOptions <- function(session, inputId, label = NULL, choices = NULL,
|
||||
))
|
||||
}
|
||||
|
||||
message <- dropNulls(list(
|
||||
label = processDeps(label, session),
|
||||
options = options,
|
||||
value = selected
|
||||
))
|
||||
message <- dropNulls(list(label = label, options = options, value = selected))
|
||||
|
||||
session$sendInputMessage(inputId, message)
|
||||
}
|
||||
@@ -667,11 +644,7 @@ updateSelectInput <- function(session = getDefaultReactiveDomain(), inputId, lab
|
||||
choices <- if (!is.null(choices)) choicesWithNames(choices)
|
||||
if (!is.null(selected)) selected <- as.character(selected)
|
||||
options <- if (!is.null(choices)) selectOptions(choices, selected, inputId, FALSE)
|
||||
message <- dropNulls(list(
|
||||
label = processDeps(label, session),
|
||||
options = options,
|
||||
value = selected
|
||||
))
|
||||
message <- dropNulls(list(label = label, options = options, value = selected))
|
||||
session$sendInputMessage(inputId, message)
|
||||
}
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ test_that("Initial snapshot values are consistent", {
|
||||
app$expect_values()
|
||||
}){{
|
||||
if (isTRUE(module)) {
|
||||
shiny::HTML('
|
||||
HTML('
|
||||
|
||||
|
||||
test_that("Module values are consistent", {
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
/*! shiny 1.10.0.9001 | (c) 2012-2025 Posit Software, PBC. | License: GPL-3 | file LICENSE */
|
||||
/*! shiny 1.10.0.9000 | (c) 2012-2025 Posit Software, PBC. | License: GPL-3 | file LICENSE */
|
||||
:where([data-shiny-busy-spinners] .recalculating){position:relative}[data-shiny-busy-spinners] .recalculating{min-height:var(--shiny-spinner-size, 32px)}[data-shiny-busy-spinners] .recalculating:after{position:absolute;content:"";--_shiny-spinner-url: var(--shiny-spinner-url, url(spinners/ring.svg));--_shiny-spinner-color: var(--shiny-spinner-color, var(--bs-primary, #007bc2));--_shiny-spinner-size: var(--shiny-spinner-size, 32px);--_shiny-spinner-delay: var(--shiny-spinner-delay, 1s);background:var(--_shiny-spinner-color);width:var(--_shiny-spinner-size);height:var(--_shiny-spinner-size);inset:calc(50% - var(--_shiny-spinner-size) / 2);mask-image:var(--_shiny-spinner-url);-webkit-mask-image:var(--_shiny-spinner-url);opacity:0;animation-delay:var(--_shiny-spinner-delay);animation-name:fade-in;animation-duration:.25s;animation-fill-mode:forwards}[data-shiny-busy-spinners] .recalculating:has(>*),[data-shiny-busy-spinners] .recalculating:empty{opacity:1}[data-shiny-busy-spinners] .recalculating>*:not(.recalculating){opacity:var(--_shiny-fade-opacity);transition:opacity .25s ease var(--shiny-spinner-delay, 1s)}[data-shiny-busy-spinners] .recalculating.html-widget-output{visibility:inherit!important}[data-shiny-busy-spinners] .recalculating.html-widget-output>*{visibility:hidden}[data-shiny-busy-spinners] .recalculating.html-widget-output :after{visibility:visible}[data-shiny-busy-spinners] .recalculating.shiny-html-output:not(.shiny-table-output):after{display:none}[data-shiny-busy-spinners][data-shiny-busy-pulse].shiny-busy:after{--_shiny-pulse-background: var( --shiny-pulse-background, linear-gradient( 120deg, transparent, var(--bs-indigo, #4b00c1), var(--bs-purple, #74149c), var(--bs-pink, #bf007f), transparent ) );--_shiny-pulse-height: var(--shiny-pulse-height, 3px);--_shiny-pulse-speed: var(--shiny-pulse-speed, 1.2s);position:fixed;top:0;left:0;height:var(--_shiny-pulse-height);background:var(--_shiny-pulse-background);z-index:9999;animation-name:busy-page-pulse;animation-duration:var(--_shiny-pulse-speed);animation-direction:alternate;animation-iteration-count:infinite;animation-timing-function:ease-in-out;content:""}[data-shiny-busy-spinners][data-shiny-busy-pulse].shiny-busy:has(.recalculating:not(.shiny-html-output)):after{display:none}[data-shiny-busy-spinners][data-shiny-busy-pulse].shiny-busy:has(.recalculating.shiny-table-output):after{display:none}[data-shiny-busy-spinners][data-shiny-busy-pulse].shiny-busy:has(#shiny-disconnected-overlay):after{display:none}[data-shiny-busy-pulse]:not([data-shiny-busy-spinners]).shiny-busy:after{--_shiny-pulse-background: var( --shiny-pulse-background, linear-gradient( 120deg, transparent, var(--bs-indigo, #4b00c1), var(--bs-purple, #74149c), var(--bs-pink, #bf007f), transparent ) );--_shiny-pulse-height: var(--shiny-pulse-height, 3px);--_shiny-pulse-speed: var(--shiny-pulse-speed, 1.2s);position:fixed;top:0;left:0;height:var(--_shiny-pulse-height);background:var(--_shiny-pulse-background);z-index:9999;animation-name:busy-page-pulse;animation-duration:var(--_shiny-pulse-speed);animation-direction:alternate;animation-iteration-count:infinite;animation-timing-function:ease-in-out;content:""}[data-shiny-busy-pulse]:not([data-shiny-busy-spinners]).shiny-busy:has(#shiny-disconnected-overlay):after{display:none}@keyframes fade-in{0%{opacity:0}to{opacity:1}}@keyframes busy-page-pulse{0%{left:-14%;right:97%}45%{left:0%;right:14%}55%{left:14%;right:0%}to{left:97%;right:-14%}}.shiny-spinner-output-container{--shiny-spinner-size: 0px}
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
/*! shiny 1.10.0.9001 | (c) 2012-2025 Posit Software, PBC. | License: GPL-3 | file LICENSE */
|
||||
/*! shiny 1.10.0.9000 | (c) 2012-2025 Posit Software, PBC. | License: GPL-3 | file LICENSE */
|
||||
"use strict";(()=>{document.documentElement.classList.add("autoreload-enabled");var c=window.location.protocol==="https:"?"wss:":"ws:",s=window.location.pathname.replace(/\/?$/,"/")+"autoreload/",i=`${c}//${window.location.host}${s}`,l=document.currentScript?.dataset?.wsUrl||i;async function u(o){let e=new WebSocket(o),n=!1;return new Promise((a,r)=>{e.onopen=()=>{n=!0},e.onerror=t=>{r(t)},e.onclose=()=>{n?a(!1):r(new Error("WebSocket connection failed"))},e.onmessage=function(t){t.data==="autoreload"&&a(!0)}})}async function d(o){return new Promise(e=>setTimeout(e,o))}async function w(){for(;;){try{if(await u(l)){window.location.reload();return}}catch{console.debug("Giving up on autoreload");return}await d(1e3)}}w().catch(o=>{console.error(o)});})();
|
||||
//# sourceMappingURL=shiny-autoreload.js.map
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
/*! shiny 1.10.0.9001 | (c) 2012-2025 Posit Software, PBC. | License: GPL-3 | file LICENSE */
|
||||
/*! shiny 1.10.0.9000 | (c) 2012-2025 Posit Software, PBC. | License: GPL-3 | file LICENSE */
|
||||
#showcase-well{border-radius:0}.shiny-code{background-color:#fff;margin-bottom:0}.shiny-code code{font-family:Menlo,Consolas,Courier New,monospace}.shiny-code-container{margin-top:20px;clear:both}.shiny-code-container h3{display:inline;margin-right:15px}.showcase-header{font-size:16px;font-weight:400}.showcase-code-link{text-align:right;padding:15px}#showcase-app-container{vertical-align:top}#showcase-code-tabs{margin-right:15px}#showcase-code-tabs pre{border:none;line-height:1em}#showcase-code-tabs .nav,#showcase-code-tabs ul{margin-bottom:0}#showcase-code-tabs .tab-content{border-style:solid;border-color:#e5e5e5;border-width:0px 1px 1px 1px;overflow:auto;border-bottom-right-radius:4px;border-bottom-left-radius:4px}#showcase-app-code{width:100%}#showcase-code-position-toggle{float:right}#showcase-sxs-code{padding-top:20px;vertical-align:top}.showcase-code-license{display:block;text-align:right}#showcase-code-content pre{background-color:#fff}
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
/*! shiny 1.10.0.9001 | (c) 2012-2025 Posit Software, PBC. | License: GPL-3 | file LICENSE */
|
||||
/*! shiny 1.10.0.9000 | (c) 2012-2025 Posit Software, PBC. | License: GPL-3 | file LICENSE */
|
||||
"use strict";(()=>{var f=400;function c(e,i){let t=0;if(e.nodeType===3){let n=e.nodeValue.replace(/\n/g,"").length;if(n>=i)return{element:e,offset:i};t+=n}else if(e.nodeType===1&&e.firstChild){let n=c(e.firstChild,i);if(n.element!==null)return n;t+=n.offset}return e.nextSibling?c(e.nextSibling,i-t):{element:null,offset:t}}function r(e,i,t){let n=0;for(let s=0;s<e.childNodes.length;s++){let l=e.childNodes[s];if(l.nodeType===3){let o=/\n/g,d;for(;(d=o.exec(l.nodeValue))!==null;)if(n++,n===i)return c(l,d.index+t+1)}else if(l.nodeType===1){let o=r(l,i-n,t);if(o.element!==null)return o;n+=o.offset}}return{element:null,offset:n}}function w(e,i){if(!document.createRange)return;let t=document.getElementById("srcref_"+e);if(!t){t=document.createElement("span"),t.id="srcref_"+e;let n=e,s=document.getElementById(i.replace(/\./g,"_")+"_code");if(!s)return;let l=r(s,n[0],n[4]),o=r(s,n[2],n[5]);if(l.element===null||o.element===null)return;let d=document.createRange();l.element.parentNode.nodeName==="SPAN"&&l.element!==o.element?d.setStartBefore(l.element.parentNode):d.setStart(l.element,l.offset),o.element.parentNode.nodeName==="SPAN"&&l.element!==o.element?d.setEndAfter(o.element.parentNode):d.setEnd(o.element,o.offset),d.surroundContents(t)}$(t).stop(!0,!0).effect("highlight",null,1600)}Shiny&&Shiny.addCustomMessageHandler("showcase-src",function(e){e.srcref&&e.srcfile&&w(e.srcref,e.srcfile)});var a=!1,m=function(e,i){let t=i?f:1,n=e?document.getElementById("showcase-sxs-code"):document.getElementById("showcase-code-inline"),s=e?document.getElementById("showcase-code-inline"):document.getElementById("showcase-sxs-code");if(document.getElementById("showcase-app-metadata")===null){let o=$("#showcase-well");e?o.fadeOut(t):o.fadeIn(t)}$(n).hide(),$(s).fadeOut(t,function(){let o=document.getElementById("showcase-code-tabs");s.removeChild(o),n.appendChild(o),e?h():document.getElementById("showcase-code-content").removeAttribute("style"),$(n).fadeIn(t),e||(document.getElementById("showcase-app-container").removeAttribute("style"),i&&$(document.body).animate({scrollTop:$(n).offset().top}));let d=document.getElementById("readme-md");d!==null&&(d.parentElement.removeChild(d),e?(s.appendChild(d),$(s).fadeIn(t)):document.getElementById("showcase-app-metadata").appendChild(d)),document.getElementById("showcase-code-position-toggle").innerHTML=e?'<i class="fa fa-level-down"></i> show below':'<i class="fa fa-level-up"></i> show with app'}),e&&$(document.body).animate({scrollTop:0},t),a=e,u(e&&i),$(window).trigger("resize")};function u(e){let t=960,n=1,s=document.getElementById("showcase-app-code").offsetWidth;s/2>960?t=s/2:s*.66>960?t=960:(t=s*.66,n=t/960);let l=document.getElementById("showcase-app-container");$(l).animate({width:t+"px",zoom:n*100+"%"},e?f:0)}var g=function(){m(!a,!0)},p=function(){document.body.offsetWidth>1350&&m(!0,!1)};function h(){document.getElementById("showcase-code-content").style.height=$(window).height()+"px"}function y(){let e=document.getElementById("showcase-markdown-content");if(e!==null){let i=e.innerText||e.innerHTML,t=window.Showdown.converter;document.getElementById("readme-md").innerHTML=new t().makeHtml(i)}}$(window).resize(function(){a&&(u(!1),h())});window.toggleCodePosition=g;$(window).on("load",p);$(window).on("load",y);window.hljs&&window.hljs.initHighlightingOnLoad();})();
|
||||
//# sourceMappingURL=shiny-showcase.js.map
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
/*! shiny 1.10.0.9001 | (c) 2012-2025 Posit Software, PBC. | License: GPL-3 | file LICENSE */
|
||||
/*! shiny 1.10.0.9000 | (c) 2012-2025 Posit Software, PBC. | License: GPL-3 | file LICENSE */
|
||||
"use strict";(()=>{var t=eval;window.addEventListener("message",function(a){let e=a.data;e.code&&t(e.code)});})();
|
||||
//# sourceMappingURL=shiny-testmode.js.map
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
4
inst/www/shared/shiny.min.css
vendored
4
inst/www/shared/shiny.min.css
vendored
File diff suppressed because one or more lines are too long
40
inst/www/shared/shiny.min.js
vendored
40
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
@@ -384,8 +384,8 @@ html.autoreload-enabled #shiny-disconnected-overlay.reloading {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* Styling for textAreaInput(autoresize=TRUE) */
|
||||
textarea.textarea-autoresize.form-control {
|
||||
/* Styling for inputTextArea(autoresize=TRUE) */
|
||||
.textarea-autoresize textarea.form-control {
|
||||
padding: 5px 8px;
|
||||
resize: none;
|
||||
overflow-y: hidden;
|
||||
|
||||
@@ -46,15 +46,12 @@ session to immediately unblock and carry on with other user interactions.
|
||||
}
|
||||
|
||||
\examples{
|
||||
\dontshow{if (rlang::is_interactive() && rlang::is_installed("mirai")) (if (getRversion() >= "3.4") withAutoprint else force)(\{ # examplesIf}
|
||||
\dontshow{if (rlang::is_interactive() && rlang::is_installed("future")) (if (getRversion() >= "3.4") withAutoprint else force)(\{ # examplesIf}
|
||||
|
||||
library(shiny)
|
||||
library(bslib)
|
||||
library(mirai)
|
||||
|
||||
# Set background processes for running tasks
|
||||
daemons(1)
|
||||
# Reset when the app is stopped
|
||||
onStop(function() daemons(0))
|
||||
library(future)
|
||||
plan(multisession)
|
||||
|
||||
ui <- page_fluid(
|
||||
titlePanel("Extended Task Demo"),
|
||||
@@ -68,12 +65,13 @@ ui <- page_fluid(
|
||||
|
||||
server <- function(input, output) {
|
||||
rand_task <- ExtendedTask$new(function() {
|
||||
mirai(
|
||||
future(
|
||||
{
|
||||
# Slow operation goes here
|
||||
Sys.sleep(2)
|
||||
sample(1:100, 1)
|
||||
}
|
||||
},
|
||||
seed = TRUE
|
||||
)
|
||||
})
|
||||
|
||||
@@ -123,12 +121,11 @@ server function.
|
||||
\item{\code{func}}{The long-running operation to execute. This should be an
|
||||
asynchronous function, meaning, it should use the
|
||||
\href{https://rstudio.github.io/promises/}{\{promises\}} package, most
|
||||
likely in conjunction with the
|
||||
\href{https://mirai.r-lib.org}{\{mirai\}} or
|
||||
likely in conjuction with the
|
||||
\href{https://rstudio.github.io/promises/articles/promises_04_futures.html}{\{future\}}
|
||||
package. (In short, the return value of \code{func} should be a
|
||||
\code{\link[mirai:mirai]{mirai}}, \code{\link[future:future]{Future}}, \code{promise},
|
||||
or something else that \code{\link[promises:is.promise]{promises::as.promise()}} understands.)
|
||||
\code{\link[future:future]{Future}} object, or a \code{promise}, or something else
|
||||
that \code{\link[promises:is.promise]{promises::as.promise()}} understands.)
|
||||
|
||||
It's also important that this logic does not read from any
|
||||
reactive inputs/sources, as inputs may change after the function is
|
||||
|
||||
@@ -234,8 +234,8 @@ With a cached reactive expression, the key and/or value expression can be
|
||||
promises, but rather objects provided by the
|
||||
\href{https://rstudio.github.io/promises/}{\pkg{promises}} package, which
|
||||
are similar to promises in JavaScript. (See \code{\link[promises:promise]{promises::promise()}} for more
|
||||
information.) You can also use \code{\link[mirai:mirai]{mirai::mirai()}} or \code{\link[future:future]{future::future()}}
|
||||
objects to run code in a separate process or even on a remote machine.
|
||||
information.) You can also use \code{\link[future:future]{future::future()}} objects to run code in a
|
||||
separate process or even on a remote machine.
|
||||
|
||||
If the value returns a promise, then anything that consumes the cached
|
||||
reactive must expect it to return a promise.
|
||||
|
||||
@@ -61,7 +61,7 @@ Other contributors:
|
||||
\item John Fraser (showdown.js library) [contributor, copyright holder]
|
||||
\item John Gruber (showdown.js library) [contributor, copyright holder]
|
||||
\item Ivan Sagalaev (highlight.js library) [contributor, copyright holder]
|
||||
\item R Core Team (tar implementation from R) [contributor, copyright holder]
|
||||
\item R Core Team (tar implementation from R) [contributor, copyright holder]
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"homepage": "https://shiny.rstudio.com",
|
||||
"repository": "github:rstudio/shiny",
|
||||
"name": "@types/rstudio-shiny",
|
||||
"version": "1.10.0-alpha.9001",
|
||||
"version": "1.10.0-alpha.9000",
|
||||
"license": "GPL-3.0-only",
|
||||
"main": "",
|
||||
"browser": "",
|
||||
|
||||
6
srcts/extras/textarea-autoresize.css
Normal file
6
srcts/extras/textarea-autoresize.css
Normal file
@@ -0,0 +1,6 @@
|
||||
.textarea-autoresize textarea.form-control {
|
||||
padding: 5px 8px;
|
||||
resize: none;
|
||||
overflow-y: hidden;
|
||||
height: auto;
|
||||
}
|
||||
@@ -113,10 +113,10 @@ class CheckboxGroupInputBinding extends InputBinding {
|
||||
options: options,
|
||||
};
|
||||
}
|
||||
async receiveMessage(
|
||||
receiveMessage(
|
||||
el: CheckboxGroupHTMLElement,
|
||||
data: CheckboxGroupReceiveMessageData
|
||||
): Promise<void> {
|
||||
): void {
|
||||
const $el = $(el);
|
||||
|
||||
// This will replace all the options
|
||||
@@ -132,7 +132,7 @@ class CheckboxGroupInputBinding extends InputBinding {
|
||||
this.setValue(el, data.value);
|
||||
}
|
||||
|
||||
await updateLabel(data.label, getLabelNode(el));
|
||||
updateLabel(data.label, getLabelNode(el));
|
||||
|
||||
$(el).trigger("change");
|
||||
}
|
||||
|
||||
@@ -292,13 +292,10 @@ class DateInputBinding extends DateInputBindingBase {
|
||||
startview: startview,
|
||||
};
|
||||
}
|
||||
async receiveMessage(
|
||||
el: HTMLElement,
|
||||
data: DateReceiveMessageData
|
||||
): Promise<void> {
|
||||
receiveMessage(el: HTMLElement, data: DateReceiveMessageData): void {
|
||||
const $input = $(el).find("input");
|
||||
|
||||
await updateLabel(data.label, this._getLabelNode(el));
|
||||
updateLabel(data.label, this._getLabelNode(el));
|
||||
|
||||
if (hasDefinedProperty(data, "min")) this._setMin($input[0], data.min);
|
||||
|
||||
|
||||
@@ -106,16 +106,13 @@ class DateRangeInputBinding extends DateInputBindingBase {
|
||||
startview: startview,
|
||||
};
|
||||
}
|
||||
async receiveMessage(
|
||||
el: HTMLElement,
|
||||
data: DateRangeReceiveMessageData
|
||||
): Promise<void> {
|
||||
receiveMessage(el: HTMLElement, data: DateRangeReceiveMessageData): void {
|
||||
const $el = $(el);
|
||||
const $inputs = $el.find("input");
|
||||
const $startinput = $inputs.eq(0);
|
||||
const $endinput = $inputs.eq(1);
|
||||
|
||||
await updateLabel(data.label, getLabelNode(el));
|
||||
updateLabel(data.label, getLabelNode(el));
|
||||
|
||||
if (hasDefinedProperty(data, "min")) {
|
||||
this._setMin($startinput[0], data.min);
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import type { EventPriority } from "../../inputPolicies/inputPolicy";
|
||||
import type { RatePolicyModes } from "../../inputPolicies/inputRateDecorator";
|
||||
import type { BindScope } from "../../shiny/bind";
|
||||
|
||||
@@ -26,10 +27,16 @@ class InputBinding {
|
||||
el; // unused var
|
||||
}
|
||||
|
||||
// The callback method takes one argument, whose value is boolean. If true,
|
||||
// allow deferred (debounce or throttle) sending depending on the value of
|
||||
// getRatePolicy. If false, send value immediately. Default behavior is `false`
|
||||
subscribe(el: HTMLElement, callback: (value: boolean) => void): void {
|
||||
// Historically, the callback value could only be boolean. In this case:
|
||||
// * false: send value immediately (i.e., priority = "immediate")
|
||||
// * true: send value later (i.e., priority = "deferred")
|
||||
// * The input rate policy is also consulted on whether to debounce or throttle
|
||||
// In recent versions, the value can also be "event", meaning that the
|
||||
// value should be sent regardless of whether it has changed.
|
||||
subscribe(
|
||||
el: HTMLElement,
|
||||
callback: (value: EventPriority | boolean) => void
|
||||
): void {
|
||||
// empty
|
||||
el; // unused var
|
||||
callback; // unused var
|
||||
|
||||
@@ -51,10 +51,7 @@ class NumberInputBinding extends TextInputBindingBase {
|
||||
return "shiny.number";
|
||||
el;
|
||||
}
|
||||
async receiveMessage(
|
||||
el: NumberHTMLElement,
|
||||
data: NumberReceiveMessageData
|
||||
): Promise<void> {
|
||||
receiveMessage(el: NumberHTMLElement, data: NumberReceiveMessageData): void {
|
||||
// Setting values to `""` will remove the attribute value from the DOM element.
|
||||
// The attr key will still remain, but there is not value... ex: `<input id="foo" type="number" min max/>`
|
||||
if (hasDefinedProperty(data, "value")) el.value = data.value ?? "";
|
||||
@@ -62,7 +59,7 @@ class NumberInputBinding extends TextInputBindingBase {
|
||||
if (hasDefinedProperty(data, "max")) el.max = data.max ?? "";
|
||||
if (hasDefinedProperty(data, "step")) el.step = data.step ?? "";
|
||||
|
||||
await updateLabel(data.label, getLabelNode(el));
|
||||
updateLabel(data.label, getLabelNode(el));
|
||||
|
||||
$(el).trigger("change");
|
||||
}
|
||||
|
||||
@@ -103,10 +103,7 @@ class RadioInputBinding extends InputBinding {
|
||||
options: options,
|
||||
};
|
||||
}
|
||||
async receiveMessage(
|
||||
el: RadioHTMLElement,
|
||||
data: RadioReceiveMessageData
|
||||
): Promise<void> {
|
||||
receiveMessage(el: RadioHTMLElement, data: RadioReceiveMessageData): void {
|
||||
const $el = $(el);
|
||||
// This will replace all the options
|
||||
|
||||
@@ -125,7 +122,7 @@ class RadioInputBinding extends InputBinding {
|
||||
this.setValue(el, data.value);
|
||||
}
|
||||
|
||||
await updateLabel(data.label, getLabelNode(el));
|
||||
updateLabel(data.label, getLabelNode(el));
|
||||
|
||||
$(el).trigger("change");
|
||||
}
|
||||
|
||||
@@ -102,10 +102,10 @@ class SelectInputBinding extends InputBinding {
|
||||
options: options,
|
||||
};
|
||||
}
|
||||
async receiveMessage(
|
||||
receiveMessage(
|
||||
el: SelectHTMLElement,
|
||||
data: SelectInputReceiveMessageData
|
||||
): Promise<void> {
|
||||
): void {
|
||||
const $el = $(el);
|
||||
|
||||
// This will replace all the options
|
||||
@@ -205,7 +205,7 @@ class SelectInputBinding extends InputBinding {
|
||||
this.setValue(el, data.value);
|
||||
}
|
||||
|
||||
await updateLabel(data.label, getLabelNode(el));
|
||||
updateLabel(data.label, getLabelNode(el));
|
||||
|
||||
$(el).trigger("change");
|
||||
}
|
||||
|
||||
@@ -179,10 +179,7 @@ class SliderInputBinding extends TextInputBindingBase {
|
||||
unsubscribe(el: HTMLElement): void {
|
||||
$(el).off(".sliderInputBinding");
|
||||
}
|
||||
async receiveMessage(
|
||||
el: HTMLElement,
|
||||
data: SliderReceiveMessageData
|
||||
): Promise<void> {
|
||||
receiveMessage(el: HTMLElement, data: SliderReceiveMessageData): void {
|
||||
const $el = $(el);
|
||||
const slider = $el.data("ionRangeSlider");
|
||||
const msg: {
|
||||
@@ -229,7 +226,7 @@ class SliderInputBinding extends TextInputBindingBase {
|
||||
}
|
||||
}
|
||||
|
||||
await updateLabel(data.label, getLabelNode(el));
|
||||
updateLabel(data.label, getLabelNode(el));
|
||||
|
||||
// (maybe) update data elements
|
||||
const domElements: Array<"data-type" | "time-format" | "timezone"> = [
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import $ from "jquery";
|
||||
import { $escape, hasDefinedProperty, updateLabel } from "../../utils";
|
||||
|
||||
import type { EventPriority } from "../../inputPolicies/inputPolicy";
|
||||
import { InputBinding } from "./inputBinding";
|
||||
|
||||
// interface TextHTMLElement extends NameValueHTMLElement {
|
||||
@@ -49,7 +50,10 @@ class TextInputBindingBase extends InputBinding {
|
||||
value;
|
||||
}
|
||||
|
||||
subscribe(el: TextHTMLElement, callback: (x: boolean) => void): void {
|
||||
subscribe(
|
||||
el: TextHTMLElement,
|
||||
callback: (x: EventPriority | boolean) => void
|
||||
): void {
|
||||
const $el = $(el);
|
||||
const updateOn = $el.data("update-on") || "change";
|
||||
|
||||
@@ -126,13 +130,10 @@ class TextInputBinding extends TextInputBindingBase {
|
||||
placeholder: el.placeholder,
|
||||
};
|
||||
}
|
||||
async receiveMessage(
|
||||
el: TextHTMLElement,
|
||||
data: TextReceiveMessageData
|
||||
): Promise<void> {
|
||||
receiveMessage(el: TextHTMLElement, data: TextReceiveMessageData): void {
|
||||
if (hasDefinedProperty(data, "value")) this.setValue(el, data.value);
|
||||
|
||||
await updateLabel(data.label, getLabelNode(el));
|
||||
updateLabel(data.label, getLabelNode(el));
|
||||
|
||||
if (hasDefinedProperty(data, "placeholder"))
|
||||
el.placeholder = data.placeholder;
|
||||
|
||||
@@ -2,53 +2,96 @@ import $ from "jquery";
|
||||
|
||||
import { TextInputBinding } from "./text";
|
||||
|
||||
// When a textarea becomes visible, update the height
|
||||
const intersectObserver = new IntersectionObserver((entries) => {
|
||||
entries.forEach((entry) => {
|
||||
if (entry.isIntersecting) {
|
||||
updateHeight(entry.target as HTMLInputElement);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
class TextareaInputBinding extends TextInputBinding {
|
||||
#inputHandler: EventListener | null = null;
|
||||
|
||||
export class TextareaInputBinding extends TextInputBinding {
|
||||
find(scope: HTMLElement): JQuery<HTMLElement> {
|
||||
// Inputs now also have the .shiny-input-textarea class
|
||||
return $(scope).find("textarea");
|
||||
}
|
||||
}
|
||||
|
||||
initialize(el: HTMLInputElement): void {
|
||||
super.initialize(el);
|
||||
updateHeight(el);
|
||||
/*************************************************************
|
||||
* Code below this point is for textAreaInput(autoresize=TRUE)
|
||||
************************************************************/
|
||||
|
||||
interface DOMEvent<T extends EventTarget> extends Event {
|
||||
readonly target: T;
|
||||
}
|
||||
|
||||
function onDelegatedEvent(
|
||||
eventName: string,
|
||||
selector: string,
|
||||
callback: (target: HTMLTextAreaElement) => void
|
||||
) {
|
||||
document.addEventListener(eventName, (e) => {
|
||||
const e2 = e as DOMEvent<HTMLTextAreaElement>;
|
||||
if (e2.target.matches(selector)) {
|
||||
callback(e2.target);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Use a single intersectionObserver as they are slow to create / use.
|
||||
let textAreaIntersectionObserver: IntersectionObserver | null = null;
|
||||
|
||||
function callUpdateHeightWhenTargetIsVisible(target: HTMLTextAreaElement) {
|
||||
if (textAreaIntersectionObserver === null) {
|
||||
// Create a single observer to watch for the textarea becoming visible
|
||||
textAreaIntersectionObserver = new IntersectionObserver((entries) => {
|
||||
entries.forEach((entry) => {
|
||||
// Quit if the entry is not visible
|
||||
if (!entry.isIntersecting) {
|
||||
return;
|
||||
}
|
||||
// If the entry is visible (even if it's just a single pixel)
|
||||
// Stop observing the target
|
||||
textAreaIntersectionObserver?.unobserve(entry.target);
|
||||
|
||||
// Update the height of the textarea
|
||||
updateHeight(entry.target as HTMLTextAreaElement);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
subscribe(el: HTMLInputElement, callback: (x: boolean) => void): void {
|
||||
super.subscribe(el, callback);
|
||||
textAreaIntersectionObserver.observe(target);
|
||||
}
|
||||
|
||||
this.#inputHandler = (e) => updateHeight(e.target as HTMLInputElement);
|
||||
el.addEventListener("input", this.#inputHandler);
|
||||
intersectObserver.observe(el);
|
||||
}
|
||||
function updateHeight(target: HTMLTextAreaElement) {
|
||||
if (target.scrollHeight > 0) {
|
||||
// Automatically resize the textarea to fit its content.
|
||||
target.style.height = "auto";
|
||||
target.style.height = target.scrollHeight + "px";
|
||||
} else {
|
||||
// The textarea is not visible on the page, therefore it has a 0 scroll height.
|
||||
|
||||
unsubscribe(el: HTMLInputElement): void {
|
||||
super.unsubscribe(el);
|
||||
|
||||
if (this.#inputHandler) el.removeEventListener("input", this.#inputHandler);
|
||||
intersectObserver.unobserve(el);
|
||||
// If we should autoresize the text area height, then we can wait for the textarea to
|
||||
// become visible and call `updateHeight` again. Hopefully the scroll height is no
|
||||
// longer 0
|
||||
callUpdateHeightWhenTargetIsVisible(target);
|
||||
}
|
||||
}
|
||||
|
||||
function updateHeight(el: HTMLInputElement) {
|
||||
if (!el.classList.contains("textarea-autoresize")) {
|
||||
// Update on change
|
||||
onDelegatedEvent(
|
||||
"input",
|
||||
"textarea.textarea-autoresize",
|
||||
(target: HTMLTextAreaElement) => {
|
||||
updateHeight(target);
|
||||
}
|
||||
);
|
||||
|
||||
// Update on load
|
||||
function updateOnLoad() {
|
||||
if (document.readyState === "loading") {
|
||||
// Document still loading, wait 10ms to check again.
|
||||
setTimeout(updateOnLoad, 10);
|
||||
return;
|
||||
}
|
||||
if (el.scrollHeight == 0) {
|
||||
return;
|
||||
}
|
||||
el.style.height = "auto";
|
||||
el.style.height = el.scrollHeight + "px";
|
||||
|
||||
// document.readyState in ["interactive", "complete"];\
|
||||
const textAreas = document.querySelectorAll(
|
||||
"textarea.textarea-autoresize"
|
||||
) as NodeListOf<HTMLTextAreaElement>;
|
||||
textAreas.forEach(updateHeight);
|
||||
}
|
||||
|
||||
export { TextareaInputBinding };
|
||||
updateOnLoad();
|
||||
|
||||
@@ -8,6 +8,7 @@ import type {
|
||||
InputRateDecorator,
|
||||
InputValidateDecorator,
|
||||
} from "../inputPolicies";
|
||||
import type { InputPolicyOpts } from "../inputPolicies/inputPolicy";
|
||||
import { shinyAppBindOutput, shinyAppUnbindOutput } from "./initedMethods";
|
||||
import { sendImageSizeFns } from "./sendImageSize";
|
||||
|
||||
@@ -27,7 +28,7 @@ function valueChangeCallback(
|
||||
inputs: InputValidateDecorator,
|
||||
binding: InputBinding,
|
||||
el: HTMLElement,
|
||||
allowDeferred: boolean
|
||||
priority: InputPolicyOpts["priority"]
|
||||
) {
|
||||
let id = binding.getId(el);
|
||||
|
||||
@@ -37,17 +38,7 @@ function valueChangeCallback(
|
||||
|
||||
if (type) id = id + ":" + type;
|
||||
|
||||
const opts: {
|
||||
priority: "deferred" | "immediate";
|
||||
binding: typeof binding;
|
||||
el: typeof el;
|
||||
} = {
|
||||
priority: allowDeferred ? "deferred" : "immediate",
|
||||
binding: binding,
|
||||
el: el,
|
||||
};
|
||||
|
||||
inputs.setInput(id, value, opts);
|
||||
inputs.setInput(id, value, { priority, binding, el });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -272,8 +263,17 @@ function bindInputs(
|
||||
const thisBinding = binding;
|
||||
const thisEl = el;
|
||||
|
||||
return function (allowDeferred: boolean) {
|
||||
valueChangeCallback(inputs, thisBinding, thisEl, allowDeferred);
|
||||
// Historically speaking, this callback has only accepted a boolean value,
|
||||
// but in recent versions it can also accept a input priority.
|
||||
return function (priority: InputPolicyOpts["priority"] | boolean) {
|
||||
const normalizedPriority =
|
||||
typeof priority !== "boolean"
|
||||
? priority
|
||||
: priority
|
||||
? "deferred"
|
||||
: "immediate";
|
||||
|
||||
valueChangeCallback(inputs, thisBinding, thisEl, normalizedPriority);
|
||||
};
|
||||
})();
|
||||
|
||||
|
||||
@@ -177,11 +177,11 @@ class ShinyClass {
|
||||
|
||||
let target: InputPolicy;
|
||||
|
||||
if (document.querySelector(".shiny-submit-button")) {
|
||||
if (document.querySelector(".submit-button")) {
|
||||
// If there is a submit button on the page, use defer decorator
|
||||
target = inputsDefer;
|
||||
|
||||
document.querySelectorAll(".shiny-submit-button").forEach(function (x) {
|
||||
document.querySelectorAll(".submit-button").forEach(function (x) {
|
||||
x.addEventListener("click", function (event) {
|
||||
event.preventDefault();
|
||||
inputsDefer.submit();
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
import $ from "jquery";
|
||||
import type { HtmlDep } from "../shiny/render";
|
||||
import { renderContent } from "../shiny/render";
|
||||
import { windowDevicePixelRatio } from "../window/pixelRatio";
|
||||
import type { MapValuesUnion, MapWithResult } from "./extraTypes";
|
||||
import { hasDefinedProperty, hasOwnProperty } from "./object";
|
||||
@@ -353,27 +351,23 @@ const compareVersion = function (
|
||||
else throw `Unknown operator: ${op}`;
|
||||
};
|
||||
|
||||
async function updateLabel(
|
||||
labelContent: string | { html: string; deps: HtmlDep[] } | undefined,
|
||||
function updateLabel(
|
||||
labelTxt: string | undefined,
|
||||
labelNode: JQuery<HTMLElement>
|
||||
): Promise<void> {
|
||||
): void {
|
||||
// Only update if label was specified in the update method
|
||||
if (typeof labelContent === "undefined") return;
|
||||
if (typeof labelTxt === "undefined") return;
|
||||
if (labelNode.length !== 1) {
|
||||
throw new Error("labelNode must be of length 1");
|
||||
}
|
||||
|
||||
if (typeof labelContent === "string") {
|
||||
labelContent = {
|
||||
html: labelContent,
|
||||
deps: [],
|
||||
};
|
||||
}
|
||||
// Should the label be empty?
|
||||
const emptyLabel = Array.isArray(labelTxt) && labelTxt.length === 0;
|
||||
|
||||
if (labelContent.html === "") {
|
||||
if (emptyLabel) {
|
||||
labelNode.addClass("shiny-label-null");
|
||||
} else {
|
||||
await renderContent(labelNode, labelContent);
|
||||
labelNode.text(labelTxt);
|
||||
labelNode.removeClass("shiny-label-null");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ declare class CheckboxGroupInputBinding extends InputBinding {
|
||||
value: ReturnType<CheckboxGroupInputBinding["getValue"]>;
|
||||
options: ValueLabelObject[];
|
||||
};
|
||||
receiveMessage(el: CheckboxGroupHTMLElement, data: CheckboxGroupReceiveMessageData): Promise<void>;
|
||||
receiveMessage(el: CheckboxGroupHTMLElement, data: CheckboxGroupReceiveMessageData): void;
|
||||
subscribe(el: CheckboxGroupHTMLElement, callback: (x: boolean) => void): void;
|
||||
unsubscribe(el: CheckboxGroupHTMLElement): void;
|
||||
}
|
||||
|
||||
2
srcts/types/src/bindings/input/date.d.ts
vendored
2
srcts/types/src/bindings/input/date.d.ts
vendored
@@ -52,7 +52,7 @@ declare class DateInputBinding extends DateInputBindingBase {
|
||||
format: string;
|
||||
startview: DatepickerViewModes;
|
||||
};
|
||||
receiveMessage(el: HTMLElement, data: DateReceiveMessageData): Promise<void>;
|
||||
receiveMessage(el: HTMLElement, data: DateReceiveMessageData): void;
|
||||
}
|
||||
export { DateInputBinding, DateInputBindingBase };
|
||||
export type { DateReceiveMessageData };
|
||||
|
||||
@@ -27,7 +27,7 @@ declare class DateRangeInputBinding extends DateInputBindingBase {
|
||||
language: string;
|
||||
startview: string;
|
||||
};
|
||||
receiveMessage(el: HTMLElement, data: DateRangeReceiveMessageData): Promise<void>;
|
||||
receiveMessage(el: HTMLElement, data: DateRangeReceiveMessageData): void;
|
||||
initialize(el: HTMLElement): void;
|
||||
subscribe(el: HTMLElement, callback: (x: boolean) => void): void;
|
||||
unsubscribe(el: HTMLElement): void;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import type { EventPriority } from "../../inputPolicies/inputPolicy";
|
||||
import type { RatePolicyModes } from "../../inputPolicies/inputRateDecorator";
|
||||
import type { BindScope } from "../../shiny/bind";
|
||||
declare class InputBinding {
|
||||
@@ -6,7 +7,7 @@ declare class InputBinding {
|
||||
getId(el: HTMLElement): string;
|
||||
getType(el: HTMLElement): string | null;
|
||||
getValue(el: HTMLElement): any;
|
||||
subscribe(el: HTMLElement, callback: (value: boolean) => void): void;
|
||||
subscribe(el: HTMLElement, callback: (value: EventPriority | boolean) => void): void;
|
||||
unsubscribe(el: HTMLElement): void;
|
||||
receiveMessage(el: HTMLElement, data: unknown): Promise<void> | void;
|
||||
getState(el: HTMLElement): unknown;
|
||||
|
||||
2
srcts/types/src/bindings/input/number.d.ts
vendored
2
srcts/types/src/bindings/input/number.d.ts
vendored
@@ -12,7 +12,7 @@ declare class NumberInputBinding extends TextInputBindingBase {
|
||||
getValue(el: NumberHTMLElement): string[] | number | string | null | undefined;
|
||||
setValue(el: NumberHTMLElement, value: number): void;
|
||||
getType(el: NumberHTMLElement): string;
|
||||
receiveMessage(el: NumberHTMLElement, data: NumberReceiveMessageData): Promise<void>;
|
||||
receiveMessage(el: NumberHTMLElement, data: NumberReceiveMessageData): void;
|
||||
getState(el: NumberHTMLElement): {
|
||||
label: string;
|
||||
value: ReturnType<NumberInputBinding["getValue"]>;
|
||||
|
||||
2
srcts/types/src/bindings/input/radio.d.ts
vendored
2
srcts/types/src/bindings/input/radio.d.ts
vendored
@@ -18,7 +18,7 @@ declare class RadioInputBinding extends InputBinding {
|
||||
value: ReturnType<RadioInputBinding["getValue"]>;
|
||||
options: ValueLabelObject[];
|
||||
};
|
||||
receiveMessage(el: RadioHTMLElement, data: RadioReceiveMessageData): Promise<void>;
|
||||
receiveMessage(el: RadioHTMLElement, data: RadioReceiveMessageData): void;
|
||||
subscribe(el: RadioHTMLElement, callback: (x: boolean) => void): void;
|
||||
unsubscribe(el: RadioHTMLElement): void;
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ declare class SelectInputBinding extends InputBinding {
|
||||
label: string;
|
||||
}>;
|
||||
};
|
||||
receiveMessage(el: SelectHTMLElement, data: SelectInputReceiveMessageData): Promise<void>;
|
||||
receiveMessage(el: SelectHTMLElement, data: SelectInputReceiveMessageData): void;
|
||||
subscribe(el: SelectHTMLElement, callback: (x: boolean) => void): void;
|
||||
unsubscribe(el: HTMLElement): void;
|
||||
initialize(el: SelectHTMLElement): void;
|
||||
|
||||
2
srcts/types/src/bindings/input/slider.d.ts
vendored
2
srcts/types/src/bindings/input/slider.d.ts
vendored
@@ -26,7 +26,7 @@ declare class SliderInputBinding extends TextInputBindingBase {
|
||||
setValue(el: HTMLElement, value: number | string | [number | string, number | string]): void;
|
||||
subscribe(el: HTMLElement, callback: (x: boolean) => void): void;
|
||||
unsubscribe(el: HTMLElement): void;
|
||||
receiveMessage(el: HTMLElement, data: SliderReceiveMessageData): Promise<void>;
|
||||
receiveMessage(el: HTMLElement, data: SliderReceiveMessageData): void;
|
||||
getRatePolicy(el: HTMLElement): {
|
||||
policy: "debounce";
|
||||
delay: 250;
|
||||
|
||||
5
srcts/types/src/bindings/input/text.d.ts
vendored
5
srcts/types/src/bindings/input/text.d.ts
vendored
@@ -1,3 +1,4 @@
|
||||
import type { EventPriority } from "../../inputPolicies/inputPolicy";
|
||||
import { InputBinding } from "./inputBinding";
|
||||
type TextHTMLElement = HTMLInputElement;
|
||||
type TextReceiveMessageData = {
|
||||
@@ -10,7 +11,7 @@ declare class TextInputBindingBase extends InputBinding {
|
||||
getId(el: TextHTMLElement): string;
|
||||
getValue(el: TextHTMLElement): unknown;
|
||||
setValue(el: TextHTMLElement, value: unknown): void;
|
||||
subscribe(el: TextHTMLElement, callback: (x: boolean) => void): void;
|
||||
subscribe(el: TextHTMLElement, callback: (x: EventPriority | boolean) => void): void;
|
||||
unsubscribe(el: TextHTMLElement): void;
|
||||
receiveMessage(el: TextHTMLElement, data: unknown): void;
|
||||
getState(el: TextHTMLElement): unknown;
|
||||
@@ -27,7 +28,7 @@ declare class TextInputBinding extends TextInputBindingBase {
|
||||
value: string;
|
||||
placeholder: string;
|
||||
};
|
||||
receiveMessage(el: TextHTMLElement, data: TextReceiveMessageData): Promise<void>;
|
||||
receiveMessage(el: TextHTMLElement, data: TextReceiveMessageData): void;
|
||||
}
|
||||
export { TextInputBinding, TextInputBindingBase };
|
||||
export type { TextHTMLElement, TextReceiveMessageData };
|
||||
|
||||
7
srcts/types/src/bindings/input/textarea.d.ts
vendored
7
srcts/types/src/bindings/input/textarea.d.ts
vendored
@@ -1,9 +1,4 @@
|
||||
import { TextInputBinding } from "./text";
|
||||
declare class TextareaInputBinding extends TextInputBinding {
|
||||
#private;
|
||||
export declare class TextareaInputBinding extends TextInputBinding {
|
||||
find(scope: HTMLElement): JQuery<HTMLElement>;
|
||||
initialize(el: HTMLInputElement): void;
|
||||
subscribe(el: HTMLInputElement, callback: (x: boolean) => void): void;
|
||||
unsubscribe(el: HTMLInputElement): void;
|
||||
}
|
||||
export { TextareaInputBinding };
|
||||
|
||||
18
srcts/types/src/bindings/input/textsubmit.d.ts
vendored
Normal file
18
srcts/types/src/bindings/input/textsubmit.d.ts
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
import type { EventPriority } from "../../inputPolicies/inputPolicy";
|
||||
import { InputBinding } from "./inputBinding";
|
||||
type TextHTMLElement = HTMLInputElement;
|
||||
type TextSubmitReceiveMessageData = {
|
||||
value?: string;
|
||||
placeholder?: string;
|
||||
submit?: boolean;
|
||||
focus?: boolean;
|
||||
};
|
||||
declare class TextSubmitInputBinding extends InputBinding {
|
||||
find(scope: HTMLElement): JQuery<HTMLElement>;
|
||||
getValue(el: TextHTMLElement): string;
|
||||
setValue(el: TextHTMLElement, value: string): void;
|
||||
subscribe(el: TextHTMLElement, callback: (x: EventPriority | boolean) => void): void;
|
||||
unsubscribe(el: HTMLElement): void;
|
||||
receiveMessage(el: TextHTMLElement, data: TextSubmitReceiveMessageData): void;
|
||||
}
|
||||
export { TextSubmitInputBinding };
|
||||
6
srcts/types/src/utils/index.d.ts
vendored
6
srcts/types/src/utils/index.d.ts
vendored
@@ -1,4 +1,3 @@
|
||||
import type { HtmlDep } from "../shiny/render";
|
||||
import type { MapValuesUnion, MapWithResult } from "./extraTypes";
|
||||
import { hasDefinedProperty, hasOwnProperty } from "./object";
|
||||
declare function escapeHTML(str: string): string;
|
||||
@@ -27,10 +26,7 @@ declare function isnan(x: unknown): boolean;
|
||||
declare function _equal(x: unknown, y: unknown): boolean;
|
||||
declare function equal(...args: unknown[]): boolean;
|
||||
declare const compareVersion: (a: string, op: "<" | "<=" | "==" | ">" | ">=", b: string) => boolean;
|
||||
declare function updateLabel(labelContent: string | {
|
||||
html: string;
|
||||
deps: HtmlDep[];
|
||||
} | undefined, labelNode: JQuery<HTMLElement>): Promise<void>;
|
||||
declare function updateLabel(labelTxt: string | undefined, labelNode: JQuery<HTMLElement>): void;
|
||||
declare function getComputedLinkColor(el: HTMLElement): string;
|
||||
declare function isBS3(): boolean;
|
||||
declare function toLowerCase<T extends string>(str: T): Lowercase<T>;
|
||||
|
||||
@@ -6,9 +6,7 @@ sortList <- function(x) {
|
||||
}
|
||||
|
||||
# This will create print.ggplot in the current environment
|
||||
# print.ggplot is for ggplot2 < 4.0.0
|
||||
# print.ggplot2::ggplot is for ggplot2 >= 4.0.0
|
||||
print.ggplot <- `print.ggplot2::ggplot` <- custom_print.ggplot
|
||||
print.ggplot <- custom_print.ggplot
|
||||
|
||||
|
||||
test_that("ggplot coordmap", {
|
||||
|
||||
@@ -1307,7 +1307,7 @@ for (do_priming in c(TRUE, FALSE)) {
|
||||
# dr should've fired, and we should have converged on the right answer.
|
||||
expect_identical(dr_fired, 2)
|
||||
isolate(expect_identical(rv$a, dr()))
|
||||
# be sure tr() converged on the right answer; (We've already confirmed throttle-like behavior above)
|
||||
expect_identical(tr_fired, 4)
|
||||
isolate(expect_identical(rv$a, tr()))
|
||||
})
|
||||
}
|
||||
|
||||
@@ -16,19 +16,18 @@ test_that("Radio buttons and checkboxes work with modules", {
|
||||
resultA <- sessA$lastInputMessage
|
||||
|
||||
expect_equal(resultA$id, "test1")
|
||||
expect_equal(as.character(resultA$message$label$html), "Label")
|
||||
expect_equal(resultA$message$label, "Label")
|
||||
expect_equal(resultA$message$value, "a")
|
||||
expect_match(resultA$message$options, '"modA-test1"')
|
||||
expect_no_match(resultA$message$options, '"test1"')
|
||||
|
||||
sessB <- createModuleSession("modB")
|
||||
|
||||
updateCheckboxGroupInput(sessB, "test2", label = icon("eye"), choices = LETTERS[1:5])
|
||||
updateCheckboxGroupInput(sessB, "test2", label = "Label", choices = LETTERS[1:5])
|
||||
resultB <- sessB$lastInputMessage
|
||||
|
||||
expect_equal(resultB$id, "test2")
|
||||
expect_length(resultB$message$label, 2)
|
||||
expect_s3_class(resultB$message$label$html, "html")
|
||||
expect_equal(resultB$message$label, "Label")
|
||||
expect_null(resultB$message$value)
|
||||
expect_match(resultB$message$options, '"modB-test2"')
|
||||
expect_no_match(resultB$message$options, '"test2"')
|
||||
|
||||
Reference in New Issue
Block a user