Compare commits

...

39 Commits

Author SHA1 Message Date
Garrick Aden-Buie
a0a8939240 docs(testServer): Update example to simulate button click 2023-07-10 10:52:44 -04:00
Garrick Aden-Buie
6fc06281bd docs(navbarPage): Be less specific about collapsible breakpoint (#3853) 2023-07-07 10:42:39 -04:00
Barret Schloerke
f724128d41 Merge pull request #3850 from rstudio/rc-v1.7.4.1 2023-07-07 09:56:25 -04:00
Barret Schloerke
518ef0f9f8 Merge branch 'main' into rc-v1.7.4.1
* main: (55 commits)
  Catch errors receiving individual input messages (#3843)
  Clarify what's ignored by ignoreNULL (#3827)
  Remove tests/testthat/apps/ (#3841)
  Correctly set width on `textarea` input element (#3838)
  Update selectize.js documentation URLs (#3836)
  test(json digits): Try unsetting option in test to get default behavior (#3837)
  Rebuild docs
  Update README.md
  fix typo in the docs for bindCache (#3825)
  Allow for `shiny:::toJSON()` to respect if `digits` has class `AsIs` to represent `use_signif=` (#3819)
  More complete `downloadButton()` example
  Rename actionQueue to taskQueue, add more context to the NEWS item (#3801)
  Remove unneeded packages from package.json
  Rebuild docs
  Rebuild shiny.js
  Update @types/node
  Rebuild yarn.lock
  Fix brush resetting behavior. Closes #3785
  Bump fastmap dependency to 1.1.1
  Sync package version (GitHub Actions)
  ...
2023-07-07 09:39:01 -04:00
Winston Chang
f5b395485e shiny v1.7.4.1
Remove use of as.numeric_version()
Closes #3849
2023-07-07 09:33:14 -04:00
Garrick Aden-Buie
31aca7aa70 Catch errors receiving individual input messages (#3843) 2023-06-30 17:10:17 -04:00
Garrick Aden-Buie
b38a630224 Clarify what's ignored by ignoreNULL (#3827)
Co-authored-by: gadenbuie <gadenbuie@users.noreply.github.com>
2023-06-30 10:44:43 -04:00
Winston Chang
1b7709411b Remove tests/testthat/apps/ (#3841) 2023-06-16 16:55:14 -05:00
Garrick Aden-Buie
2b48aa0d91 Correctly set width on textarea input element (#3838)
Co-authored-by: gadenbuie <gadenbuie@users.noreply.github.com>
2023-06-12 15:57:49 -04:00
Viviane Girardin
6fdf23752e Update selectize.js documentation URLs (#3836)
Co-authored-by: Neal Richardson <neal.p.richardson@gmail.com>
Co-authored-by: Winston Chang <winston@stdout.org>
2023-06-09 15:43:21 -05:00
Barret Schloerke
8542f5d017 test(json digits): Try unsetting option in test to get default behavior (#3837) 2023-06-09 15:30:02 -05:00
Winston Chang
e7b830755a Rebuild docs 2023-06-01 15:44:18 -05:00
Gordon Shotwell
23c7b0683a Update README.md
Add Discord link
2023-06-01 15:43:20 -05:00
Tom Jemmett
5805895581 fix typo in the docs for bindCache (#3825) 2023-05-23 08:52:42 -05:00
Barret Schloerke
90539bff25 Allow for shiny:::toJSON() to respect if digits has class AsIs to represent use_signif= (#3819) 2023-05-16 16:14:03 -04:00
Winston Chang
62bb21d5b6 Merge pull request #3804 from rstudio/docs/ex-download-button 2023-04-14 15:48:19 -05:00
Garrick Aden-Buie
4f85268d44 More complete downloadButton() example 2023-04-13 09:04:40 -04:00
Carson Sievert
611e517bb8 Rename actionQueue to taskQueue, add more context to the NEWS item (#3801) 2023-03-31 14:38:28 -05:00
Winston Chang
4d05a568c1 Remove unneeded packages from package.json 2023-03-06 17:01:43 -06:00
Winston Chang
1330325519 Rebuild docs 2023-03-01 21:38:10 -06:00
Winston Chang
92d850efa6 Rebuild shiny.js 2023-03-01 21:26:59 -06:00
Winston Chang
7bf56125eb Update @types/node 2023-03-01 21:26:59 -06:00
Winston Chang
69f861cc8a Rebuild yarn.lock 2023-03-01 21:20:56 -06:00
Winston Chang
a94be7b128 Fix brush resetting behavior. Closes #3785 2023-03-01 20:58:26 -06:00
Winston Chang
703766fb2e Merge pull request #3782 from rstudio/plot-interact-init 2023-02-24 16:59:42 -06:00
Winston Chang
8e73749e21 Bump fastmap dependency to 1.1.1 2023-02-24 10:30:07 -06:00
wch
dc8ffa115b Sync package version (GitHub Actions) 2023-02-23 20:32:23 +00:00
Winston Chang
a0385da0d7 Rebuild shiny.js 2023-02-23 14:18:21 -06:00
Winston Chang
a6b7dee4cd Send initial values for plot interaction 2023-02-23 14:14:01 -06:00
Winston Chang
f9ff5c2637 Bump version to 1.7.4.9002 2023-01-25 11:19:40 -06:00
Winston Chang
6a1fbc57f4 Clarify comments 2023-01-25 11:18:20 -06:00
Winston Chang
38337a926f Ensure that reactiveValues keys and values are sorted (#3774) 2023-01-25 11:10:05 -06:00
Winston Chang
bf6b87886c Merge pull request #3775 from rstudio/map-loadtime 2023-01-24 13:55:37 -06:00
Winston Chang
33e6b0a305 Add on_load function for registering expressions to run on load 2023-01-23 17:25:26 -06:00
Winston Chang
cb5eac052f Initialize Map objects at load time instead of build time 2023-01-23 16:26:44 -06:00
Winston Chang
39fee3782f Merge pull request #3772 from rstudio/fix-slider-stoppropagation 2023-01-23 10:59:47 -06:00
Winston Chang
654f30a312 Udpate NEWS 2023-01-20 17:04:21 -06:00
Winston Chang
a763da2b94 Fix stopPropagation error in ion.rangeSlider 2023-01-20 17:00:12 -06:00
Jon Calder
0c177d30dc Fix two typos in insertUI() docs (#3712)
* Fix two typos in insertUI() docs

* document()

Co-authored-by: Carson Sievert <cpsievert1@gmail.com>
2023-01-13 11:41:44 -06:00
85 changed files with 10847 additions and 6817 deletions

View File

@@ -1,7 +1,7 @@
Package: shiny
Type: Package
Title: Web Application Framework for R
Version: 1.7.4.9001
Version: 1.7.4.9002
Authors@R: c(
person("Winston", "Chang", role = c("aut", "cre"), email = "winston@rstudio.com", comment = c(ORCID = "0000-0002-1576-2126")),
person("Joe", "Cheng", role = "aut", email = "joe@rstudio.com"),
@@ -87,7 +87,7 @@ Imports:
tools,
crayon,
rlang (>= 0.4.10),
fastmap (>= 1.1.0),
fastmap (>= 1.1.1),
withr,
commonmark (>= 1.7),
glue (>= 1.3.2),
@@ -111,7 +111,7 @@ Suggests:
ragg,
showtext,
sass
URL: https://shiny.rstudio.com/
URL: https://shiny.posit.co/
BugReports: https://github.com/rstudio/shiny/issues
Collate:
'globals.R'

29
NEWS.md
View File

@@ -1,4 +1,4 @@
# shiny 1.7.4.9001
# shiny 1.7.4.9002
## Full changelog
@@ -6,10 +6,29 @@
### New features and improvements
* Closed #789: `<script>` loaded from dynamic UI are no longer loaded using synchronous `XMLHttpRequest` (via jQuery). (#3666)
* Closed #789: Dynamic UI is now rendered asynchronously, thanks in part to the newly exported `Shiny.renderDependenciesAsync()`, `Shiny.renderHtmlAsync()`, and `Shiny.renderContentAsync()`. Importantly, this means `<script>` tags are now loaded asynchronously (the old way used `XMLHttpRequest`, which is synchronous). In addition, `Shiny` now manages a queue of async tasks (exposed via `Shiny.shinyapp.taskQueue`) so that order of execution is preserved. (#3666)
* For `reactiveValues()` objects, whenever the `$names()` or `$values()` methods are called, the keys are now returned in the order that they were inserted. (#3774)
* `Map` objects are now initialized at load time instead of build time. This avoids potential problems that could arise from storing `fastmap` objects into the built Shiny package. (#3775)
* Allow for `shiny:::toJSON()` to respect if `digits=` has class `"AsIs"` which represents if `use_signif=` is `TRUE` or `FALSE`. This is useful for testing to keep the digits smaller. For example, setting `options(shiny.json.digits = 4)` will save 4 digits after the decimal, rather than the default of `I(16)` which will save 16 significant digits. (#3819)
### Bug fixes
* Fixed #3771: Sometimes the error `ion.rangeSlider.min.js: i.stopPropagation is not a function` would appear in the JavaScript console. (#3772)
* Fixed #3833: When `width` is provided to `textAreaInput()`, we now correctly set the width of the `<textarea>` element. (#3838)
* Fixes #3840: `updateSliderInput()` now warns when attempting to set invalid `min`, `max`, or `value` values. Sending an invalid update message to an input no longer causes other update messages to fail. (#3843)
# shiny 1.7.4.1
## Full changelog
* Closed #3849: In R-devel, a warning was raised when Shiny was loaded because `as.numeric_version()` was called with a number instead of a string. (#3850)
# shiny 1.7.4
@@ -23,7 +42,7 @@
### New features and improvements
* `plotOutput()`, `imageOutput()`, and `uiOutput()` gain a `fill` argument. If `TRUE` (the default for `plotOutput()`), the output container is allowed to grow/shrink to fit a fill container (created via `htmltools::bindFillRole()`) with an opinionated height. This means `plotOutput()` will grow/shrink by default [inside of `bslib::card_body_fill()`](https://rstudio.github.io/bslib/articles/cards.html#responsive-sizing), but `imageOutput()` and `uiOutput()` will have to opt-in to similar behavior with `fill = TRUE`. (#3715)
* `plotOutput()`, `imageOutput()`, and `uiOutput()` gain a `fill` argument. If `TRUE` (the default for `plotOutput()`), the output container is allowed to grow/shrink to fit a fill container (created via `htmltools::bindFillRole()`) with an opinionated height. This means `plotOutput()` will grow/shrink by default [inside of `bslib::card_body_fill()`](https://rstudio.github.io/bslib/articles/cards.html#responsive-sizing), but `imageOutput()` and `uiOutput()` will have to opt-in to similar behavior with `fill = TRUE`. (#3715)
* Closed #3687: Updated jQuery-UI to v1.13.2. (#3697)
@@ -160,7 +179,7 @@ This release focuses on improvements in three main areas:
1. Better theming (and Bootstrap 4) support:
* The `theme` argument of `fluidPage()`, `navbarPage()`, and `bootstrapPage()` all now understand `bslib::bs_theme()` objects, which can be used to opt-into Bootstrap 4, use any Bootswatch theme, and/or implement custom themes without writing any CSS.
* The `session` object now includes `$setCurrentTheme()` and `$getCurrentTheme()` methods to dynamically update (or obtain) the page's `theme` after initial load, which is useful for things such as [adding a dark mode switch to an app](https://rstudio.github.io/bslib/articles/bslib.html#dynamic) or some other "real-time" theming tool like `bslib::bs_themer()`.
* The `session` object now includes `$setCurrentTheme()` and `$getCurrentTheme()` methods to dynamically update (or obtain) the page's `theme` after initial load, which is useful for things such as [adding a dark mode switch to an app](https://rstudio.github.io/bslib/articles/theming.html#dynamic) or some other "real-time" theming tool like `bslib::bs_themer()`.
* For more details, see [`{bslib}`'s website](https://rstudio.github.io/bslib/)
2. Caching of `reactive()` and `render*()` (e.g. `renderText()`, `renderTable()`, etc) expressions.
@@ -553,7 +572,7 @@ This is a significant release for Shiny, with a major new feature that was nearl
* Fixed #1600: URL-encoded bookmarking did not work with sliders that had dates or date-times. (#1961)
* Fixed #1962: [File dragging and dropping](https://www.rstudio.com/blog/shiny-1-0-4/) broke in the presence of jQuery version 3.0 as introduced by the [rhandsontable](https://jrowen.github.io/rhandsontable/) [htmlwidget](https://www.htmlwidgets.org/). (#2005)
* Fixed #1962: [File dragging and dropping](https://posit.co/blog/shiny-1-0-4/) broke in the presence of jQuery version 3.0 as introduced by the [rhandsontable](https://jrowen.github.io/rhandsontable/) [htmlwidget](https://www.htmlwidgets.org/). (#2005)
* Improved the error handling inside the `addResourcePath()` function, to give end users more informative error messages when the `directoryPath` argument cannot be normalized. This is especially useful for `runtime: shiny_prerendered` Rmd documents, like `learnr` tutorials. (#1968)

View File

@@ -159,8 +159,8 @@ utils::globalVariables(".GenericCallEnv", add = TRUE)
#' ```
#'
#' To use different settings for a session-scoped cache, you can set
#' `self$cache` at the top of your server function. By default, it will create
#' a 200 MB memory cache for each session, but you can replace it with
#' `session$cache` at the top of your server function. By default, it will
#' create a 200 MB memory cache for each session, but you can replace it with
#' something different. To use the session-scoped cache, you must also call
#' `bindCache()` with `cache="session"`. This will create a 100 MB cache for
#' the session:

View File

@@ -452,8 +452,10 @@ RestoreInputSet <- R6Class("RestoreInputSet",
)
)
# This is a fastmap::faststack(); value is assigned in .onLoad().
restoreCtxStack <- NULL
on_load({
restoreCtxStack <- fastmap::faststack()
})
withRestoreContext <- function(ctx, expr) {
restoreCtxStack$push(ctx)

View File

@@ -374,8 +374,7 @@ collapseSizes <- function(padding) {
#' @param inverse `TRUE` to use a dark background and light text for the
#' navigation bar
#' @param collapsible `TRUE` to automatically collapse the navigation
#' elements into a menu when the width of the browser is less than 940 pixels
#' (useful for viewing on smaller touchscreen device)
#' elements into an expandable menu on mobile devices or narrow window widths.
#' @param fluid `TRUE` to use a fluid layout. `FALSE` to use a fixed
#' layout.
#' @param windowTitle the browser window title (as a character string). The
@@ -1200,19 +1199,25 @@ uiOutput <- htmlOutput
#' @examples
#' \dontrun{
#' ui <- fluidPage(
#' p("Choose a dataset to download."),
#' selectInput("dataset", "Dataset", choices = c("mtcars", "airquality")),
#' downloadButton("downloadData", "Download")
#' )
#'
#' server <- function(input, output) {
#' # Our dataset
#' data <- mtcars
#' # The requested dataset
#' data <- reactive({
#' get(input$dataset)
#' })
#'
#' output$downloadData <- downloadHandler(
#' filename = function() {
#' paste("data-", Sys.Date(), ".csv", sep="")
#' # Use the selected dataset as the suggested file name
#' paste0(input$dataset, ".csv")
#' },
#' content = function(file) {
#' write.csv(data, file)
#' # Write the dataset to the `file` that will be downloaded
#' write.csv(data(), file)
#' }
#' )
#' }

View File

@@ -190,8 +190,10 @@ devmode_inform <- function(
#' @include map.R
registered_devmode_options <- Map$new()
registered_devmode_options <- NULL
on_load({
registered_devmode_options <- Map$new()
})
#' @describeIn devmode Registers a Shiny Developer Mode option with an updated
#' value and Developer message. This registration method allows package
@@ -340,21 +342,22 @@ get_devmode_option <- function(
}
on_load({
register_devmode_option(
"shiny.autoreload",
"Turning on shiny autoreload. To disable, call `options(shiny.autoreload = FALSE)`",
TRUE
)
register_devmode_option(
"shiny.autoreload",
"Turning on shiny autoreload. To disable, call `options(shiny.autoreload = FALSE)`",
TRUE
)
register_devmode_option(
"shiny.minified",
"Using full shiny javascript file. To use the minified version, call `options(shiny.minified = TRUE)`",
FALSE
)
register_devmode_option(
"shiny.minified",
"Using full shiny javascript file. To use the minified version, call `options(shiny.minified = TRUE)`",
FALSE
)
register_devmode_option(
"shiny.fullstacktrace",
"Turning on full stack trace. To disable, call `options(shiny.fullstacktrace = FALSE)`",
TRUE
)
register_devmode_option(
"shiny.fullstacktrace",
"Turning on full stack trace. To disable, call `options(shiny.fullstacktrace = FALSE)`",
TRUE
)
})

View File

@@ -7,19 +7,21 @@
# the private seed during load.
withPrivateSeed(set.seed(NULL))
# Create this at the top level, but since the object is from a different
# package, we don't want to bake it into the built binary package.
restoreCtxStack <<- fastmap::faststack()
for (expr in on_load_exprs) {
eval(expr, envir = environment(.onLoad))
}
# Make sure these methods are available to knitr if shiny is loaded but not
# attached.
s3_register("knitr::knit_print", "reactive")
s3_register("knitr::knit_print", "shiny.appobj")
s3_register("knitr::knit_print", "shiny.render.function")
# Shiny 1.4.0 bumps jQuery 1.x to 3.x, which caused a problem
# with static-rendering of htmlwidgets, and htmlwidgets 1.5
# includes a fix for this problem
# https://github.com/rstudio/shiny/issues/2630
register_upgrade_message("htmlwidgets", 1.5)
}
on_load_exprs <- list()
# Register an expression to be evaluated when the package is loaded (in the
# .onLoad function).
on_load <- function(expr) {
on_load_exprs[[length(on_load_exprs) + 1]] <<- substitute(expr)
}

View File

@@ -559,4 +559,6 @@ MessageLogger = R6Class(
)
)
rLog <- RLog$new("shiny.reactlog", "shiny.reactlog.console")
on_load({
rLog <- RLog$new("shiny.reactlog", "shiny.reactlog.console")
})

View File

@@ -182,8 +182,8 @@ brushedPoints <- function(df, brush, xvar = NULL, yvar = NULL,
# $ xmax : num 3.78
# $ ymin : num 17.1
# $ ymax : num 20.4
# $ panelvar1: int 6
# $ panelvar2: int 0
# $ panelvar1: chr "6"
# $ panelvar2: chr "0
# $ coords_css:List of 4
# ..$ xmin: int 260
# ..$ xmax: int 298
@@ -367,8 +367,8 @@ nearPoints <- function(df, coordinfo, xvar = NULL, yvar = NULL,
# $ img_css_ratio:List of 2
# ..$ x: num 1.25
# ..$ y: num 1.25
# $ panelvar1 : int 6
# $ panelvar2 : int 0
# $ panelvar1 : chr "6"
# $ panelvar2 : chr "0"
# $ mapping :List of 4
# ..$ x : chr "wt"
# ..$ y : chr "mpg"

View File

@@ -4,7 +4,7 @@
#' from a list of values.
#'
#' By default, `selectInput()` and `selectizeInput()` use the JavaScript library
#' \pkg{selectize.js} (<https://github.com/selectize/selectize.js>) instead of
#' \pkg{selectize.js} (<https://selectize.dev/) instead of
#' the basic select input element. To use the standard HTML select input
#' element, use `selectInput()` with `selectize=FALSE`.
#'
@@ -172,7 +172,7 @@ needOptgroup <- function(choices) {
#' @rdname selectInput
#' @param ... Arguments passed to `selectInput()`.
#' @param options A list of options. See the documentation of \pkg{selectize.js}
#' @param options A list of options. See the documentation of \pkg{selectize.js}(<https://selectize.dev/docs/usage>)
#' for possible options (character option values inside [base::I()] will
#' be treated as literal JavaScript code; see [renderDataTable()]
#' for details).
@@ -287,7 +287,7 @@ selectizeStaticDependency <- function(version) {
#'
#' By default, `varSelectInput()` and `selectizeInput()` use the
#' JavaScript library \pkg{selectize.js}
#' (<https://github.com/selectize/selectize.js>) to instead of the basic
#' (<https://selectize.dev/>) to instead of the basic
#' select input element. To use the standard HTML select input element, use
#' `selectInput()` with `selectize=FALSE`.
#'
@@ -383,7 +383,7 @@ varSelectInput <- function(
#' @rdname varSelectInput
#' @param ... Arguments passed to `varSelectInput()`.
#' @param options A list of options. See the documentation of \pkg{selectize.js}
#' @param options A list of options. See the documentation of \pkg{selectize.js}(<https://selectize.dev/docs/usage>)
#' for possible options (character option values inside [base::I()] will
#' be treated as literal JavaScript code; see [renderDataTable()]
#' for details).

View File

@@ -52,7 +52,7 @@ textAreaInput <- function(inputId, label, value = "", width = NULL, height = NUL
style <- css(
# The width is specified on the parent div.
width = if (!is.null(width)) "width: 100%;",
width = if (!is.null(width)) "100%",
height = validateCssUnit(height),
resize = resize
)

View File

@@ -1,6 +1,6 @@
#' Insert and remove UI objects
#'
#' These functions allow you to dynamically add and remove arbirary UI
#' These functions allow you to dynamically add and remove arbitrary UI
#' into your app, whenever you want, as many times as you want.
#' Unlike [renderUI()], the UI generated with `insertUI()` is persistent:
#' once it's created, it stays there until removed by `removeUI()`. Each
@@ -11,7 +11,7 @@
#' function.
#'
#' It's particularly useful to pair `removeUI` with `insertUI()`, but there is
#' no restriction on what you can use on. Any element that can be selected
#' no restriction on what you can use it on. Any element that can be selected
#' through a jQuery selector can be removed through this function.
#'
#' @param selector A string that is accepted by jQuery's selector

View File

@@ -326,6 +326,9 @@ ReactiveValues <- R6Class(
.dedupe = logical(0),
# Key, asList(), or names() have been retrieved
.hasRetrieved = list(),
# All names, in insertion order. The names are also stored in the .values
# object, but it does not preserve order.
.nameOrder = character(0),
initialize = function(
@@ -403,6 +406,11 @@ ReactiveValues <- R6Class(
return(invisible())
}
# If it's new, append key to the name order
if (!key_exists) {
.nameOrder[length(.nameOrder) + 1] <<- key
}
# set the value for better logging
.values$set(key, value)
@@ -444,14 +452,13 @@ ReactiveValues <- R6Class(
},
names = function() {
nameValues <- .values$keys()
if (!isTRUE(.hasRetrieved$names)) {
domain <- getDefaultReactiveDomain()
rLog$defineNames(.reactId, nameValues, .label, domain)
rLog$defineNames(.reactId, .nameOrder, .label, domain)
.hasRetrieved$names <<- TRUE
}
.namesDeps$register()
return(nameValues)
return(.nameOrder)
},
# Get a metadata value. Does not trigger reactivity.
@@ -499,7 +506,7 @@ ReactiveValues <- R6Class(
},
toList = function(all.names=FALSE) {
listValue <- .values$values()
listValue <- .values$mget(.nameOrder)
if (!all.names) {
listValue <- listValue[!grepl("^\\.", base::names(listValue))]
}
@@ -2180,8 +2187,8 @@ maskReactiveContext <- function(expr) {
#' @param autoDestroy If `TRUE` (the default), the observer will be
#' automatically destroyed when its domain (if any) ends.
#' @param ignoreNULL Whether the action should be triggered (or value
#' calculated, in the case of `eventReactive`) when the input is
#' `NULL`. See Details.
#' calculated, in the case of `eventReactive`) when the input event expression
#' is `NULL`. See Details.
#' @param ignoreInit If `TRUE`, then, when this `observeEvent` is
#' first created/initialized, ignore the `handlerExpr` (the second
#' argument), whether it is otherwise supposed to run or not. The default is

View File

@@ -1,5 +1,9 @@
# Create a map for input handlers and register the defaults.
inputHandlers <- Map$new()
# Create a Map object for input handlers and register the defaults.
# This is assigned in .onLoad time.
inputHandlers <- NULL
on_load({
inputHandlers <- Map$new()
})
#' Register an Input Handler
#'
@@ -125,115 +129,117 @@ applyInputHandlers <- function(inputs, shinysession = getDefaultReactiveDomain()
inputs
}
on_load({
# Takes a list-of-lists and returns a matrix. The lists
# must all be the same length. NULL is replaced by NA.
registerInputHandler("shiny.matrix", function(data, ...) {
if (length(data) == 0)
return(matrix(nrow=0, ncol=0))
# Takes a list-of-lists and returns a matrix. The lists
# must all be the same length. NULL is replaced by NA.
registerInputHandler("shiny.matrix", function(data, ...) {
if (length(data) == 0)
return(matrix(nrow=0, ncol=0))
m <- matrix(unlist(lapply(data, function(x) {
sapply(x, function(y) {
ifelse(is.null(y), NA, y)
})
})), nrow = length(data[[1]]), ncol = length(data))
return(m)
})
registerInputHandler("shiny.number", function(val, ...){
ifelse(is.null(val), NA, val)
})
registerInputHandler("shiny.password", function(val, shinysession, name) {
# Mark passwords as not serializable
setSerializer(name, serializerUnserializable)
val
})
registerInputHandler("shiny.date", function(val, ...){
# First replace NULLs with NA, then convert to Date vector
datelist <- ifelse(lapply(val, is.null), NA, val)
res <- NULL
tryCatch({
res <- as.Date(unlist(datelist))
},
error = function(e) {
# It's possible for client to send a string like "99999-01-01", which
# as.Date can't handle.
warning(e$message)
res <<- as.Date(rep(NA, length(datelist)))
}
)
res
})
registerInputHandler("shiny.datetime", function(val, ...){
# First replace NULLs with NA, then convert to POSIXct vector
times <- lapply(val, function(x) {
if (is.null(x)) NA
else x
m <- matrix(unlist(lapply(data, function(x) {
sapply(x, function(y) {
ifelse(is.null(y), NA, y)
})
})), nrow = length(data[[1]]), ncol = length(data))
return(m)
})
as.POSIXct(unlist(times), origin = "1970-01-01", tz = "UTC")
})
registerInputHandler("shiny.action", function(val, shinysession, name) {
# mark up the action button value with a special class so we can recognize it later
class(val) <- c("shinyActionButtonValue", class(val))
val
})
registerInputHandler("shiny.file", function(val, shinysession, name) {
# This function is only used when restoring a Shiny fileInput. When a file is
# uploaded the usual way, it takes a different code path and won't hit this
# function.
if (is.null(val))
return(NULL)
# The data will be a named list of lists; convert to a data frame.
val <- as.data.frame(lapply(val, unlist), stringsAsFactors = FALSE)
# `val$datapath` should be a filename without a path, for security reasons.
if (basename(val$datapath) != val$datapath) {
stop("Invalid '/' found in file input path.")
}
# Prepend the persistent dir
oldfile <- file.path(getCurrentRestoreContext()$dir, val$datapath)
# Copy the original file to a new temp dir, so that a restored session can't
# modify the original.
newdir <- file.path(tempdir(), createUniqueId(12))
dir.create(newdir)
val$datapath <- file.path(newdir, val$datapath)
file.copy(oldfile, val$datapath)
# Need to mark this input value with the correct serializer. When a file is
# uploaded the usual way (instead of being restored), this occurs in
# session$`@uploadEnd`.
setSerializer(name, serializerFileInput)
snapshotPreprocessInput(name, snapshotPreprocessorFileInput)
val
})
# to be used with !!!answer
registerInputHandler("shiny.symbolList", function(val, ...) {
if (is.null(val)) {
list()
} else {
lapply(val, as.symbol)
}
})
# to be used with !!answer
registerInputHandler("shiny.symbol", function(val, ...) {
if (is.null(val) || identical(val, "")) {
NULL
} else {
as.symbol(val)
}
registerInputHandler("shiny.number", function(val, ...){
ifelse(is.null(val), NA, val)
})
registerInputHandler("shiny.password", function(val, shinysession, name) {
# Mark passwords as not serializable
setSerializer(name, serializerUnserializable)
val
})
registerInputHandler("shiny.date", function(val, ...){
# First replace NULLs with NA, then convert to Date vector
datelist <- ifelse(lapply(val, is.null), NA, val)
res <- NULL
tryCatch({
res <- as.Date(unlist(datelist))
},
error = function(e) {
# It's possible for client to send a string like "99999-01-01", which
# as.Date can't handle.
warning(e$message)
res <<- as.Date(rep(NA, length(datelist)))
}
)
res
})
registerInputHandler("shiny.datetime", function(val, ...){
# First replace NULLs with NA, then convert to POSIXct vector
times <- lapply(val, function(x) {
if (is.null(x)) NA
else x
})
as.POSIXct(unlist(times), origin = "1970-01-01", tz = "UTC")
})
registerInputHandler("shiny.action", function(val, shinysession, name) {
# mark up the action button value with a special class so we can recognize it later
class(val) <- c("shinyActionButtonValue", class(val))
val
})
registerInputHandler("shiny.file", function(val, shinysession, name) {
# This function is only used when restoring a Shiny fileInput. When a file is
# uploaded the usual way, it takes a different code path and won't hit this
# function.
if (is.null(val))
return(NULL)
# The data will be a named list of lists; convert to a data frame.
val <- as.data.frame(lapply(val, unlist), stringsAsFactors = FALSE)
# `val$datapath` should be a filename without a path, for security reasons.
if (basename(val$datapath) != val$datapath) {
stop("Invalid '/' found in file input path.")
}
# Prepend the persistent dir
oldfile <- file.path(getCurrentRestoreContext()$dir, val$datapath)
# Copy the original file to a new temp dir, so that a restored session can't
# modify the original.
newdir <- file.path(tempdir(), createUniqueId(12))
dir.create(newdir)
val$datapath <- file.path(newdir, val$datapath)
file.copy(oldfile, val$datapath)
# Need to mark this input value with the correct serializer. When a file is
# uploaded the usual way (instead of being restored), this occurs in
# session$`@uploadEnd`.
setSerializer(name, serializerFileInput)
snapshotPreprocessInput(name, snapshotPreprocessorFileInput)
val
})
# to be used with !!!answer
registerInputHandler("shiny.symbolList", function(val, ...) {
if (is.null(val)) {
list()
} else {
lapply(val, as.symbol)
}
})
# to be used with !!answer
registerInputHandler("shiny.symbol", function(val, ...) {
if (is.null(val) || identical(val, "")) {
NULL
} else {
as.symbol(val)
}
})
})

View File

@@ -1,7 +1,12 @@
#' @include server-input-handlers.R
appsByToken <- Map$new()
appsNeedingFlush <- Map$new()
appsByToken <- NULL
appsNeedingFlush <- NULL
on_load({
appsByToken <- Map$new()
appsNeedingFlush <- Map$new()
})
# Provide a character representation of the WS that can be used
# as a key in a Map.
@@ -122,7 +127,10 @@ decodeMessage <- function(data) {
return(mainMessage)
}
autoReloadCallbacks <- Callbacks$new()
autoReloadCallbacks <- NULL
on_load({
autoReloadCallbacks <- Callbacks$new()
})
createAppHandlers <- function(httpHandlers, serverFuncSource) {
appvars <- new.env()

View File

@@ -33,8 +33,12 @@ createUniqueId <- function(bytes, prefix = "", suffix = "") {
}
toJSON <- function(x, ..., dataframe = "columns", null = "null", na = "null",
auto_unbox = TRUE, digits = getOption("shiny.json.digits", 16),
use_signif = TRUE, force = TRUE, POSIXt = "ISO8601", UTC = TRUE,
auto_unbox = TRUE,
# Shiny has had a legacy value of 16 significant digits
# We can use `I(16)` mixed with the default behavior in jsonlite's `use_signif=`
# https://github.com/jeroen/jsonlite/commit/728efa9
digits = getOption("shiny.json.digits", I(16)), use_signif = is(digits, "AsIs"),
force = TRUE, POSIXt = "ISO8601", UTC = TRUE,
rownames = FALSE, keep_vec_names = TRUE, strict_atomic = TRUE) {
if (strict_atomic) {

View File

@@ -48,32 +48,6 @@ is_installed <- function(pkg, version = NULL) {
installed && isTRUE(get_package_version(pkg) >= version)
}
register_upgrade_message <- function(pkg, version, error = FALSE) {
msg <- sprintf(
"This version of '%s' is designed to work with '%s' >= %s.
Please upgrade via install.packages('%s').",
environmentName(environment(register_upgrade_message)),
pkg, version, pkg
)
cond <- if (error) stop else packageStartupMessage
if (pkg %in% loadedNamespaces() && !is_installed(pkg, version)) {
cond(msg)
}
# Always register hook in case pkg is loaded at some
# point the future (or, potentially, but less commonly,
# unloaded & reloaded)
setHook(
packageEvent(pkg, "onLoad"),
function(...) {
if (!is_installed(pkg, version)) cond(msg)
}
)
}
# Simplified version rlang:::s3_register() that just uses
# warning() instead of rlang::warn() when registration fails
# https://github.com/r-lib/rlang/blob/main/R/compat-s3-register.R
@@ -190,11 +164,9 @@ system_file <- function(..., package = "base") {
normalizePath(files, winslash = "/")
}
# A wrapper for `system.file()`, which caches the results, because
# `system.file()` can be slow. Note that because of caching, if
# `system_file_cached()` is called on a package that isn't installed, then the
# package is installed, and then `system_file_cached()` is called again, it will
# still return "".
# A wrapper for `system.file()`, which caches the package path because
# `system.file()` can be slow. If a package is not installed, the result won't
# be cached.
system_file_cached <- local({
pkg_dir_cache <- character()
@@ -206,7 +178,9 @@ system_file_cached <- local({
not_cached <- is.na(match(package, names(pkg_dir_cache)))
if (not_cached) {
pkg_dir <- system.file(package = package)
pkg_dir_cache[[package]] <<- pkg_dir
if (nzchar(pkg_dir)) {
pkg_dir_cache[[package]] <<- pkg_dir
}
} else {
pkg_dir <- pkg_dir_cache[[package]]
}

View File

@@ -37,29 +37,43 @@
#'
#'
#' # Testing a module --------------------------------------------------------
#' myModuleServer <- function(id, multiplier = 2, prefix = "I am ") {
#' # Testing the server function doesn't require a UI, but we've included it
#' # here for completeness. In this simple app, a user clicks a button to
#' # multiply a value by the module's multiplier argument. In the tests below,
#' # we'll make sure the value is 1, 2, 4, etc. with each button click.
#' multModuleUI <- function(id) {
#' ns <- NS(id)
#' tagList(
#' textOutput(ns("txt")),
#' actionButton(ns("multiply_it"), "Multiply It")
#' )
#' }
#'
#' multModuleServer <- function(id, multiplier = 2) {
#' moduleServer(id, function(input, output, session) {
#' myreactive <- reactive({
#' input$x * multiplier
#' the_value <- reactive({
#' max(input$multiply_it * multiplier, 1)
#' })
#' output$txt <- renderText({
#' paste0(prefix, myreactive())
#' paste("The value is", the_value())
#' })
#' })
#' }
#'
#' testServer(myModuleServer, args = list(multiplier = 2), {
#' session$setInputs(x = 1)
#' # You're also free to use third-party
#' # testing packages like testthat:
#' # expect_equal(myreactive(), 2)
#' stopifnot(myreactive() == 2)
#' stopifnot(output$txt == "I am 2")
#' testServer(multModuleServer, args = list(multiplier = 2), {
#' # Set the initial button value to 0
#' session$setInputs(multiply_it = 0)
#' stopifnot(the_value() == 1)
#' stopifnot(output$txt == "The value is 1")
#'
#' session$setInputs(x = 2)
#' stopifnot(myreactive() == 4)
#' stopifnot(output$txt == "I am 4")
#' # Any additional arguments, below, are passed along to the module.
#' # Simulate two button clicks
#' session$setInputs(multiply_it = 2)
#' stopifnot(the_value() == 4)
#' stopifnot(output$txt == "The value is 4")
#'
#' # Note: you're also free to use third-party
#' # testing packages like testthat:
#' # expect_equal(myreactive(), 1)
#' })
#' @export
testServer <- function(app = NULL, expr, args = list(), session = MockShinySession$new()) {

View File

@@ -423,6 +423,23 @@ updateSliderInput <- function(session = getDefaultReactiveDomain(), inputId, lab
{
validate_session_object(session)
if (!is.null(value)) {
if (!is.null(min) && !is.null(max)) {
# Validate value/min/max together if all three are provided
tryCatch(
validate_slider_value(min, max, value, "updateSliderInput"),
error = function(err) warning(conditionMessage(err), call. = FALSE)
)
} else if (length(value) < 1 || length(value) > 2 || any(is.na(value))) {
# Otherwise ensure basic assumptions about value are met
warning(
"In updateSliderInput(): value must be a single value or a length-2 ",
"vector and cannot contain NA values.",
call. = FALSE
)
}
}
# If no min/max/value is provided, we won't know the
# type, and this will return an empty string
dataType <- getSliderType(min, max, value)

View File

@@ -4,7 +4,7 @@ NULL
# @staticimports pkg:staticimports
# is_installed get_package_version system_file
# s3_register register_upgrade_message
# s3_register
# any_named any_unnamed
#' Make a random number generator repeatable

View File

@@ -16,7 +16,7 @@ Easily build rich and productive interactive web apps in R &mdash; no HTML/CSS/J
* A prebuilt set of highly sophisticated, customizable, and easy-to-use widgets (e.g., plots, tables, sliders, dropdowns, date pickers, and more).
* An attractive default look based on [Bootstrap](https://getbootstrap.com/) which can also be easily customized with the [bslib](https://github.com/rstudio/bslib) package or avoided entirely with more direct R bindings to HTML/CSS/JavaScript.
* Seamless integration with [R Markdown](https://shiny.rstudio.com/articles/interactive-docs.html), making it easy to embed numerous applications natively within a larger dynamic document.
* Tools for improving and monitoring performance, including native support for [async programming](https://www.rstudio.com/blog/shiny-1-1-0/), [caching](https://talks.cpsievert.me/20201117), [load testing](https://rstudio.github.io/shinyloadtest/), and more.
* Tools for improving and monitoring performance, including native support for [async programming](https://posit.co/blog/shiny-1-1-0/), [caching](https://talks.cpsievert.me/20201117), [load testing](https://rstudio.github.io/shinyloadtest/), and more.
* [Modules](https://shiny.rstudio.com/articles/modules.html): a framework for reducing code duplication and complexity.
* An ability to [bookmark application state](https://shiny.rstudio.com/articles/bookmarking-state.html) and/or [generate code to reproduce output(s)](https://github.com/rstudio/shinymeta).
* A rich ecosystem of extension packages for more [custom widgets](http://www.htmlwidgets.org/), [input validation](https://github.com/rstudio/shinyvalidate), [unit testing](https://github.com/rstudio/shinytest), and more.
@@ -45,6 +45,10 @@ For more examples and inspiration, check out the [Shiny User Gallery](https://sh
For help with learning fundamental Shiny programming concepts, check out the [Mastering Shiny](https://mastering-shiny.org/) book and the [Shiny Tutorial](https://shiny.rstudio.com/tutorial/). The former is currently more up-to-date with modern Shiny features, whereas the latter takes a deeper, more visual, dive into fundamental concepts.
## Join the conversation
If you want to chat about Shiny, meet other developers, or help us decide what to work on next, [join us on Discord](https://discord.gg/yMGCamUMnS).
## Getting Help
To ask a question about Shiny, please use the [RStudio Community website](https://community.rstudio.com/new-topic?category=shiny&tags=shiny).

View File

@@ -753,7 +753,7 @@
x = $handle.offset().left;
x += ($handle.width() / 2) - 1;
this.pointerClick("single", {preventDefault: function () {}, pageX: x});
this.pointerClick("single", {preventDefault: function () {}, stopPropagation: function () {}, pageX: x});
}
},

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1,2 +1,2 @@
/*! shiny 1.7.4.9001 | (c) 2012-2023 RStudio, PBC. | License: GPL-3 | file LICENSE */
/*! shiny 1.7.4.9002 | (c) 2012-2023 RStudio, 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}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1,3 +1,3 @@
/*! shiny 1.7.4.9001 | (c) 2012-2023 RStudio, PBC. | License: GPL-3 | file LICENSE */
/*! shiny 1.7.4.9002 | (c) 2012-2023 RStudio, PBC. | License: GPL-3 | file LICENSE */
"use strict";(function(){var a=eval;window.addEventListener("message",function(i){var e=i.data;e.code&&a(e.code)});})();
//# sourceMappingURL=shiny-testmode.js.map

View File

@@ -1,7 +1,7 @@
{
"version": 3,
"sources": ["../../../srcts/src/utils/eval.ts", "../../../srcts/extras/shiny-testmode.ts"],
"sourcesContent": ["//esbuild.github.io/content-types/#direct-eval\n//tl/dr;\n// * Direct usage of `eval(\"x\")` is bad with bundled code.\n// * Instead, use indirect calls to `eval` such as `indirectEval(\"x\")`\n// * Even just renaming the function works well enough.\n// > This is known as \"indirect eval\" because eval is not being called directly, and so does not trigger the grammatical special case for direct eval in the JavaScript VM. You can call indirect eval using any syntax at all except for an expression of the exact form eval('x'). For example, var eval2 = eval; eval2('x') and [eval][0]('x') and window.eval('x') are all indirect eval calls.\n// > When you use indirect eval, the code is evaluated in the global scope instead of in the inline scope of the caller.\nvar indirectEval = eval;\nexport { indirectEval };", "/* eslint-disable unicorn/filename-case */\nimport { indirectEval } from \"../src/utils/eval\"; // Listen for messages from parent frame. This file is only added when the\n// shiny.testmode option is TRUE.\n\nwindow.addEventListener(\"message\", function (e) {\n var message = e.data;\n if (message.code) indirectEval(message.code);\n});"],
"mappings": ";yBAOA,IAAIA,EAAe,KCHnB,OAAO,iBAAiB,UAAW,SAAUC,EAAG,CAC9C,IAAIC,EAAUD,EAAE,KACZC,EAAQ,MAAMC,EAAaD,EAAQ,IAAI,CAC7C,CAAC",
"sourcesContent": ["//esbuild.github.io/content-types/#direct-eval\n//tl/dr;\n// * Direct usage of `eval(\"x\")` is bad with bundled code.\n// * Instead, use indirect calls to `eval` such as `indirectEval(\"x\")`\n// * Even just renaming the function works well enough.\n// > This is known as \"indirect eval\" because eval is not being called directly, and so does not trigger the grammatical special case for direct eval in the JavaScript VM. You can call indirect eval using any syntax at all except for an expression of the exact form eval('x'). For example, var eval2 = eval; eval2('x') and [eval][0]('x') and window.eval('x') are all indirect eval calls.\n// > When you use indirect eval, the code is evaluated in the global scope instead of in the inline scope of the caller.\n\nvar indirectEval = eval;\nexport { indirectEval };", "/* eslint-disable unicorn/filename-case */\nimport { indirectEval } from \"../src/utils/eval\";\n\n// Listen for messages from parent frame. This file is only added when the\n// shiny.testmode option is TRUE.\nwindow.addEventListener(\"message\", function (e) {\n var message = e.data;\n if (message.code) indirectEval(message.code);\n});"],
"mappings": ";yBAQA,IAAIA,EAAe,KCHnB,OAAO,iBAAiB,UAAW,SAAUC,EAAG,CAC9C,IAAIC,EAAUD,EAAE,KACZC,EAAQ,MAAMC,EAAaD,EAAQ,IAAI,CAC7C,CAAC",
"names": ["indirectEval", "e", "message", "indirectEval"]
}

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -166,8 +166,8 @@ instead of the default 200 MB:
}\if{html}{\out{</div>}}
To use different settings for a session-scoped cache, you can set
\code{self$cache} at the top of your server function. By default, it will create
a 200 MB memory cache for each session, but you can replace it with
\code{session$cache} at the top of your server function. By default, it will
create a 200 MB memory cache for each session, but you can replace it with
something different. To use the session-scoped cache, you must also call
\code{bindCache()} with \code{cache="session"}. This will create a 100 MB cache for
the session:

View File

@@ -36,19 +36,25 @@ function.
\examples{
\dontrun{
ui <- fluidPage(
p("Choose a dataset to download."),
selectInput("dataset", "Dataset", choices = c("mtcars", "airquality")),
downloadButton("downloadData", "Download")
)
server <- function(input, output) {
# Our dataset
data <- mtcars
# The requested dataset
data <- reactive({
get(input$dataset)
})
output$downloadData <- downloadHandler(
filename = function() {
paste("data-", Sys.Date(), ".csv", sep="")
# Use the selected dataset as the suggested file name
paste0(input$dataset, ".csv")
},
content = function(file) {
write.csv(data, file)
# Write the dataset to the `file` that will be downloaded
write.csv(data(), file)
}
)
}

View File

@@ -64,7 +64,7 @@ updated and all observers have been run (default).}
\item{session}{The shiny session. Advanced use only.}
}
\description{
These functions allow you to dynamically add and remove arbirary UI
These functions allow you to dynamically add and remove arbitrary UI
into your app, whenever you want, as many times as you want.
Unlike \code{\link[=renderUI]{renderUI()}}, the UI generated with \code{insertUI()} is persistent:
once it's created, it stays there until removed by \code{removeUI()}. Each
@@ -76,7 +76,7 @@ function.
}
\details{
It's particularly useful to pair \code{removeUI} with \code{insertUI()}, but there is
no restriction on what you can use on. Any element that can be selected
no restriction on what you can use it on. Any element that can be selected
through a jQuery selector can be removed through this function.
}
\examples{

View File

@@ -17,7 +17,7 @@ memoryCache(
\arguments{
\item{max_size}{Maximum size of the cache, in bytes. If the cache exceeds
this size, cached objects will be removed according to the value of the
\code{evict}. Use \code{Inf} for no size limit. The default is 1 gigabyte.}
\code{evict}. Use \code{Inf} for no size limit. The default is 512 megabytes.}
\item{max_age}{Maximum age of files in cache before they are evicted, in
seconds. Use \code{Inf} for no age limit.}

View File

@@ -58,8 +58,7 @@ tabPanels}
navigation bar}
\item{collapsible}{\code{TRUE} to automatically collapse the navigation
elements into a menu when the width of the browser is less than 940 pixels
(useful for viewing on smaller touchscreen device)}
elements into an expandable menu on mobile devices or narrow window widths.}
\item{fluid}{\code{TRUE} to use a fluid layout. \code{FALSE} to use a fixed
layout.}

View File

@@ -86,8 +86,8 @@ Positive, negative, and zero values are allowed.}
automatically destroyed when its domain (if any) ends.}
\item{ignoreNULL}{Whether the action should be triggered (or value
calculated, in the case of \code{eventReactive}) when the input is
\code{NULL}. See Details.}
calculated, in the case of \code{eventReactive}) when the input event expression
is \code{NULL}. See Details.}
\item{ignoreInit}{If \code{TRUE}, then, when this \code{observeEvent} is
first created/initialized, ignore the \code{handlerExpr} (the second

View File

@@ -48,7 +48,7 @@ but when \code{size} is set, it will be a box instead.}
\item{...}{Arguments passed to \code{selectInput()}.}
\item{options}{A list of options. See the documentation of \pkg{selectize.js}
\item{options}{A list of options. See the documentation of \pkg{selectize.js}(\url{https://selectize.dev/docs/usage})
for possible options (character option values inside \code{\link[base:AsIs]{base::I()}} will
be treated as literal JavaScript code; see \code{\link[=renderDataTable]{renderDataTable()}}
for details).}
@@ -62,7 +62,7 @@ from a list of values.
}
\details{
By default, \code{selectInput()} and \code{selectizeInput()} use the JavaScript library
\pkg{selectize.js} (\url{https://github.com/selectize/selectize.js}) instead of
\pkg{selectize.js} (<https://selectize.dev/) instead of
the basic select input element. To use the standard HTML select input
element, use \code{selectInput()} with \code{selectize=FALSE}.

View File

@@ -47,28 +47,42 @@ testServer(server, {
# Testing a module --------------------------------------------------------
myModuleServer <- function(id, multiplier = 2, prefix = "I am ") {
# Testing the server function doesn't require a UI, but we've included it
# here for completeness. In this simple app, a user clicks a button to
# multiply a value by the module's multiplier argument. In the tests below,
# we'll make sure the value is 1, 2, 4, etc. with each button click.
multModuleUI <- function(id) {
ns <- NS(id)
tagList(
textOutput(ns("txt")),
actionButton(ns("multiply_it"), "Multiply It")
)
}
multModuleServer <- function(id, multiplier = 2) {
moduleServer(id, function(input, output, session) {
myreactive <- reactive({
input$x * multiplier
the_value <- reactive({
max(input$multiply_it * multiplier, 1)
})
output$txt <- renderText({
paste0(prefix, myreactive())
paste("The value is", the_value())
})
})
}
testServer(myModuleServer, args = list(multiplier = 2), {
session$setInputs(x = 1)
# You're also free to use third-party
# testing packages like testthat:
# expect_equal(myreactive(), 2)
stopifnot(myreactive() == 2)
stopifnot(output$txt == "I am 2")
testServer(multModuleServer, args = list(multiplier = 2), {
# Set the initial button value to 0
session$setInputs(multiply_it = 0)
stopifnot(the_value() == 1)
stopifnot(output$txt == "The value is 1")
session$setInputs(x = 2)
stopifnot(myreactive() == 4)
stopifnot(output$txt == "I am 4")
# Any additional arguments, below, are passed along to the module.
# Simulate two button clicks
session$setInputs(multiply_it = 2)
stopifnot(the_value() == 4)
stopifnot(output$txt == "The value is 4")
# Note: you're also free to use third-party
# testing packages like testthat:
# expect_equal(myreactive(), 1)
})
}

View File

@@ -62,7 +62,7 @@ the example section for a small demo of this feature.}
\item{selected}{The initially selected value (or multiple values if \code{multiple = TRUE}). If not specified then defaults to the first value for
single-select lists and no values for multiple select lists.}
\item{options}{A list of options. See the documentation of \pkg{selectize.js}
\item{options}{A list of options. See the documentation of \pkg{selectize.js}(\url{https://selectize.dev/docs/usage})
for possible options (character option values inside \code{\link[base:AsIs]{base::I()}} will
be treated as literal JavaScript code; see \code{\link[=renderDataTable]{renderDataTable()}}
for details).}

View File

@@ -42,7 +42,7 @@ but when \code{size} is set, it will be a box instead.}
\item{...}{Arguments passed to \code{varSelectInput()}.}
\item{options}{A list of options. See the documentation of \pkg{selectize.js}
\item{options}{A list of options. See the documentation of \pkg{selectize.js}(\url{https://selectize.dev/docs/usage})
for possible options (character option values inside \code{\link[base:AsIs]{base::I()}} will
be treated as literal JavaScript code; see \code{\link[=renderDataTable]{renderDataTable()}}
for details).}
@@ -57,7 +57,7 @@ from the column names of a data frame.
\details{
By default, \code{varSelectInput()} and \code{selectizeInput()} use the
JavaScript library \pkg{selectize.js}
(\url{https://github.com/selectize/selectize.js}) to instead of the basic
(\url{https://selectize.dev/}) to instead of the basic
select input element. To use the standard HTML select input element, use
\code{selectInput()} with \code{selectize=FALSE}.
}

View File

@@ -3,7 +3,7 @@
"homepage": "https://shiny.rstudio.com",
"repository": "github:rstudio/shiny",
"name": "@types/rstudio-shiny",
"version": "1.7.4-alpha.9001",
"version": "1.7.4-alpha.9002",
"license": "GPL-3.0-only",
"main": "",
"browser": "",
@@ -40,12 +40,11 @@
"@types/jest": "^26.0.23",
"@types/jqueryui": "1.12.16",
"@types/lodash": "^4.14.170",
"@types/node": "^15.6.1",
"@types/node": "^18.14.2",
"@types/showdown": "^1.9.3",
"@typescript-eslint/eslint-plugin": "^5.38.1",
"@typescript-eslint/parser": "^5.38.1",
"autoprefixer": "^10.2.6",
"bootstrap-datepicker": "1.9.0",
"browserslist": "^4.19.1",
"caniuse-lite": "^1.0.30001312",
"core-js": "^3.13.0",
@@ -60,9 +59,8 @@
"eslint-plugin-prettier": "^4.2.1",
"eslint-plugin-unicorn": "^43.0.2",
"fs-readdir-recursive": "^1.1.0",
"ion-rangeslider": "2.3.1",
"jest": "^26.6.3",
"jquery": "3.6.0",
"jquery": "^3.6.0",
"lodash": "^4.17.21",
"madge": "^4.0.2",
"node-gyp": "^8.1.0",
@@ -71,8 +69,6 @@
"prettier": "^2.7.1",
"readcontrol": "^1.0.0",
"replace": "^1.2.1",
"selectize": "0.12.4",
"strftime": "0.9.2",
"ts-jest": "^26",
"ts-node": "^10.9.1",
"type-coverage": "^2.22.0",

View File

@@ -197,7 +197,16 @@ class SliderInputBinding extends TextInputBindingBase {
msg.to = data.value[1];
} else {
if (Array.isArray(data.value)) {
throw "Slider only contains a single value and cannot be updated with an array";
const errorReason = [
"an empty array.",
"a single-value array.",
"an array with more than two values.",
];
throw (
"Slider requires two values to update with an array, " +
"but message value was " +
errorReason[Math.min(data.value.length, 2)]
);
}
msg.from = data.value;
}

View File

@@ -58,6 +58,7 @@ type BrushOpts = {
type Brush = {
reset: () => void;
hasOldBrush: () => boolean;
importOldBrush: () => void;
isInsideBrush: (offsetCss: Offset) => boolean;
isInResizeArea: (offsetCss: Offset) => boolean;
@@ -173,10 +174,15 @@ function createBrush(
if ($div) $div.remove();
}
function hasOldBrush(): boolean {
const oldDiv = $el.find("#" + el.id + "_brush");
return oldDiv.length > 0;
}
// If there's an existing brush div, use that div to set the new brush's
// settings, provided that the x, y, and panel variables have the same names,
// and there's a panel with matching panel variable values.
function importOldBrush() {
function importOldBrush(): void {
const oldDiv = $el.find("#" + el.id + "_brush");
if (oldDiv.length === 0) return;
@@ -617,6 +623,7 @@ function createBrush(
return {
reset: reset,
hasOldBrush,
importOldBrush: importOldBrush,
isInsideBrush: isInsideBrush,
isInResizeArea: isInResizeArea,

View File

@@ -57,6 +57,9 @@ function createClickHandler(
): CreateHandler {
const clickInfoSender = coordmap.mouseCoordinateSender(inputId, clip);
// Send initial (null) value on creation.
clickInfoSender(null);
return {
mousedown: function (e) {
// Listen for left mouse button only
@@ -90,6 +93,9 @@ function createHoverHandler(
hoverInfoSender = new Throttler(null, sendHoverInfo, delay);
else hoverInfoSender = new Debouncer(null, sendHoverInfo, delay);
// Send initial (null) value on creation.
hoverInfoSender.immediateCall(null);
// What to do when mouse exits the image
let mouseout: () => void;
@@ -233,6 +239,11 @@ function createBrushHandler(
brushInfoSender = new Debouncer(null, sendBrushInfo, opts.brushDelay);
}
// Send initial (null) value on creation.
if (!brush.hasOldBrush()) {
brushInfoSender.immediateCall();
}
function mousedown(e: JQuery.MouseDownEvent) {
// This can happen when mousedown inside the graphic, then mouseup
// outside, then mousedown inside. Just ignore the second

View File

@@ -22,7 +22,7 @@ class InputBatchSender implements InputPolicy {
if (opts.priority === "event") {
this._sendNow();
} else if (!this.sendIsEnqueued) {
this.shinyapp.actionQueue.enqueue(() => {
this.shinyapp.taskQueue.enqueue(() => {
this.sendIsEnqueued = false;
this._sendNow();
});

View File

@@ -114,7 +114,7 @@ class ShinyApp {
// without overlapping. This is used for handling incoming messages from the
// server and scheduling outgoing messages to the server, and can be used for
// other things tasks as well.
actionQueue = new AsyncQueue<() => Promise<void> | void>();
taskQueue = new AsyncQueue<() => Promise<void> | void>();
config: {
workerId: string;
@@ -240,7 +240,7 @@ class ShinyApp {
this.startActionQueueLoop();
};
socket.onmessage = (e) => {
this.actionQueue.enqueue(async () => await this.dispatchMessage(e.data));
this.taskQueue.enqueue(async () => await this.dispatchMessage(e.data));
};
// Called when a successfully-opened websocket is closed, or when an
// attempt to open a connection fails.
@@ -266,7 +266,7 @@ class ShinyApp {
async startActionQueueLoop(): Promise<void> {
// eslint-disable-next-line no-constant-condition
while (true) {
const action = await this.actionQueue.dequeue();
const action = await this.taskQueue.dequeue();
try {
await action();
@@ -725,8 +725,16 @@ class ShinyApp {
evt.message = message[i].message;
evt.binding = inputBinding;
$(el).trigger(evt);
if (!evt.isDefaultPrevented())
inputBinding.receiveMessage(el, evt.message);
if (!evt.isDefaultPrevented()) {
try {
inputBinding.receiveMessage(el, evt.message);
} catch (error) {
console.error(
"[shiny] Error in inputBinding.receiveMessage()",
{ error, binding: inputBinding, message: evt.message }
);
}
}
}
}
}

View File

@@ -1,5 +1,5 @@
import { InputBinding } from "./inputBinding";
declare type ActionButtonReceiveMessageData = {
type ActionButtonReceiveMessageData = {
label?: string;
icon?: string | [];
};

View File

@@ -1,7 +1,7 @@
import { InputBinding } from "./inputBinding";
declare type CheckedHTMLElement = HTMLInputElement;
declare type CheckboxChecked = CheckedHTMLElement["checked"];
declare type CheckboxReceiveMessageData = {
type CheckedHTMLElement = HTMLInputElement;
type CheckboxChecked = CheckedHTMLElement["checked"];
type CheckboxReceiveMessageData = {
value?: CheckboxChecked;
label?: string;
};

View File

@@ -1,16 +1,16 @@
import { InputBinding } from "./inputBinding";
import type { CheckedHTMLElement } from "./checkbox";
declare type CheckboxGroupHTMLElement = CheckedHTMLElement;
declare type ValueLabelObject = {
type CheckboxGroupHTMLElement = CheckedHTMLElement;
type ValueLabelObject = {
value: HTMLInputElement["value"];
label: string;
};
declare type CheckboxGroupReceiveMessageData = {
type CheckboxGroupReceiveMessageData = {
options?: string;
value?: Parameters<CheckboxGroupInputBinding["setValue"]>[1];
label: string;
};
declare type CheckboxGroupValue = CheckboxGroupHTMLElement["value"];
type CheckboxGroupValue = CheckboxGroupHTMLElement["value"];
declare class CheckboxGroupInputBinding extends InputBinding {
find(scope: HTMLElement): JQuery<HTMLElement>;
getValue(el: CheckboxGroupHTMLElement): CheckboxGroupValue[];

View File

@@ -9,7 +9,7 @@ declare global {
bsDatepicker(methodName: string, params: Date | null): void;
}
}
declare type DateReceiveMessageData = {
type DateReceiveMessageData = {
label: string;
min?: Date | null;
max?: Date | null;

View File

@@ -1,6 +1,6 @@
import { formatDateUTC } from "../../utils";
import { DateInputBindingBase } from "./date";
declare type DateRangeReceiveMessageData = {
type DateRangeReceiveMessageData = {
label: string;
min?: Date;
max?: Date;

View File

@@ -1,7 +1,7 @@
import { BindingRegistry } from "../registry";
import { InputBinding } from "./inputBinding";
import { FileInputBinding } from "./fileinput";
declare type InitInputBindings = {
type InitInputBindings = {
inputBindings: BindingRegistry<InputBinding>;
fileInputBinding: FileInputBinding;
};

View File

@@ -1,6 +1,6 @@
import { TextInputBindingBase } from "./text";
declare type NumberHTMLElement = HTMLInputElement;
declare type NumberReceiveMessageData = {
type NumberHTMLElement = HTMLInputElement;
type NumberReceiveMessageData = {
label: string;
value?: string | null;
min?: string | null;

View File

@@ -1,10 +1,10 @@
import { InputBinding } from "./inputBinding";
declare type RadioHTMLElement = HTMLInputElement;
declare type ValueLabelObject = {
type RadioHTMLElement = HTMLInputElement;
type ValueLabelObject = {
value: HTMLInputElement["value"];
label: string;
};
declare type RadioReceiveMessageData = {
type RadioReceiveMessageData = {
value?: string | [];
options?: ValueLabelObject[];
label: string;

View File

@@ -1,18 +1,18 @@
/// <reference types="selectize" />
import { InputBinding } from "./inputBinding";
import type { NotUndefined } from "../../utils/extraTypes";
declare type SelectHTMLElement = HTMLSelectElement & {
type SelectHTMLElement = HTMLSelectElement & {
nonempty: boolean;
};
declare type SelectInputReceiveMessageData = {
type SelectInputReceiveMessageData = {
label: string;
options?: string;
config?: string;
url?: string;
value?: string;
};
declare type SelectizeOptions = Selectize.IOptions<string, unknown>;
declare type SelectizeInfo = Selectize.IApi<string, unknown> & {
type SelectizeOptions = Selectize.IOptions<string, unknown>;
type SelectizeInfo = Selectize.IApi<string, unknown> & {
settings: SelectizeOptions;
};
declare class SelectInputBinding extends InputBinding {

View File

@@ -1,7 +1,7 @@
import type { TextHTMLElement } from "./text";
import { TextInputBindingBase } from "./text";
declare type TimeFormatter = (fmt: string, dt: Date) => string;
declare type SliderReceiveMessageData = {
type TimeFormatter = (fmt: string, dt: Date) => string;
type SliderReceiveMessageData = {
label: string;
value?: Array<number | string> | number | string;
min?: number;

View File

@@ -1,5 +1,5 @@
import { InputBinding } from "./inputBinding";
declare type TabInputReceiveMessageData = {
type TabInputReceiveMessageData = {
value?: string;
};
declare class BootstrapTabInputBinding extends InputBinding {

View File

@@ -1,6 +1,6 @@
import { InputBinding } from "./inputBinding";
declare type TextHTMLElement = HTMLInputElement;
declare type TextReceiveMessageData = {
type TextHTMLElement = HTMLInputElement;
type TextReceiveMessageData = {
label: string;
value?: TextHTMLElement["value"];
placeholder?: TextHTMLElement["placeholder"];

View File

@@ -1,6 +1,6 @@
import { BindingRegistry } from "../registry";
import { OutputBinding } from "./outputBinding";
declare type InitOutputBindings = {
type InitOutputBindings = {
outputBindings: BindingRegistry<OutputBinding>;
};
declare function initOutputBindings(): InitOutputBindings;

View File

@@ -1,7 +1,7 @@
import type { JQueryEventHandlerBase } from "bootstrap";
import "jquery";
declare type EvtPrefix<T extends string> = `${T}.${string}`;
declare type EvtFn<T extends JQuery.Event> = ((evt: T) => void) | null | undefined;
type EvtPrefix<T extends string> = `${T}.${string}`;
type EvtFn<T extends JQuery.Event> = ((evt: T) => void) | null | undefined;
declare global {
interface JQuery {
on(events: EvtPrefix<"change">, handler: EvtFn<JQuery.DragEvent>): this;

View File

@@ -1,11 +1,11 @@
import type { ShinyApp } from "../shiny/shinyapp";
declare type JobId = string;
declare type UploadUrl = string;
declare type UploadInitValue = {
type JobId = string;
type UploadUrl = string;
type UploadInitValue = {
jobId: JobId;
uploadUrl: UploadUrl;
};
declare type UploadEndValue = never;
type UploadEndValue = never;
declare class FileProcessor {
files: File[];
fileIndex: number;

View File

@@ -1,15 +1,15 @@
import type { Coordmap } from "./initCoordmap";
import type { Panel } from "./initPanelScales";
import type { Offset } from "./findbox";
declare type Bounds = {
type Bounds = {
xmin: number;
xmax: number;
ymin: number;
ymax: number;
};
declare type BoundsCss = Bounds;
declare type BoundsData = Bounds;
declare type ImageState = {
type BoundsCss = Bounds;
type BoundsData = Bounds;
type ImageState = {
brushing: boolean;
dragging: boolean;
resizing: boolean;
@@ -26,7 +26,7 @@ declare type ImageState = {
panel: Panel | null;
changeStartBounds: Bounds;
};
declare type BrushOpts = {
type BrushOpts = {
brushDirection: "x" | "xy" | "y";
brushClip: boolean;
brushFill: string;
@@ -36,8 +36,9 @@ declare type BrushOpts = {
brushDelay?: number;
brushResetOnNew?: boolean;
};
declare type Brush = {
type Brush = {
reset: () => void;
hasOldBrush: () => boolean;
importOldBrush: () => void;
isInsideBrush: (offsetCss: Offset) => boolean;
isInResizeArea: (offsetCss: Offset) => boolean;

View File

@@ -3,14 +3,14 @@ import type { BoundsCss, Bounds, BrushOpts } from "./createBrush";
import type { Offset } from "./findbox";
import type { Coordmap } from "./initCoordmap";
import type { Panel } from "./initPanelScales";
declare type CreateHandler = {
type CreateHandler = {
mousemove?: (e: JQuery.MouseMoveEvent) => void;
mouseout?: (e: JQuery.MouseOutEvent) => void;
mousedown?: (e: JQuery.MouseDownEvent) => void;
onResetImg: () => void;
onResize: ((e: JQuery.ResizeEvent) => void) | null;
};
declare type BrushInfo = {
type BrushInfo = {
xmin: number;
xmax: number;
ymin: number;
@@ -28,9 +28,9 @@ declare type BrushInfo = {
brushId?: string;
outputId?: string;
};
declare type InputId = Parameters<Coordmap["mouseCoordinateSender"]>[0];
declare type Clip = Parameters<Coordmap["mouseCoordinateSender"]>[1];
declare type NullOutside = Parameters<Coordmap["mouseCoordinateSender"]>[2];
type InputId = Parameters<Coordmap["mouseCoordinateSender"]>[0];
type Clip = Parameters<Coordmap["mouseCoordinateSender"]>[1];
type NullOutside = Parameters<Coordmap["mouseCoordinateSender"]>[2];
declare function createClickHandler(inputId: InputId, clip: Clip, coordmap: Coordmap): CreateHandler;
declare function createHoverHandler(inputId: InputId, delay: number, delayType: string | "throttle", clip: Clip, nullOutside: NullOutside, coordmap: Coordmap): CreateHandler;
declare function createBrushHandler(inputId: InputId, $el: JQuery<HTMLElement>, opts: BrushOpts, coordmap: Coordmap, outputId: BrushInfo["outputId"]): CreateHandler;

View File

@@ -1,5 +1,5 @@
import type { Bounds } from "./createBrush";
declare type Offset = {
type Offset = {
x: number;
y: number;
};

View File

@@ -3,13 +3,13 @@ import type { Offset } from "./findbox";
import type { Bounds } from "./createBrush";
import type { Panel, PanelInit } from "./initPanelScales";
declare function findOrigin($el: JQuery<HTMLElement>): Offset;
declare type OffsetCss = {
type OffsetCss = {
[key: string]: number;
};
declare type OffsetImg = {
type OffsetImg = {
[key: string]: number;
};
declare type CoordmapInit = {
type CoordmapInit = {
panels: PanelInit[];
dims: {
height: number;
@@ -19,7 +19,7 @@ declare type CoordmapInit = {
width: null;
};
};
declare type Coordmap = {
type Coordmap = {
panels: Panel[];
dims: {
height: number;

View File

@@ -1,6 +1,6 @@
import type { Offset } from "./findbox";
import type { Bounds } from "./createBrush";
declare type PanelInit = {
type PanelInit = {
domain: {
top: number;
bottom: number;
@@ -24,7 +24,7 @@ declare type PanelInit = {
[key: string]: number | string;
};
};
declare type Panel = PanelInit & {
type Panel = PanelInit & {
scaleDataToImg: {
(val: Bounds, clip?: boolean): Bounds;
};

View File

@@ -1,5 +1,5 @@
import type { InputPolicy, InputPolicyOpts } from "./inputPolicy";
declare type LastSentValues = {
type LastSentValues = {
[key: string]: {
[key: string]: string;
};

View File

@@ -1,6 +1,6 @@
import type { InputBinding } from "../bindings";
declare type EventPriority = "deferred" | "event" | "immediate";
declare type InputPolicyOpts = {
type EventPriority = "deferred" | "event" | "immediate";
type InputPolicyOpts = {
priority: EventPriority;
el?: HTMLElement;
binding?: InputBinding;

View File

@@ -1,6 +1,6 @@
import type { InputPolicy, InputPolicyOpts } from "./inputPolicy";
import type { InputRatePolicy } from "./inputRatePolicy";
declare type RatePolicyModes = "debounce" | "direct" | "throttle";
type RatePolicyModes = "debounce" | "direct" | "throttle";
declare class InputRateDecorator implements InputPolicy {
target: InputPolicy;
inputRatePolicies: {

View File

@@ -1,8 +1,8 @@
import type { InputBinding, OutputBinding } from "../bindings";
import type { BindingRegistry } from "../bindings/registry";
import type { InputRateDecorator, InputValidateDecorator } from "../inputPolicies";
declare type BindScope = HTMLElement | JQuery<HTMLElement>;
declare type BindInputsCtx = {
type BindScope = HTMLElement | JQuery<HTMLElement>;
type BindInputsCtx = {
inputs: InputValidateDecorator;
inputsRate: InputRateDecorator;
inputBindings: BindingRegistry<InputBinding>;

View File

@@ -13,27 +13,27 @@ declare function renderHtmlAsync(html: string, el: BindScope, dependencies: Html
declare function renderHtml(html: string, el: BindScope, dependencies: HtmlDep[], where?: WherePosition): ReturnType<typeof singletonsRenderHtml>;
declare function renderDependenciesAsync(dependencies: HtmlDep[] | null): Promise<void>;
declare function renderDependencies(dependencies: HtmlDep[] | null): void;
declare type HtmlDepVersion = string;
declare type MetaItem = {
type HtmlDepVersion = string;
type MetaItem = {
name: string;
content: string;
[x: string]: string;
};
declare type StylesheetItem = {
type StylesheetItem = {
href: string;
rel?: string;
type?: string;
};
declare type ScriptItem = {
type ScriptItem = {
src: string;
[x: string]: string;
};
declare type AttachmentItem = {
type AttachmentItem = {
key: string;
href: string;
[x: string]: string;
};
declare type HtmlDep = {
type HtmlDep = {
name: string;
version: HtmlDepVersion;
restyle?: boolean;

View File

@@ -1,26 +1,26 @@
import type { OutputBindingAdapter } from "../bindings/outputAdapter";
import type { UploadInitValue, UploadEndValue } from "../file/fileProcessor";
import { AsyncQueue } from "../utils/asyncQueue";
declare type ResponseValue = UploadEndValue | UploadInitValue;
declare type Handler = (message: any) => Promise<void> | void;
declare type ShinyWebSocket = WebSocket & {
type ResponseValue = UploadEndValue | UploadInitValue;
type Handler = (message: any) => Promise<void> | void;
type ShinyWebSocket = WebSocket & {
allowReconnect?: boolean;
};
declare type ErrorsMessageValue = {
type ErrorsMessageValue = {
message: string;
call: string[];
type?: string[];
};
declare type OnSuccessRequest = (value: ResponseValue) => void;
declare type OnErrorRequest = (err: string) => void;
declare type InputValues = {
type OnSuccessRequest = (value: ResponseValue) => void;
type OnErrorRequest = (err: string) => void;
type InputValues = {
[key: string]: unknown;
};
declare type MessageValue = Parameters<WebSocket["send"]>[0];
type MessageValue = Parameters<WebSocket["send"]>[0];
declare function addCustomMessageHandler(type: string, handler: Handler): void;
declare class ShinyApp {
$socket: ShinyWebSocket | null;
actionQueue: AsyncQueue<() => Promise<void> | void>;
taskQueue: AsyncQueue<() => Promise<void> | void>;
config: {
workerId: string;
sessionId: string;

View File

@@ -2,7 +2,7 @@ import type { BindScope } from "./bind";
declare const knownSingletons: {
[key: string]: boolean;
};
declare type WherePosition = "afterBegin" | "afterEnd" | "beforeBegin" | "beforeEnd" | "replace";
type WherePosition = "afterBegin" | "afterEnd" | "beforeBegin" | "beforeEnd" | "replace";
declare function renderHtml(html: string, el: BindScope, where: WherePosition): ReturnType<typeof processHtml>;
declare function registerNames(s: string[] | string): void;
declare function processHtml(val: string): {

View File

@@ -1,11 +1,11 @@
declare type AnyFunction = (...args: any[]) => any;
declare type AnyVoidFunction = (...args: any[]) => void;
declare type MapValuesUnion<T> = T[keyof T];
declare type MapWithResult<X, R> = {
type AnyFunction = (...args: any[]) => any;
type AnyVoidFunction = (...args: any[]) => void;
type MapValuesUnion<T> = T[keyof T];
type MapWithResult<X, R> = {
[Property in keyof X]: R;
};
/**
* Exclude undefined from T
*/
declare type NotUndefined<T> = T extends undefined ? never : T;
type NotUndefined<T> = T extends undefined ? never : T;
export type { AnyFunction, AnyVoidFunction, MapValuesUnion, MapWithResult, NotUndefined, };

View File

@@ -1,4 +1,4 @@
declare type UserAgent = typeof window.navigator.userAgent;
type UserAgent = typeof window.navigator.userAgent;
declare let userAgent: UserAgent;
declare function setUserAgent(userAgent_: UserAgent): void;
export type { UserAgent };

Submodule tests/testthat/apps deleted from c471e6449e

View File

@@ -126,6 +126,23 @@ test_that("ReactiveValues", {
expect_error(values$a <- 1)
})
test_that("reactiveValues keys are sorted", {
values <- reactiveValues(b=2, a=0)
values$C <- 13
values$A <- 0
values$c <- 3
values$B <- 12
# Setting an existing value shouldn't change order
values$a <- 1
values$A <- 11
expect_identical(isolate(names(values)), c("b", "a", "C", "A", "c", "B"))
expect_identical(
isolate(reactiveValuesToList(values)),
list(b=2, a=1, C=13, A=11, c=3, B=12)
)
})
test_that("reactiveValues() has useful print method", {
verify_output(test_path("print-reactiveValues.txt"), {
x <- reactiveValues(x = 1, y = 2, z = 3)

View File

@@ -268,3 +268,27 @@ test_that("quoToFunction handles nested quosures", {
func <- quoToFunction(quo_outer, "foo")
expect_identical(func(), 2)
})
test_that("toJSON can set digits using options - default", {
withr::local_options(list(shiny.json.digits = NULL))
expect_equal(
as.character(toJSON(pi)),
"[3.141592653589793]"
)
})
test_that("toJSON can set digits using options - number", {
withr::local_options(list(shiny.json.digits = 4))
expect_equal(
as.character(toJSON(pi)),
"[3.1416]"
)
})
test_that("toJSON can set digits using options - asis number", {
withr::local_options(list(shiny.json.digits = I(4)))
expect_equal(
as.character(toJSON(pi)),
"[3.142]"
)
})

View File

@@ -2,6 +2,15 @@ diff --git a/inst/www/shared/ionrangeslider/js/ion.rangeSlider.js b/inst/www/sha
index 2fe2c8d..89d204e 100644
--- a/inst/www/shared/ionrangeslider/js/ion.rangeSlider.js
+++ b/inst/www/shared/ionrangeslider/js/ion.rangeSlider.js
@@ -753,7 +753,7 @@
x = $handle.offset().left;
x += ($handle.width() / 2) - 1;
- this.pointerClick("single", {preventDefault: function () {}, pageX: x});
+ this.pointerClick("single", {preventDefault: function () {}, stopPropagation: function () {}, pageX: x});
}
},
@@ -816,6 +816,7 @@
*/
pointerDown: function (target, e) {

4528
yarn.lock

File diff suppressed because it is too large Load Diff