Compare commits

..

6 Commits

Author SHA1 Message Date
cpsievert
3f3399568f Sync package version (GitHub Actions) 2022-10-20 19:09:43 +00:00
Carson
f50076f2f9 bump version 2022-10-20 14:01:02 -05:00
Carson
c4e314ae04 Merge branch 'main' into vfill 2022-10-20 13:56:25 -05:00
Carson
b4a8236df5 Don't require latest bslib 2022-10-20 13:49:05 -05:00
Carson
a3743e2c90 Require dev version of bslib 2022-10-06 13:58:32 -05:00
Carson
0e401d268d plotOutput(), imageOutput(), and uiOutput() are now more capable of vertically stretching to a suitable container like fillPage() 2022-09-26 10:14:06 -05:00
75 changed files with 1724 additions and 4862 deletions

View File

@@ -44,6 +44,9 @@ rules:
semi:
- error
- always
newline-after-var:
- error
- always
dot-location:
- error
- property

View File

@@ -21,5 +21,3 @@ jobs:
node-version: "14.x"
R-CMD-check:
uses: rstudio/shiny-workflows/.github/workflows/R-CMD-check.yaml@v1
with:
cache-version: "2.1"

View File

@@ -1,7 +1,7 @@
Package: shiny
Type: Package
Title: Web Application Framework for R
Version: 1.7.4.9001
Version: 1.7.2.9001
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"),
@@ -78,8 +78,8 @@ Imports:
mime (>= 0.3),
jsonlite (>= 0.9.16),
xtable,
fontawesome (>= 0.4.0),
htmltools (>= 0.5.4),
fontawesome (>= 0.2.1),
htmltools (>= 0.5.2),
R6 (>= 2.0),
sourcetools,
later (>= 1.0.0),
@@ -202,10 +202,10 @@ Collate:
'version_selectize.R'
'version_strftime.R'
'viewer.R'
RoxygenNote: 7.2.3
RoxygenNote: 7.2.1
Encoding: UTF-8
Roxygen: list(markdown = TRUE)
RdMacros: lifecycle
Config/testthat/edition: 3
Config/Needs/check:
shinytest2
rstudio/shinytest2

218
NEWS.md
View File

@@ -1,4 +1,5 @@
# shiny 1.7.4.9001
shiny 1.7.2.9000
================
## Full changelog
@@ -6,38 +7,15 @@
### New features and improvements
* Closed #789: `<script>` loaded from dynamic UI are no longer loaded using synchronous `XMLHttpRequest` (via jQuery). (#3666)
### Bug fixes
# shiny 1.7.4
## Full changelog
### Breaking changes
* Closed #3719: Output container sizes, which are available via [`session$clientData` and `getCurrentOutputInfo()`](https://shiny.rstudio.com/articles/client-data.html), no longer round to the nearest pixel (i.e., they are now more exact, possibly fractional values). (#3720)
* Closed #3704, #3735, and #3740: `renderPlot()` no longer generates an error (or segfault) when it executes before the output is visible. Instead, it'll now use the graphics device's default size for it's initial size. Relatedly, `plotPNG()` now ignores `NULL` values for `width`/`height` (and uses the device's default `width`/`height` instead). (#3739)
### 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)
* Closed #3687: Updated jQuery-UI to v1.13.2. (#3697)
* Internal: Added clearer and strict TypeScript type definitions (#3644)
# shiny 1.7.3
### Bug fixes
* Shiny 1.7.0 changed the `icon(lib="fontawesome")` implementation from a bundled copy of fontawesome, to the {fontawesome} package. This led to issue #3688, where icons that were previously working, were now breaking. That's because {fontawesome} 0.3.0 and earlier did not have support for icon names used in Font Awesome 5 and earlier, only the newest icon names used in Font Awesome 6. Now, {fontawesome} 0.4.0 has restored support for those older icon names, and Shiny 1.7.2.1 has updated its {fontawesome} requirement to >=0.4.0.
* Closed #3687: Updated jQuery-UI to v1.13.2. (#3697)
# shiny 1.7.2
shiny 1.7.2
===========
## Full changelog
@@ -88,7 +66,8 @@
* HTML dependencies that are sent to dynamic UI now have better type checking, and no longer require a `dep.src.href` field. (#3537)
# shiny 1.7.1
shiny 1.7.1
===========
## Bug Fixes
@@ -97,7 +76,8 @@
* Re-arranged conditions for testthat 1.0.0 compatibility. (#3512)
# shiny 1.7.0
shiny 1.7.0
===========
## Full changelog
@@ -154,7 +134,8 @@
* Updated to jQuery 3.6.0. (#3311)
# shiny 1.6.0
shiny 1.6.0
===========
This release focuses on improvements in three main areas:
@@ -263,7 +244,8 @@ This release focuses on improvements in three main areas:
* Removed es5-shim library, which was internally used within `selectInput()` for ECMAScript 5 compatibility. (#2993)
# shiny 1.5.0
shiny 1.5.0
===========
## Full changelog
@@ -316,17 +298,20 @@ This release focuses on improvements in three main areas:
* Updated from Font-Awesome 5.3.1 to 5.13.0, which includes icons related to COVID-19. For upgrade notes, see https://github.com/FortAwesome/Font-Awesome/blob/master/UPGRADING.md. (#2891)
# shiny 1.4.0.2
shiny 1.4.0.2
===========
Minor patch release: fixed some timing-dependent tests failed intermittently on CRAN build machines.
# shiny 1.4.0.1
shiny 1.4.0.1
===========
Minor patch release to account for changes to the grid package that will be upcoming in the R 4.0 release (#2776).
# shiny 1.4.0
shiny 1.4.0
===========
## Full changelog
@@ -389,7 +374,8 @@ Minor patch release to account for changes to the grid package that will be upco
* Fixed #2329, #1817: These bugs were reported as fixed in Shiny 1.3.0 but were not actually fixed because some JavaScript changes were accidentally not included in the release. The fix resolves issues that occur when `withProgressBar()` or bookmarking are combined with the [networkD3](https://christophergandrud.github.io/networkD3/) package's Sankey plot.
# shiny 1.3.2
shiny 1.3.2
===========
### Bug fixes
@@ -398,7 +384,8 @@ Minor patch release to account for changes to the grid package that will be upco
* Fixed #2280: Shiny applications that used a www/index.html file did not serve up the index file. (#2382)
# shiny 1.3.1
shiny 1.3.1
===========
## Full changelog
@@ -407,7 +394,8 @@ Minor patch release to account for changes to the grid package that will be upco
* Fixed a performance issue introduced in v1.3.0 when using large nested lists within Shiny. (#2377)
# shiny 1.3.0
shiny 1.3.0
===========
## Full changelog
@@ -438,7 +426,8 @@ Minor patch release to account for changes to the grid package that will be upco
* Fixed #2247: `renderCachedPlot` now supports using promises for either `expr` or `cacheKeyExpr`. (Shiny v1.2.0 supported async `expr`, but only if `cacheKeyExpr` was async as well; now you can use any combination of sync/async for `expr` and `cacheKeyExpr`.) #2261
# shiny 1.2.0
shiny 1.2.0
===========
This release features plot caching, an important new tool for improving performance and scalability. Using `renderCachedPlot` in place of `renderPlot` can greatly improve responsiveness for apps that show the same plot many times (for example, a dashboard or report where all users view the same data). Shiny gives you a fair amount of control in where the cache is stored and how cached plots are invalidated, so be sure to read [this article](https://shiny.rstudio.com/articles/plot-caching.html) to get the most out of this feature.
@@ -503,7 +492,8 @@ This release features plot caching, an important new tool for improving performa
* Addressed #1864 by changing `optgroup` documentation to use `list` instead of `c`. (#2084)
# shiny 1.1.0
shiny 1.1.0
===========
This is a significant release for Shiny, with a major new feature that was nearly a year in the making: support for asynchronous operations! Until now, R's single-threaded nature meant that performing long-running calculations or tasks from Shiny would bring your app to a halt for other users of that process. This release of Shiny deeply integrates the [promises](https://rstudio.github.io/promises/) package to allow you to execute some tasks asynchronously, including as part of reactive expressions and outputs. See the [promises](https://rstudio.github.io/promises/) documentation to learn more.
@@ -576,7 +566,8 @@ This is a significant release for Shiny, with a major new feature that was nearl
In some rare cases, interrupting an application (by pressing Ctrl-C or Esc) may result in the message `Error in execCallbacks(timeoutSecs) : c++ exception (unknown reason)`. Although this message sounds alarming, it is harmless, and will go away in a future version of the later package (more information [here](https://github.com/r-lib/later/issues/55)).
# shiny 1.0.5
shiny 1.0.5
===========
## Full changelog
@@ -589,7 +580,8 @@ In some rare cases, interrupting an application (by pressing Ctrl-C or Esc) may
* Fixed #1824: HTTP HEAD requests on static files caused the application to stop. (#1825)
# shiny 1.0.4
shiny 1.0.4
===========
There are three headlining features in this release of Shiny. It is now possible to add and remove tabs from a `tabPanel`; there is a new function, `onStop()`, which registers callbacks that execute when an application exits; and `fileInput`s now can have files dragged and dropped on them. In addition to these features, this release has a number of minor features and bug fixes. See the full changelog below for more details.
@@ -650,7 +642,8 @@ There are three headlining features in this release of Shiny. It is now possible
* Fixed #1474: A `browser()` call in an observer could cause an error in the RStudio IDE on Windows. (#1802)
# shiny 1.0.3
shiny 1.0.3
================
This is a hotfix release of Shiny. With previous versions of Shiny, when running an application on the newly-released version of R, 3.4.0, it would print a message: `Warning in body(fun) : argument is not a function`. This has no effect on the application, but because the message could be alarming to users, we are releasing a new version of Shiny that fixes this issue.
@@ -663,7 +656,8 @@ This is a hotfix release of Shiny. With previous versions of Shiny, when running
* Fixed #1676: On R 3.4.0, running a Shiny application gave a warning: `Warning in body(fun) : argument is not a function`. (#1677)
# shiny 1.0.2
shiny 1.0.2
================
This is a hotfix release of Shiny. The primary reason for this release is because the web host for MathJax JavaScript library is scheduled to be shut down in the next few weeks. After it is shut down, Shiny applications that use MathJax will no longer be able to load the MathJax library if they are run with Shiny 1.0.1 and below. (If you don't know whether your application uses MathJax, it probably does not.) For more information about why the MathJax CDN is shutting down, see https://www.mathjax.org/cdn-shutting-down/.
@@ -682,7 +676,8 @@ This is a hotfix release of Shiny. The primary reason for this release is becaus
* Fixed #1653: wrong code example in documentation. (#1658)
# shiny 1.0.1
shiny 1.0.1
================
This is a maintenance release of Shiny, mostly aimed at fixing bugs and introducing minor features. The most notable additions in this version of Shiny are the introduction of the `reactiveVal()` function (it's like `reactiveValues()`, but it only stores a single value), and that the choices of `radioButtons()` and `checkboxGroupInput()` can now contain HTML content instead of just plain text.
@@ -752,7 +747,8 @@ in shiny apps. For more info, see the documentation (`?updateQueryString` and `?
* Closed #1500: Updated ion.rangeSlider to 2.1.6. (#1540)
# shiny 1.0.0
shiny 1.0.0
===========
Shiny has reached a milestone: version 1.0.0! In the last year, we've added two major features that we considered essential for a 1.0.0 release: bookmarking, and support for testing Shiny applications. As usual, this version of Shiny also includes many minor features and bug fixes.
@@ -817,7 +813,8 @@ Now there's an official way to slow down reactive values and expressions that in
* Updated to Font Awesome 4.7.0.
# shiny 0.14.2
shiny 0.14.2
============
This is a maintenance release of Shiny, with some bug fixes and minor new features.
@@ -845,7 +842,8 @@ This is a maintenance release of Shiny, with some bug fixes and minor new featur
* Fixed a bug where, in versions of R before 3.2, Shiny applications could crash due to a bug in R's implementation of `list2env()`. (#1446)
# shiny 0.14.1
shiny 0.14.1
============
This is a maintenance release of Shiny, with some bug fixes and minor new features.
@@ -875,7 +873,8 @@ This is a maintenance release of Shiny, with some bug fixes and minor new featur
* Updated to jQuery UI 1.12.1. Previously, Shiny included a build of 1.11.4 which was missing the datepicker component due to a conflict with the bootstrap-datepicker used by Shiny's `dateInput()` and `dateRangeInput()`. (#1374)
# shiny 0.14
shiny 0.14
==========
A new Shiny release is upon us! There are many new exciting features, bug fixes, and library updates. We'll just highlight the most important changes here, but you can browse through the full changelog below for details. This will likely be the last release before shiny 1.0, so get out your party hats!
@@ -1074,12 +1073,14 @@ There are many more minor features, small improvements, and bug fixes than we ca
* Updated to jQuery 1.12.4.
# shiny 0.13.2
shiny 0.13.2
============
* Updated documentation for `htmlTemplate`.
# shiny 0.13.1
shiny 0.13.1
============
* `flexCol` did not work on RStudio for Windows or Linux.
@@ -1088,7 +1089,8 @@ There are many more minor features, small improvements, and bug fixes than we ca
* BREAKING CHANGE: The long-deprecated ability to pass functions (rather than expressions) to reactive() and observe() has finally been removed.
# shiny 0.13.0
shiny 0.13.0
============
* Fixed #962: plot interactions did not work with the development version of ggplot2 (after ggplot2 1.0.1).
@@ -1139,7 +1141,8 @@ There are many more minor features, small improvements, and bug fixes than we ca
* Added support for the new htmltools 0.3 feature `htmlTemplate`. It's now possible to use regular HTML markup to design your UI, but still use R expressions to define inputs, outputs, and HTML widgets.
# shiny 0.12.2
shiny 0.12.2
============
* GitHub changed URLs for gists from .tar.gz to .zip, so `runGist` was updated to work with the new URLs.
@@ -1162,14 +1165,16 @@ There are many more minor features, small improvements, and bug fixes than we ca
* Shiny now correctly handles HTTP HEAD requests. (#876)
# shiny 0.12.1
shiny 0.12.1
============
* Fixed an issue where unbindAll() causes subsequent bindAll() to be ignored for previously bound outputs. (#856)
* Undeprecate `dataTableOutput` and `renderDataTable`, which had been deprecated in favor of the new DT package. The DT package is a bit too new and has a slightly different API, we were too hasty in deprecating the existing Shiny functions.
# shiny 0.12.0
shiny 0.12.0
============
In addition to the changes listed below (in the *Full Changelog* section), there is an infrastructure change that could affect existing Shiny apps.
@@ -1225,7 +1230,8 @@ Shiny 0.12.0 deprecated Shiny's dataTableOutput and renderDataTable functions an
* renderDataTable() and dataTableOutput() have been deprecated in shiny and will be removed in future versions of shiny. Please use the DT package instead: http://rstudio.github.io/DT/ (#807)
# shiny 0.11.1
shiny 0.11.1
============
* Major client-side performance improvements for pages that have many conditionalPanels, tabPanels, and plotOutputs. (#693, #717, #723)
@@ -1252,7 +1258,8 @@ Shiny 0.12.0 deprecated Shiny's dataTableOutput and renderDataTable functions an
* downloadHandler content callback functions are now invoked with a temp file name that has the same extension as the final filename that will be used by the download. This is to deal with the fact that some file writing functions in R will auto-append the extension for their file type (pdf, zip).
# shiny 0.11
shiny 0.11
==========
Shiny 0.11 switches away from the Bootstrap 2 web framework to the next version, Bootstrap 3. This is in part because Bootstrap 2 is no longer being developed, and in part because it allows us to tap into the ecosystem of Bootstrap 3 themes.
@@ -1330,17 +1337,20 @@ Along with the release of Shiny 0.11, we've packaged up some Bootstrap 3 themes
* Password input fields can now be used, with `passwordInput()`. (#672)
# shiny 0.10.2.2
shiny 0.10.2.2
==============
* Remove use of `rstudio::viewer` in a code example, for R CMD check.
# shiny 0.10.2.1
shiny 0.10.2.1
==============
* Changed some examples to use \donttest instead of \dontrun.
# shiny 0.10.2
shiny 0.10.2
============
* The minimal version of R required for the shiny package is 3.0.0 now.
@@ -1373,7 +1383,8 @@ Along with the release of Shiny 0.11, we've packaged up some Bootstrap 3 themes
* Added `position` parameter to `navbarPage`.
# shiny 0.10.1
shiny 0.10.1
============
* Added Unicode support for Windows. Shiny apps running on Windows must use the UTF-8 encoding for ui.R and server.R (also the optional global.R) if they contain non-ASCII characters. See this article for details and examples: http://shiny.rstudio.com/gallery/unicode-characters.html (#516)
@@ -1386,7 +1397,8 @@ Along with the release of Shiny 0.11, we've packaged up some Bootstrap 3 themes
* Added support for option groups in the select/selectize inputs. When the `choices` argument for `selectInput()`/`selectizeInput()` is a list of sub-lists and any sub-list is of length greater than 1, the HTML tag `<optgroup>` will be used. See an example at http://shiny.rstudio.com/gallery/option-groups-for-selectize-input.html (#542)
# shiny 0.10.0
shiny 0.10.0
============
* BREAKING CHANGE: By default, observers now terminate themselves if they were created during a session and that session ends. See ?domains for more details.
@@ -1423,12 +1435,14 @@ Along with the release of Shiny 0.11, we've packaged up some Bootstrap 3 themes
* `runGitHub()` can also take a value of the form "username/repo" in its first argument, e.g. both runGitHub("shiny_example", "rstudio") and runGitHub("rstudio/shiny_example") are valid ways to run the GitHub repo.
# shiny 0.9.1
shiny 0.9.1
===========
* Fixed warning 'Error in Context$new : could not find function "loadMethod"' that was happening to dependent packages on "R CMD check".
# shiny 0.9.0
shiny 0.9.0
===========
* BREAKING CHANGE: Added a `host` parameter to runApp() and runExample(), which defaults to the shiny.host option if it is non-NULL, or "127.0.0.1" otherwise. This means that by default, Shiny applications can only be accessed on the same machine from which they are served. To allow other clients to connect, as in previous versions of Shiny, use "0.0.0.0" (or the IP address of one of your network interfaces, if you care to be explicit about it).
@@ -1501,7 +1515,8 @@ Along with the release of Shiny 0.11, we've packaged up some Bootstrap 3 themes
* Dots are now legal characters for inputId/outputId. (Thanks, Kevin Lindquist. #358)
# shiny 0.8.0
shiny 0.8.0
===========
* Debug hooks are registered on all user-provided functions and (reactive) expressions (e.g., in renderPlot()), which makes it possible to set breakpoints in these functions using the latest version of the RStudio IDE, and the RStudio visual debugging tools can be used to debug Shiny apps. Internally, the registration is done via installExprFunction(), which is a new function introduced in this version to replace exprToFunction() so that the registration can be automatically done.
@@ -1520,7 +1535,8 @@ Along with the release of Shiny 0.11, we've packaged up some Bootstrap 3 themes
* The minimal required version for the httpuv package was increased to 1.2 (on CRAN now).
# shiny 0.7.0
shiny 0.7.0
===========
* Stopped sending websocket subprotocol. This fixes a compatibility issue with Google Chrome 30.
@@ -1549,7 +1565,8 @@ Along with the release of Shiny 0.11, we've packaged up some Bootstrap 3 themes
* Add shiny.sharedSecret option, to require the HTTP header Shiny-Shared-Secret to be set to the given value.
# shiny 0.6.0
shiny 0.6.0
===========
* `tabsetPanel()` can be directed to start with a specific tab selected.
@@ -1580,7 +1597,8 @@ Along with the release of Shiny 0.11, we've packaged up some Bootstrap 3 themes
* Shiny apps can be run without a server.r and ui.r file.
# shiny 0.5.0
shiny 0.5.0
===========
* Switch from websockets package for handling websocket connections to httpuv.
@@ -1597,14 +1615,16 @@ Along with the release of Shiny 0.11, we've packaged up some Bootstrap 3 themes
* Fix bug #55, where `renderTable()` would throw error with an empty data frame.
# shiny 0.4.1
shiny 0.4.1
===========
* Fix bug where width and height weren't passed along properly from `reactivePlot` to `renderPlot`.
* Fix bug where infinite recursion would happen when `reactivePlot` was passed a function for width or height.
# shiny 0.4.0
shiny 0.4.0
===========
* Added suspend/resume capability to observers.
@@ -1619,7 +1639,8 @@ Along with the release of Shiny 0.11, we've packaged up some Bootstrap 3 themes
* Fixed a bug where empty values in a numericInput were sent to the R process as 0. They are now sent as NA.
# shiny 0.3.1
shiny 0.3.1
===========
* Fix issue #91: bug where downloading files did not work.
@@ -1628,7 +1649,8 @@ Along with the release of Shiny 0.11, we've packaged up some Bootstrap 3 themes
* Reactive functions now preserve the visible/invisible state of their returned values.
# shiny 0.3.0
shiny 0.3.0
===========
* Reactive functions are now evaluated lazily.
@@ -1653,44 +1675,52 @@ Along with the release of Shiny 0.11, we've packaged up some Bootstrap 3 themes
* Fix issue #64, where pressing Enter in a textbox would cause a form to submit.
# shiny 0.2.4
shiny 0.2.4
===========
* `runGist` has been updated to use the new download URLs from https://gist.github.com.
* Shiny now uses `CairoPNG()` for output, when the Cairo package is available. This provides better-looking output on Linux and Windows.
# shiny 0.2.3
shiny 0.2.3
===========
* Ignore request variables for routing purposes
# shiny 0.2.2
shiny 0.2.2
===========
* Fix CRAN warning (assigning to global environment)
# shiny 0.2.1
shiny 0.2.1
===========
* [BREAKING] Modify API of `downloadHandler`: The `content` function now takes a file path, not writable connection, as an argument. This makes it much easier to work with APIs that only write to file paths, not connections.
# shiny 0.2.0
shiny 0.2.0
===========
* Fix subtle name resolution bug--the usual symptom being S4 methods not being invoked correctly when called from inside of ui.R or server.R
# shiny 0.1.14
shiny 0.1.14
===========
* Fix slider animator, which broke in 0.1.10
# shiny 0.1.13
shiny 0.1.13
===========
* Fix temp file leak in reactivePlot
# shiny 0.1.12
shiny 0.1.12
===========
* Fix problems with runGist on Windows
@@ -1699,7 +1729,8 @@ Along with the release of Shiny 0.11, we've packaged up some Bootstrap 3 themes
* Add CSS hooks for app-wide busy indicators
# shiny 0.1.11
shiny 0.1.11
===========
* Fix input binding with IE8 on Shiny Server
@@ -1708,7 +1739,8 @@ Along with the release of Shiny 0.11, we've packaged up some Bootstrap 3 themes
* Allow dynamic sizing of reactivePlot (i.e. using a function instead of a fixed value)
# shiny 0.1.10
shiny 0.1.10
===========
* Support more MIME types when serving out of www
@@ -1721,7 +1753,8 @@ Along with the release of Shiny 0.11, we've packaged up some Bootstrap 3 themes
* Fix plot rendering with IE8 on Shiny Server
# shiny 0.1.9
shiny 0.1.9
===========
* Much less flicker when updating plots
@@ -1730,7 +1763,8 @@ Along with the release of Shiny 0.11, we've packaged up some Bootstrap 3 themes
* Add `includeText`, `includeHTML`, and `includeMarkdown` functions for putting text, HTML, and Markdown content from external files in the application's UI.
# shiny 0.1.8
shiny 0.1.8
===========
* Add `runGist` function for conveniently running a Shiny app that is published on gist.github.com.
@@ -1743,7 +1777,8 @@ Along with the release of Shiny 0.11, we've packaged up some Bootstrap 3 themes
* Add `bootstrapPage` function for creating new Bootstrap based layouts from scratch.
# shiny 0.1.7
shiny 0.1.7
===========
* Fix issue #26: Shiny.OutputBindings not correctly exported.
@@ -1752,7 +1787,8 @@ Along with the release of Shiny 0.11, we've packaged up some Bootstrap 3 themes
* Transcode JSON into UTF-8 (prevents non-ASCII reactivePrint values from causing errors on Windows).
# shiny 0.1.6
shiny 0.1.6
===========
* Import package dependencies, instead of attaching them (with the exception of websockets, which doesn't currently work unless attached).
@@ -1761,7 +1797,8 @@ Along with the release of Shiny 0.11, we've packaged up some Bootstrap 3 themes
* bindAll was not correctly sending initial values to the server; fixed.
# shiny 0.1.5
shiny 0.1.5
===========
* BREAKING CHANGE: JS APIs Shiny.bindInput and Shiny.bindOutput removed and replaced with Shiny.bindAll; Shiny.unbindInput and Shiny.unbindOutput removed and replaced with Shiny.unbindAll.
@@ -1776,7 +1813,8 @@ Along with the release of Shiny 0.11, we've packaged up some Bootstrap 3 themes
* htmlOutput (CSS class `shiny-html-output`) can contain inputs and outputs.
# shiny 0.1.4
shiny 0.1.4
===========
* Allow Bootstrap tabsets to act as reactive inputs; their value indicates which tab is active
@@ -1789,7 +1827,8 @@ Along with the release of Shiny 0.11, we've packaged up some Bootstrap 3 themes
* Add Shiny.bindInputs(scope), .unbindInputs(scope), .bindOutputs(scope), and .unbindOutputs(scope) JS API calls to allow dynamic binding/unbinding of HTML elements
# shiny 0.1.3
shiny 0.1.3
===========
* Introduce Shiny.inputBindings.register JS API and InputBinding class, for creating custom input controls
@@ -1802,6 +1841,7 @@ Along with the release of Shiny 0.11, we've packaged up some Bootstrap 3 themes
* Fix issue #10: Plots in tabsets not rendered
# shiny 0.1.2
shiny 0.1.2
===========
* Initial private beta release!

View File

@@ -25,7 +25,6 @@
#' `- tests
#' |- testthat.R
#' `- testthat
#' |- setup-shinytest2.R
#' |- test-examplemodule.R
#' |- test-server.R
#' |- test-shinytest2.R
@@ -47,7 +46,6 @@
#' `tests/testthat/` directory using the
#' [shinytest2](https://rstudio.github.io/shinytest2/reference/test_app.html)
#' package.
#' * `tests/testthat/setup-shinytest2.R` is setup file to source your `./R` folder into the testing environment.
#' * `tests/testthat/test-examplemodule.R` is a test for an application's module server function.
#' * `tests/testthat/test-server.R` is a test for the application's server code
#' * `tests/testthat/test-shinytest2.R` is a test that uses the
@@ -128,7 +126,7 @@ shinyAppTemplate <- function(path = NULL, examples = "default", dryrun = FALSE)
}
if ("tests" %in% examples) {
rlang::check_installed("shinytest2", "for {testthat} tests to work as expected", version = "0.2.0")
rlang::check_installed("shinytest2", "for {testthat} tests to work as expected")
}
# =======================================================

View File

@@ -57,17 +57,18 @@ bootstrapPage <- function(..., title = NULL, theme = NULL, lang = NULL) {
ui <- attachDependencies(ui, bootstrapLib())
}
setLang(ui, lang)
setHtmlAttr(ui, "lang", lang)
}
setLang <- function(ui, lang) {
# Add lang attribute to be passed to renderPage function
attr(ui, "lang") <- lang
setHtmlAttr <- function(ui, attr_name, attr_value) {
attrs <- getHtmlAttrs(ui)
attrs[[attr_name]] <- attr_value
attr(ui, "html_attrs") <- attrs
ui
}
getLang <- function(ui) {
getHtmlAttrs <- function(ui) {
# Check if ui has lang attribute; otherwise, NULL
attr(ui, "lang", exact = TRUE)
attr(ui, "html_attrs", exact = TRUE)
}
#' Bootstrap libraries
@@ -331,10 +332,22 @@ fillPage <- function(..., padding = 0, title = NULL, bootstrap = TRUE,
...
)
ui <- setLang(ui, lang)
ui <- setHtmlAttr(ui, "lang", lang)
}
return(ui)
# vfill-container rules live in bslib which means something like
# fillPage(plotOutput("x")) won't work unless a bslib theme is used
setBodyAttr(ui, "class", "vfill-container")
}
setBodyAttr <- function(ui, attr_name, attr_value) {
attrs <- getBodyAttrs(ui)
attrs[[attr_name]] <- attr_value
attr(ui, "body_attrs") <- attrs
ui
}
getBodyAttrs <- function(ui) {
attr(ui, "body_attrs", exact = TRUE)
}
collapseSizes <- function(padding) {
@@ -792,9 +805,9 @@ verbatimTextOutput <- function(outputId, placeholder = FALSE) {
#' @name plotOutput
#' @rdname plotOutput
#' @export
imageOutput <- function(outputId, width = "100%", height="400px",
imageOutput <- function(outputId, width = NULL, height = NULL,
click = NULL, dblclick = NULL, hover = NULL, brush = NULL,
inline = FALSE, fill = FALSE) {
inline = FALSE) {
style <- if (!inline) {
# Using `css()` here instead of paste/sprintf so that NULL values will
@@ -807,6 +820,7 @@ imageOutput <- function(outputId, width = "100%", height="400px",
args <- list(
id = outputId,
class = "shiny-image-output",
class = if (is.null(height)) "vfill-item",
style = style
)
@@ -850,8 +864,7 @@ imageOutput <- function(outputId, width = "100%", height="400px",
}
container <- if (inline) span else div
res <- do.call(container, args)
bindFillRole(res, item = fill)
do.call(container, args)
}
#' Create an plot or image output element
@@ -919,11 +932,6 @@ imageOutput <- function(outputId, width = "100%", height="400px",
#' `imageOutput`/`plotOutput` calls may share the same `id`
#' value; brushing one image or plot will cause any other brushes with the
#' same `id` to disappear.
#' @param fill Whether or not the returned tag should be treated as a fill item,
#' meaning that its `height` is allowed to grow/shrink to fit a fill container
#' with an opinionated height (see [htmltools::bindFillRole()]) with an
#' opinionated height. Examples of fill containers include `bslib::card()` and
#' `bslib::card_body_fill()`.
#' @inheritParams textOutput
#' @note The arguments `clickId` and `hoverId` only work for R base graphics
#' (see the \pkg{\link[graphics:graphics-package]{graphics}} package). They do
@@ -1092,13 +1100,13 @@ imageOutput <- function(outputId, width = "100%", height="400px",
#'
#' }
#' @export
plotOutput <- function(outputId, width = "100%", height="400px",
plotOutput <- function(outputId, width = NULL, height = NULL,
click = NULL, dblclick = NULL, hover = NULL, brush = NULL,
inline = FALSE, fill = !inline) {
inline = FALSE) {
# Result is the same as imageOutput, except for HTML class
res <- imageOutput(outputId, width, height, click, dblclick,
hover, brush, inline, fill)
hover, brush, inline)
res$attribs$class <- "shiny-plot-output"
res
@@ -1147,16 +1155,12 @@ dataTableOutput <- function(outputId) {
#' `uiOutput` is intended to be used with `renderUI` on the server side. It is
#' currently just an alias for `htmlOutput`.
#'
#' @param outputId output variable to read the value from
#' @param outputId output variable to read the value from.
#' @param ... Other arguments to pass to the container tag function. This is
#' useful for providing additional classes for the tag.
#' @param fill If `TRUE`, the result of `container` is treated as _both_ a fill
#' item and container (see [htmltools::bindFillRole()]), which means both the
#' `container` as well as its immediate children (i.e., the result of
#' `renderUI()`) are allowed to grow/shrink to fit a fill container with an
#' opinionated height. Set `fill = "item"` or `fill = "container"` to treat
#' `container` as just a fill item or a fill container.
#' @inheritParams textOutput
#' @param fill Whether the output should be allowed to grow/shrink to the size
#' of the `container`. Ignored when `inline = TRUE`.
#' @return An HTML output element that can be included in a panel
#' @examples
#' htmlOutput("summary")
@@ -1172,10 +1176,12 @@ htmlOutput <- function(outputId, inline = FALSE,
if (any_unnamed(list(...))) {
warning("Unnamed elements in ... will be replaced with dynamic UI.")
}
res <- container(id = outputId, class = "shiny-html-output", ...)
bindFillRole(
res, item = isTRUE(fill) || isTRUE("item" == fill),
container = isTRUE(fill) || isTRUE("container" == fill)
container(
id = outputId,
class = "shiny-html-output",
class = if (fill && !inline) "vfill-container vfill-item",
...
)
}

View File

@@ -1,6 +1,6 @@
#' Shiny Developer Mode
#'
#' @description `r lifecycle::badge("experimental")`
#' @description \lifecycle{experimental}
#'
#' Developer Mode enables a number of [options()] to make a developer's life
#' easier, like enabling non-minified JS and printing messages about

View File

@@ -11,13 +11,7 @@ startPNG <- function(filename, width, height, res, ...) {
grDevices::png
}
args <- list2(filename = filename, width = width, height = height, res = res, ...)
# It's possible for width/height to be NULL/numeric(0) (e.g., when using
# suspendWhenHidden=F w/ tabsetPanel(), see rstudio/shiny#1409), so when
# this happens let the device determine what the default size should be.
if (length(args$width) == 0) args$width <- NULL
if (length(args$height) == 0) args$height <- NULL
args <- rlang::list2(filename=filename, width=width, height=height, res=res, ...)
# Set a smarter default for the device's bg argument (based on thematic's global state).
# Note that, technically, this is really only needed for CairoPNG, since the other
@@ -70,10 +64,6 @@ startPNG <- function(filename, width, height, res, ...) {
#' * Otherwise, use [grDevices::png()]. In this case, Linux and Windows
#' may not antialias some point shapes, resulting in poor quality output.
#'
#' @details
#' A `NULL` value provided to `width` or `height` is ignored (i.e., the
#' default `width` or `height` of the graphics device is used).
#'
#' @param func A function that generates a plot.
#' @param filename The name of the output file. Defaults to a temp file with
#' extension `.png`.

View File

@@ -43,10 +43,7 @@ removeModal <- function(session = getDefaultReactiveDomain()) {
#' @param title An optional title for the dialog.
#' @param footer UI for footer. Use `NULL` for no footer.
#' @param size One of `"s"` for small, `"m"` (the default) for medium,
#' `"l"` for large, or `"xl"` for extra large. Note that `"xl"` only
#' works with Bootstrap 4 and above (to opt-in to Bootstrap 4+,
#' pass [bslib::bs_theme()] to the `theme` argument of a page container
#' like [fluidPage()]).
#' or `"l"` for large.
#' @param easyClose If `TRUE`, the modal dialog can be dismissed by
#' clicking outside the dialog box, or be pressing the Escape key. If
#' `FALSE` (the default), the modal dialog can't be dismissed in those

View File

@@ -194,8 +194,8 @@ renderPlot <- function(expr, width = 'auto', height = 'auto', res = 72, ...,
}
resizeSavedPlot <- function(name, session, result, width, height, alt, pixelratio, res, ...) {
if (isTRUE(result$img$width == width && result$img$height == height &&
result$pixelratio == pixelratio && result$res == res)) {
if (result$img$width == width && result$img$height == height &&
result$pixelratio == pixelratio && result$res == res) {
return(result)
}

View File

@@ -29,7 +29,7 @@ registerClient <- function(client) {
#' Define Server Functionality
#'
#' @description `r lifecycle::badge("superseded")`
#' @description \lifecycle{superseded}
#'
#' @description Defines the server-side logic of the Shiny application. This generally
#' involves creating functions that map user inputs to various kinds of output.

View File

@@ -1876,26 +1876,6 @@ ShinySession <- R6Class(
# Provides a mechanism for handling direct HTTP requests that are posted
# to the session (rather than going through the websocket)
handleRequest = function(req) {
if (!is.null(self$user)) {
if (is.null(req$HTTP_SHINY_SERVER_CREDENTIALS)) {
# Session owner is logged in, but this requester is not
return(NULL)
}
requestUser <- NULL
try(
{
creds <- safeFromJSON(req$HTTP_SHINY_SERVER_CREDENTIALS)
requestUser <- creds$user
},
silent = TRUE
)
if (!identical(self$user, requestUser)) {
# This requester is not the same user as session owner
return(NULL)
}
}
# TODO: Turn off caching for the response
subpath <- req$PATH_INFO

View File

@@ -29,7 +29,6 @@ withMathJax <- function(...) {
}
renderPage <- function(ui, showcase=0, testMode=FALSE) {
lang <- getLang(ui)
# If the ui is a NOT complete document (created by htmlTemplate()), then do some
# preprocessing and make sure it's a complete document.
@@ -38,13 +37,17 @@ renderPage <- function(ui, showcase=0, testMode=FALSE) {
ui <- showcaseUI(ui)
# Wrap ui in body tag if it doesn't already have a single top-level body tag.
if (!(inherits(ui, "shiny.tag") && ui$name == "body"))
ui <- tags$body(ui)
ui <- if (inherits(ui, "shiny.tag") && ui$name == "body") {
tagAppendAttributes(ui, !!!getBodyAttrs(ui))
} else {
tags$body(ui, !!!getBodyAttrs(ui))
}
# Put the body into the default template
ui <- htmlTemplate(
system_file("template", "default.html", package = "shiny"),
lang = lang,
html_open = HTML(sub("</html>$", "", tags$html(!!!getHtmlAttrs(ui)))),
body = ui,
# this template is a complete HTML document
document_ = TRUE
@@ -148,7 +151,7 @@ shinyDependencyCSS <- function(theme) {
#' Create a Shiny UI handler
#'
#' @description `r lifecycle::badge("superseded")`
#' @description \lifecycle{superseded}
#'
#' @description Historically this function was used in ui.R files to register a user
#' interface with Shiny. It is no longer required as of Shiny 0.10; simply

View File

@@ -1,2 +0,0 @@
# Load application support files into testing environment
shinytest2::load_app_env()

View File

@@ -1,5 +1,5 @@
<!DOCTYPE html>
<html{{ if (isTRUE(nzchar(lang))) paste0(" lang=\"", lang, "\"") }}>
{{html_open}}
<head>
{{ headContent() }}
</head>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -42,7 +42,7 @@ Selectize.define("selectize-plugin-a11y", function (options) {
// random IDs are assigned when .selectize() is called, but we're
// doing it here to limit the scope of changes.
var kids = self.$dropdown_content[0].children;
for (var i = 0; i < kids.length; i++) {
for (i = 0; i < kids.length; i++) {
var attrs = kids[i].attributes;
if (!attrs.role) {
kids[i].setAttribute("role", "option");

View File

@@ -1 +1 @@
Selectize.define("selectize-plugin-a11y",function(c){var t=this,l=13;typeof t.accessibility=="undefined"&&(t.accessibility={}),t.accessibility.helpers={randomId:function(e){for(var r="",s=e||10,i="abcdefghijklmnopqrstuvwxyz0123456789",n=i.length,a=0;a<s;a++)r+=i[Math.floor(n*Math.random())];return r}},t.accessibility.liveRegion={$region:"",speak:function(e){var r=$("<div></div>");r.text(e),this.$region.html(r)},domListener:function(){var e=new MutationObserver(function(r){r.forEach(function(s){var i=$(s.target);if(i.hasClass("items"))if(i.hasClass("dropdown-active")){t.$control_input.attr("aria-expanded","true");for(var n=t.$dropdown_content[0].children,a=0;a<n.length;a++){var o=n[a].attributes;o.role||n[a].setAttribute("role","option"),o.id||n[a].setAttribute("id",t.accessibility.helpers.randomId())}}else t.$control_input.attr("aria-expanded","false"),t.$control_input.removeAttr("aria-activedescendant");else i.hasClass("active")&&i.attr("data-value")&&(t.$control_input.attr("aria-activedescendant",i.attr("id")),t.accessibility.liveRegion.speak(i.text(),500))})});e.observe(t.$dropdown[0],{attributes:!0,attributeFilter:["class"],subtree:!0,attributeOldValue:!0}),e.observe(t.$control[0],{attributes:!0,attributeFilter:["class"]}),e.observe(t.$control_input[0],{attributes:!0,attributeFilter:["value"]})},setAttributes:function(){this.$region.attr({"aria-live":"assertive",role:"log","aria-relevant":"additions","aria-atomic":"true"})},setStyles:function(){this.$region.css({position:"absolute",width:"1px",height:"1px","margin-top":"-1px",clip:"rect(1px, 1px, 1px, 1px)",overflow:"hidden"})},init:function(){this.$region=$("<div>"),this.setAttributes(),this.setStyles(),$("body").append(this.$region),this.domListener()}},this.setup=function(){var e=t.setup;return function(){e.apply(this,arguments);var r=t.accessibility.helpers.randomId(),s=t.accessibility.helpers.randomId();t.$control.on("keydown",function(i){i.keyCode===l&&(t.settings.openOnFocus?(t.settings.openOnFocus=!1,t.focus(),setTimeout(function(){t.settings.openOnFocus=!0},0)):t.focus())}),t.$control_input.attr({role:"combobox","aria-expanded":"false",haspopup:"listbox","aria-owns":s,"aria-label":t.$wrapper.closest("[data-accessibility-selectize-label]").attr("data-accessibility-selectize-label")}),t.$dropdown_content.attr({role:"listbox",id:s}),t.accessibility.liveRegion.init()}}(),this.destroy=function(){var e=t.destroy;return function(){return t.accessibility.liveRegion.$region.remove(),e.apply(this,arguments)}}()});
"use strict";Selectize.define("selectize-plugin-a11y",function(c){var t=this,l=13;typeof t.accessibility=="undefined"&&(t.accessibility={}),t.accessibility.helpers={randomId:function(e){for(var a="",s=e||10,r="abcdefghijklmnopqrstuvwxyz0123456789",n=r.length,o=0;o<s;o++)a+=r[Math.floor(n*Math.random())];return a}},t.accessibility.liveRegion={$region:"",speak:function(e){var a=$("<div></div>");a.text(e),this.$region.html(a)},domListener:function(){var e=new MutationObserver(function(a){a.forEach(function(s){var r=$(s.target);if(r.hasClass("items"))if(r.hasClass("dropdown-active")){t.$control_input.attr("aria-expanded","true");var n=t.$dropdown_content[0].children;for(i=0;i<n.length;i++){var o=n[i].attributes;o.role||n[i].setAttribute("role","option"),o.id||n[i].setAttribute("id",t.accessibility.helpers.randomId())}}else t.$control_input.attr("aria-expanded","false"),t.$control_input.removeAttr("aria-activedescendant");else r.hasClass("active")&&r.attr("data-value")&&(t.$control_input.attr("aria-activedescendant",r.attr("id")),t.accessibility.liveRegion.speak(r.text(),500))})});e.observe(t.$dropdown[0],{attributes:!0,attributeFilter:["class"],subtree:!0,attributeOldValue:!0}),e.observe(t.$control[0],{attributes:!0,attributeFilter:["class"]}),e.observe(t.$control_input[0],{attributes:!0,attributeFilter:["value"]})},setAttributes:function(){this.$region.attr({"aria-live":"assertive",role:"log","aria-relevant":"additions","aria-atomic":"true"})},setStyles:function(){this.$region.css({position:"absolute",width:"1px",height:"1px","margin-top":"-1px",clip:"rect(1px, 1px, 1px, 1px)",overflow:"hidden"})},init:function(){this.$region=$("<div>"),this.setAttributes(),this.setStyles(),$("body").append(this.$region),this.domListener()}},this.setup=function(){var e=t.setup;return function(){e.apply(this,arguments);var a=t.accessibility.helpers.randomId(),s=t.accessibility.helpers.randomId();t.$control.on("keydown",function(r){r.keyCode===l&&(t.settings.openOnFocus?(t.settings.openOnFocus=!1,t.focus(),setTimeout(function(){t.settings.openOnFocus=!0},0)):t.focus())}),t.$control_input.attr({role:"combobox","aria-expanded":"false",haspopup:"listbox","aria-owns":s,"aria-label":t.$wrapper.closest("[data-accessibility-selectize-label]").attr("data-accessibility-selectize-label")}),t.$dropdown_content.attr({role:"listbox",id:s}),t.accessibility.liveRegion.init()}}(),this.destroy=function(){var e=t.destroy;return function(){return t.accessibility.liveRegion.$region.remove(),e.apply(this,arguments)}}()});

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.2.9001 | (c) 2012-2022 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

View File

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

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

@@ -86,6 +86,11 @@ pre.shiny-text-output {
}
}
.shiny-image-output, .shiny-plot-output {
width: 100%;
height: 400px;
}
#shiny-disconnected-overlay {
position: fixed;
top: 0;

View File

@@ -71,7 +71,7 @@ registered \code{devmode_default} value will be used.}
\code{TRUE} and the specified option is not set in \code{\link[=options]{options()}}.}
}
\description{
\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#experimental}{\figure{lifecycle-experimental.svg}{options: alt='[Experimental]'}}}{\strong{[Experimental]}}
\lifecycle{experimental}
Developer Mode enables a number of \code{\link[=options]{options()}} to make a developer's life
easier, like enabling non-minified JS and printing messages about

View File

@@ -22,19 +22,15 @@ uiOutput(
)
}
\arguments{
\item{outputId}{output variable to read the value from}
\item{outputId}{output variable to read the value from.}
\item{inline}{use an inline (\code{span()}) or block container (\code{div()})
for the output}
\item{container}{a function to generate an HTML element to contain the text}
\item{fill}{If \code{TRUE}, the result of \code{container} is treated as \emph{both} a fill
item and container (see \code{\link[htmltools:bindFillRole]{htmltools::bindFillRole()}}), which means both the
\code{container} as well as its immediate children (i.e., the result of
\code{renderUI()}) are allowed to grow/shrink to fit a fill container with an
opinionated height. Set \code{fill = "item"} or \code{fill = "container"} to treat
\code{container} as just a fill item or a fill container.}
\item{fill}{Whether the output should be allowed to grow/shrink to the size
of the \code{container}. Ignored when \code{inline = TRUE}.}
\item{...}{Other arguments to pass to the container tag function. This is
useful for providing additional classes for the tag.}

View File

@@ -24,10 +24,7 @@ modalButton(label, icon = NULL)
\item{footer}{UI for footer. Use \code{NULL} for no footer.}
\item{size}{One of \code{"s"} for small, \code{"m"} (the default) for medium,
\code{"l"} for large, or \code{"xl"} for extra large. Note that \code{"xl"} only
works with Bootstrap 4 and above (to opt-in to Bootstrap 4+,
pass \code{\link[bslib:bs_theme]{bslib::bs_theme()}} to the \code{theme} argument of a page container
like \code{\link[=fluidPage]{fluidPage()}}).}
or \code{"l"} for large.}
\item{easyClose}{If \code{TRUE}, the modal dialog can be dismissed by
clicking outside the dialog box, or be pressing the Escape key. If

View File

@@ -7,26 +7,24 @@
\usage{
imageOutput(
outputId,
width = "100\%",
height = "400px",
width = NULL,
height = NULL,
click = NULL,
dblclick = NULL,
hover = NULL,
brush = NULL,
inline = FALSE,
fill = FALSE
inline = FALSE
)
plotOutput(
outputId,
width = "100\%",
height = "400px",
width = NULL,
height = NULL,
click = NULL,
dblclick = NULL,
hover = NULL,
brush = NULL,
inline = FALSE,
fill = !inline
inline = FALSE
)
}
\arguments{
@@ -78,12 +76,6 @@ same \code{id} to disappear.}
\item{inline}{use an inline (\code{span()}) or block container (\code{div()})
for the output}
\item{fill}{Whether or not the returned tag should be treated as a fill item,
meaning that its \code{height} is allowed to grow/shrink to fit a fill container
with an opinionated height (see \code{\link[htmltools:bindFillRole]{htmltools::bindFillRole()}}) with an
opinionated height. Examples of fill containers include \code{bslib::card()} and
\code{bslib::card_body_fill()}.}
}
\value{
A plot or image output element that can be included in a panel.

View File

@@ -46,7 +46,3 @@ is not set to \code{FALSE}), then use \code{\link[Cairo:Cairo]{Cairo::CairoPNG()
may not antialias some point shapes, resulting in poor quality output.
}
}
\details{
A \code{NULL} value provided to \code{width} or \code{height} is ignored (i.e., the
default \code{width} or \code{height} of the graphics device is used).
}

View File

@@ -45,7 +45,6 @@ following files and directories is created:
`- tests
|- testthat.R
`- testthat
|- setup-shinytest2.R
|- test-examplemodule.R
|- test-server.R
|- test-shinytest2.R
@@ -68,7 +67,6 @@ choose to use or remove any of them. They can be executed by the
\verb{tests/testthat/} directory using the
\href{https://rstudio.github.io/shinytest2/reference/test_app.html}{shinytest2}
package.
\item \code{tests/testthat/setup-shinytest2.R} is setup file to source your \code{./R} folder into the testing environment.
\item \code{tests/testthat/test-examplemodule.R} is a test for an application's module server function.
\item \code{tests/testthat/test-server.R} is a test for the application's server code
\item \code{tests/testthat/test-shinytest2.R} is a test that uses the

View File

@@ -11,7 +11,7 @@ shinyServer(func)
for more information.}
}
\description{
\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#superseded}{\figure{lifecycle-superseded.svg}{options: alt='[Superseded]'}}}{\strong{[Superseded]}}
\lifecycle{superseded}
Defines the server-side logic of the Shiny application. This generally
involves creating functions that map user inputs to various kinds of output.

View File

@@ -13,7 +13,7 @@ shinyUI(ui)
The user interface definition, without modifications or side effects.
}
\description{
\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#superseded}{\figure{lifecycle-superseded.svg}{options: alt='[Superseded]'}}}{\strong{[Superseded]}}
\lifecycle{superseded}
Historically this function was used in ui.R files to register a user
interface with Shiny. It is no longer required as of Shiny 0.10; simply

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.2-alpha.9001",
"license": "GPL-3.0-only",
"main": "",
"browser": "",

View File

@@ -10,12 +10,6 @@ import globalsPlugin from "esbuild-plugin-globals";
const opts = {
bundle: false,
sourcemap: false,
// Oddly, esbuild seems to use the top-level tsconfig.json file even when just
// minifying JS to JS. Because that tsconfig file has "strict":true, esbuild
// ends up adding "use strict" to the top of each minified JS file, which can
// alter behavior. To avoid this, we have a separate tsconfig file with
// "alwaysStrict":false.
tsconfig: "srcts/build/external_libs_tsconfig.json",
};
readdir(outDir + "datepicker/js/locales/").then(async (localeFiles) => {

View File

@@ -1,6 +0,0 @@
{
"compilerOptions": {
"target": "ES5",
"alwaysStrict": false
}
}

View File

@@ -71,7 +71,7 @@ class SelectInputBinding extends InputBinding {
} else {
const selectize = this._selectize(el);
selectize?.setValue(value);
selectize.setValue(value);
}
}
getState(el: SelectHTMLElement): {
@@ -107,10 +107,10 @@ class SelectInputBinding extends InputBinding {
// This will replace all the options
if (hasDefinedProperty(data, "options")) {
const selectize = this._selectize(el);
// Must destroy selectize before appending new options, otherwise
// selectize will restore the original select
selectize?.destroy();
if (selectize) selectize.destroy();
// Clear existing options and add each new one
$el.empty().append(data.options);
this._selectize(el);
@@ -176,7 +176,9 @@ class SelectInputBinding extends InputBinding {
callback(res);
if (!loaded) {
if (hasDefinedProperty(data, "value")) {
selectize.setValue(data.value as any);
if (typeof data.value === "string") {
selectize.setValue(data.value);
}
} else if (settings.maxItems === 1) {
// first item selected by default only for single-select
selectize.setValue(res[0].value);
@@ -219,19 +221,15 @@ class SelectInputBinding extends InputBinding {
initialize(el: SelectHTMLElement): void {
this._selectize(el);
}
protected _selectize(
el: SelectHTMLElement,
update = false
): SelectizeInfo | undefined {
// Apps like 008-html do not have the selectize js library
// Safe-guard against missing the selectize js library
if (!$.fn.selectize) return undefined;
protected _selectize(el: SelectHTMLElement, update = false): SelectizeInfo {
if (!$.fn.selectize) throw "selectize jquery is not defined";
const $el = $(el);
const config = $el
.parent()
.find('script[data-for="' + $escape(el.id) + '"]');
if (config.length === 0) return undefined;
if (config.length === 0)
throw "No config found for selectize with id:" + $escape(el.id);
let options: SelectizeOptions & {
labelField: "label";

View File

@@ -2,7 +2,7 @@ import $ from "jquery";
import { OutputBinding } from "./outputBinding";
import { shinyUnbindAll } from "../../shiny/initedMethods";
import { renderContentAsync } from "../../shiny/render";
import { renderContent } from "../../shiny/render";
import type { ErrorsMessageValue } from "../../shiny/shinyapp";
class HtmlOutputBinding extends OutputBinding {
@@ -13,11 +13,11 @@ class HtmlOutputBinding extends OutputBinding {
shinyUnbindAll(el);
this.renderError(el, err);
}
override async renderValue(
renderValue(
el: HTMLElement,
data: Parameters<typeof renderContentAsync>[1]
): Promise<void> {
await renderContentAsync(el, data);
data: Parameters<typeof renderContent>[1]
): void {
renderContent(el, data);
}
}

View File

@@ -11,7 +11,7 @@ class OutputBinding {
throw "Not implemented";
scope;
}
renderValue(el: HTMLElement, data: unknown): Promise<void> | void {
renderValue(el: HTMLElement, data: unknown): void {
throw "Not implemented";
el;
data;
@@ -21,9 +21,9 @@ class OutputBinding {
return el.getAttribute("data-input-id") || el.id;
}
async onValueChange(el: HTMLElement, data: unknown): Promise<void> {
onValueChange(el: HTMLElement, data: unknown): void {
this.clearError(el);
await this.renderValue(el, data);
this.renderValue(el, data);
}
onValueError(el: HTMLElement, err: ErrorsMessageValue): void {
this.renderError(el, err);

View File

@@ -31,8 +31,8 @@ class OutputBindingAdapter {
getId(): string {
return this.binding.getId(this.el);
}
async onValueChange(data: unknown): Promise<void> {
await this.binding.onValueChange(this.el, data);
onValueChange(data: unknown): void {
this.binding.onValueChange(this.el, data);
}
onValueError(err: ErrorsMessageValue): void {
this.binding.onValueError(this.el, err);

View File

@@ -220,9 +220,11 @@ function createBrush(
// div being resized.
function onResize() {
const boundsDataVal = boundsData();
// Check to see if we have valid boundsData
if (Object.values(boundsDataVal).some(isnan)) return;
for (const val in Object.values(boundsDataVal)) {
if (isnan(val)) return;
}
boundsData(boundsDataVal);
updateDiv();

View File

@@ -6,9 +6,9 @@ import type { ShinyApp } from "../shiny/shinyapp";
class InputBatchSender implements InputPolicy {
target!: InputPolicy; // We need this field to satisfy the InputPolicy interface
shinyapp: ShinyApp;
timerId: ReturnType<typeof setTimeout> | null = null;
pendingData: { [key: string]: unknown } = {};
reentrant = false;
sendIsEnqueued = false;
lastChanceCallback: Array<() => void> = [];
constructor(shinyapp: ShinyApp) {
@@ -21,11 +21,8 @@ class InputBatchSender implements InputPolicy {
if (!this.reentrant) {
if (opts.priority === "event") {
this._sendNow();
} else if (!this.sendIsEnqueued) {
this.shinyapp.actionQueue.enqueue(() => {
this.sendIsEnqueued = false;
this._sendNow();
});
} else if (!this.timerId) {
this.timerId = setTimeout(this._sendNow.bind(this), 0);
}
}
}
@@ -37,6 +34,7 @@ class InputBatchSender implements InputPolicy {
this.reentrant = true;
try {
this.timerId = null;
this.lastChanceCallback.forEach((callback) => callback());
const currentData = this.pendingData;

View File

@@ -6,14 +6,7 @@ import { $escape, compareVersion } from "../utils";
import { showNotification, removeNotification } from "./notifications";
import { showModal, removeModal } from "./modal";
import { showReconnectDialog, hideReconnectDialog } from "./reconnectDialog";
import {
renderContentAsync,
renderContent,
renderDependenciesAsync,
renderDependencies,
renderHtmlAsync,
renderHtml,
} from "./render";
import { renderContent, renderDependencies, renderHtml } from "./render";
import { initShiny } from "./init";
import type {
shinyBindAll,
@@ -47,11 +40,8 @@ interface Shiny {
createSocket?: () => WebSocket;
showReconnectDialog: typeof showReconnectDialog;
hideReconnectDialog: typeof hideReconnectDialog;
renderDependenciesAsync: typeof renderDependenciesAsync;
renderDependencies: typeof renderDependencies;
renderContentAsync: typeof renderContentAsync;
renderContent: typeof renderContent;
renderHtmlAsync: typeof renderHtmlAsync;
renderHtml: typeof renderHtml;
user: string;
progressHandlers?: ShinyApp["progressHandlers"];
@@ -100,11 +90,8 @@ function setShiny(windowShiny_: Shiny): void {
windowShiny.addCustomMessageHandler = addCustomMessageHandler;
windowShiny.showReconnectDialog = showReconnectDialog;
windowShiny.hideReconnectDialog = hideReconnectDialog;
windowShiny.renderDependenciesAsync = renderDependenciesAsync;
windowShiny.renderDependencies = renderDependencies;
windowShiny.renderContentAsync = renderContentAsync;
windowShiny.renderContent = renderContent;
windowShiny.renderHtmlAsync = renderHtmlAsync;
windowShiny.renderHtml = renderHtml;
$(function () {

View File

@@ -154,12 +154,12 @@ function initShiny(windowShiny: Shiny): void {
// in case it is auto-sizing
$(".shiny-image-output, .shiny-plot-output, .shiny-report-size").each(
function () {
const id = getIdFromEl(this),
rect = this.getBoundingClientRect();
const id = getIdFromEl(this);
if (rect.width !== 0 || rect.height !== 0) {
initialValues[".clientdata_output_" + id + "_width"] = rect.width;
initialValues[".clientdata_output_" + id + "_height"] = rect.height;
if (this.offsetWidth !== 0 || this.offsetHeight !== 0) {
initialValues[".clientdata_output_" + id + "_width"] = this.offsetWidth;
initialValues[".clientdata_output_" + id + "_height"] =
this.offsetHeight;
}
}
);
@@ -275,12 +275,17 @@ function initShiny(windowShiny: Shiny): void {
function doSendImageSize() {
$(".shiny-image-output, .shiny-plot-output, .shiny-report-size").each(
function () {
const id = getIdFromEl(this),
rect = this.getBoundingClientRect();
const id = getIdFromEl(this);
if (rect.width !== 0 || rect.height !== 0) {
inputs.setInput(".clientdata_output_" + id + "_width", rect.width);
inputs.setInput(".clientdata_output_" + id + "_height", rect.height);
if (this.offsetWidth !== 0 || this.offsetHeight !== 0) {
inputs.setInput(
".clientdata_output_" + id + "_width",
this.offsetWidth
);
inputs.setInput(
".clientdata_output_" + id + "_height",
this.offsetHeight
);
}
}
);

View File

@@ -1,28 +1,12 @@
import $ from "jquery";
import { shinyUnbindAll } from "./initedMethods";
import type { HtmlDep } from "./render";
import { renderContentAsync, renderDependenciesAsync } from "./render";
import { renderContent } from "./render";
// Show a modal dialog. This is meant to handle two types of cases: one is
// that the content is a Bootstrap modal dialog, and the other is that the
// content is non-Bootstrap. Bootstrap modals require some special handling,
// which is coded in here.
async function show({
html = "",
deps = [],
}: {
html?: string;
deps?: HtmlDep[];
} = {}): Promise<void> {
// Normally we'd first create the modal's DOM elements, then call
// `renderContentAsync(x, {html: html, deps: deps})`, but that has a potential
// problem with async rendering. If we did that, then an empty modal (from
// this function) could show up and then sit there empty while the
// dependencies load (asynchronously), and only after all that get filled with
// content for the modal. So instead we'll render the deps here, then render
// the modal, then render the content in the modal.
await renderDependenciesAsync(deps);
function show({ html = "", deps = [] } = {}): void {
// If there was an existing Bootstrap modal, then there will be a modal-
// backdrop div that was added outside of the modal wrapper, and it must be
// removed; otherwise there can be multiple of these divs.
@@ -60,7 +44,7 @@ async function show({
});
// Set/replace contents of wrapper with html.
await renderContentAsync($modal, { html: html });
renderContent($modal, { html: html, deps: deps });
}
function remove(): void {

View File

@@ -3,13 +3,12 @@ import $ from "jquery";
import { $escape, randomId } from "../utils";
import { shinyUnbindAll } from "./initedMethods";
import type { HtmlDep } from "./render";
import { renderDependenciesAsync } from "./render";
import { renderContentAsync } from "./render";
import { renderContent } from "./render";
// Milliseconds to fade in or out
const fadeDuration = 250;
async function show({
function show({
html = "",
action = "",
deps = [],
@@ -25,18 +24,9 @@ async function show({
id?: string | null;
closeButton?: boolean;
type?: string | null;
} = {}): Promise<ReturnType<typeof randomId>> {
} = {}): ReturnType<typeof randomId> {
if (!id) id = randomId();
// Normally we'd first create the notification's DOM elements, then call
// `renderContentAsync(x, {html: html, deps: deps})`, but that has a potential
// problem with async rendering. If we did that, then an empty notification
// (from this function) could show up and then sit there empty while the
// dependencies load (asynchronously), and only after all that get filled with
// content for the notification. So instead we'll render the deps here, then
// render the notification, then render the content in the notification.
await renderDependenciesAsync(deps);
// Create panel if necessary
createPanel();
@@ -52,8 +42,7 @@ async function show({
`<div class="shiny-notification-content-action">${action}</div>`;
const $content = $notification.find(".shiny-notification-content");
// Set/replace contents of wrapper with html.
await renderContentAsync($content, { html: newHtml });
renderContent($content, { html: newHtml, deps: deps });
// Remove any existing classes of the form 'shiny-notification-xxxx'.
// The xxxx would be strings like 'warning'.

View File

@@ -12,73 +12,15 @@ import { sendImageSizeFns } from "./sendImageSize";
import { renderHtml as singletonsRenderHtml } from "./singletons";
import type { WherePosition } from "./singletons";
// There are synchronous and asynchronous versions of the exported functions
// renderContent(), renderHtml(), and renderDependencies(). This is because they
// the original versions of these functions were synchronous, but we added
// support for asynchronous rendering, to avoid the deprecated XMLHttpRequest
// function (https://github.com/rstudio/shiny/pull/3666).
//
// At the bottom, there is the appendScriptTags(), which calls $.append(), which
// in turn calls (synchronous) XMLHttpRequest(); and its counterpart
// appendScriptTagsAsync(), which uses a different (asynchronous) method. The
// sync and async versions of this function necessitate the sync and async
// versions of the other functions.
//
// The async versions of these functions are used internally and should be used
// for new external code when possible, but for backward compatibility for
// external code that calls these functions, we'll keep the synchronous versions
// around as well.
// =============================================================================
// renderContent
// =============================================================================
// Render HTML in a DOM element, add dependencies, and bind Shiny
// inputs/outputs. `content` can be null, a string, or an object with
// properties 'html' and 'deps'.
async function renderContentAsync(
el: BindScope,
content: string | { html: string; deps?: HtmlDep[] } | null,
where: WherePosition = "replace"
): Promise<void> {
if (where === "replace") {
shinyUnbindAll(el);
}
let html = "";
let dependencies: HtmlDep[] = [];
if (content === null) {
html = "";
} else if (typeof content === "string") {
html = content;
} else if (typeof content === "object") {
html = content.html;
dependencies = content.deps || [];
}
await renderHtmlAsync(html, el, dependencies, where);
let scope: BindScope = el;
if (where === "replace") {
shinyInitializeInputs(el);
shinyBindAll(el);
} else {
const $parent = $(el).parent();
if ($parent.length > 0) {
scope = $parent;
if (where === "beforeBegin" || where === "afterEnd") {
const $grandparent = $parent.parent();
if ($grandparent.length > 0) scope = $grandparent;
}
}
shinyInitializeInputs(scope);
shinyBindAll(scope);
function renderDependencies(dependencies: HtmlDep[] | null): void {
if (dependencies) {
dependencies.forEach(renderDependency);
}
}
// Render HTML in a DOM element, add dependencies, and bind Shiny
// inputs/outputs. `content` can be null, a string, or an object with
// properties 'html' and 'deps'.
function renderContent(
el: BindScope,
content: string | { html: string; deps?: HtmlDep[] } | null,
@@ -123,20 +65,6 @@ function renderContent(
}
}
// =============================================================================
// renderHtml
// =============================================================================
// Render HTML in a DOM element, inserting singletons into head as needed
async function renderHtmlAsync(
html: string,
el: BindScope,
dependencies: HtmlDep[],
where: WherePosition = "replace"
): Promise<ReturnType<typeof singletonsRenderHtml>> {
await renderDependenciesAsync(dependencies);
return singletonsRenderHtml(html, el, where);
}
// Render HTML in a DOM element, inserting singletons into head as needed
function renderHtml(
html: string,
@@ -148,30 +76,6 @@ function renderHtml(
return singletonsRenderHtml(html, el, where);
}
// =============================================================================
// renderDependencies
// =============================================================================
async function renderDependenciesAsync(
dependencies: HtmlDep[] | null
): Promise<void> {
if (dependencies) {
for (const dep of dependencies) {
await renderDependencyAsync(dep);
}
}
}
function renderDependencies(dependencies: HtmlDep[] | null): void {
if (dependencies) {
for (const dep of dependencies) {
renderDependency(dep);
}
}
}
// =============================================================================
// HTML dependency types
// =============================================================================
type HtmlDepVersion = string;
type MetaItem = {
@@ -222,10 +126,6 @@ type HtmlDepNormalized = {
attachment: AttachmentItem[];
head?: string;
};
// =============================================================================
// renderDependency helper functions
// =============================================================================
const htmlDependencies: { [key: string]: HtmlDepVersion } = {};
function registerDependency(name: string, version: HtmlDepVersion): void {
@@ -247,6 +147,93 @@ function needsRestyle(dep: HtmlDepNormalized) {
return htmlDependencies[names[idx]] === dep.version;
}
// Client-side dependency resolution and rendering
function renderDependency(dep_: HtmlDep) {
const dep = normalizeHtmlDependency(dep_);
// Convert stylesheet objs to links early, because if `restyle` is true, we'll
// pass them through to `addStylesheetsAndRestyle` below.
const stylesheetLinks = dep.stylesheet.map((x) => {
// Add "rel" and "type" fields if not already present.
if (!hasDefinedProperty(x, "rel")) x.rel = "stylesheet";
if (!hasDefinedProperty(x, "type")) x.type = "text/css";
const link = document.createElement("link");
Object.entries(x).forEach(function ([attr, val]: [
string,
string | undefined
]) {
if (attr === "href") {
val = encodeURI(val as string);
}
// If val isn't truthy (e.g., null), consider it a boolean attribute
link.setAttribute(attr, val ? val : "");
});
return link;
});
// If a restyle is needed, do that stuff and return. Note that other items
// (like scripts) aren't added, because they would have been added in a
// previous run.
if (needsRestyle(dep)) {
addStylesheetsAndRestyle(stylesheetLinks);
return true;
}
if (hasDefinedProperty(htmlDependencies, dep.name)) return false;
registerDependency(dep.name, dep.version);
const $head = $("head").first();
// Add each type of element to the DOM.
dep.meta.forEach((x) => {
const meta = document.createElement("meta");
for (const [attr, val] of Object.entries(x)) {
meta.setAttribute(attr, val);
}
$head.append(meta);
});
if (stylesheetLinks.length !== 0) {
$head.append(stylesheetLinks);
}
dep.script.forEach((x) => {
const script = document.createElement("script");
Object.entries(x).forEach(function ([attr, val]) {
if (attr === "src") {
val = encodeURI(val);
}
// If val isn't truthy (e.g., null), consider it a boolean attribute
script.setAttribute(attr, val ? val : "");
});
$head.append(script);
});
dep.attachment.forEach((x) => {
const link = $("<link rel='attachment'>")
.attr("id", dep.name + "-" + x.key + "-attachment")
.attr("href", encodeURI(x.href));
$head.append(link);
});
if (dep.head) {
const $newHead = $("<head></head>");
$newHead.html(dep.head);
$head.append($newHead.children());
}
return true;
}
function addStylesheetsAndRestyle(links: HTMLLinkElement[]): void {
const $head = $("head").first();
@@ -367,206 +354,6 @@ function addStylesheetsAndRestyle(links: HTMLLinkElement[]): void {
});
}
function getStylesheetLinkTags(dep: HtmlDepNormalized): HTMLLinkElement[] {
// Convert stylesheet objs to links early, because if `restyle` is true, we'll
// pass them through to `addStylesheetsAndRestyle` below.
return dep.stylesheet.map((x) => {
// Add "rel" and "type" fields if not already present.
if (!hasDefinedProperty(x, "rel")) x.rel = "stylesheet";
if (!hasDefinedProperty(x, "type")) x.type = "text/css";
const link = document.createElement("link");
Object.entries(x).forEach(function ([attr, val]: [
string,
string | undefined
]) {
if (attr === "href") {
val = encodeURI(val as string);
}
// If val isn't truthy (e.g., null), consider it a boolean attribute
link.setAttribute(attr, val ? val : "");
});
return link;
});
}
function appendStylesheetLinkTags(
dep: HtmlDepNormalized,
$head: JQuery<HTMLElement>
): void {
const stylesheetLinks = getStylesheetLinkTags(dep);
if (stylesheetLinks.length !== 0) {
$head.append(stylesheetLinks);
}
}
function appendScriptTags(dep: HtmlDepNormalized, $head: JQuery<HTMLElement>) {
dep.script.forEach((x) => {
const script = document.createElement("script");
Object.entries(x).forEach(function ([attr, val]) {
if (attr === "src") {
val = encodeURI(val);
}
// If val isn't truthy (e.g., null), consider it a boolean attribute
script.setAttribute(attr, val ? val : "");
});
$head.append(script);
});
}
async function appendScriptTagsAsync(dep: HtmlDepNormalized): Promise<void> {
const scriptPromises: Array<Promise<any>> = [];
dep.script.forEach((x) => {
const script = document.createElement("script");
if (!hasDefinedProperty(x, "async")) {
// Set async to false by default, so that if there are multiple script
// tags, they are guaranteed to run in order. For dynamically added
// <script> tags, browsers set async to true by default, which differs
// from static <script> tags in the html, which default to false.
//
// Refs:
// https://stackoverflow.com/a/8996894/412655
// https://jason-ge.medium.com/dynamically-load-javascript-files-in-order-5318ac6bcc61
//
// Note that one odd thing about these dynamically-created <script> tags
// is that even though the JS object's `x.script` property is true, it
// does NOT show up as a property on the <script> element.
script.async = false;
}
Object.entries(x).forEach(function ([attr, val]) {
if (attr === "src") {
val = encodeURI(val);
}
// If val isn't truthy (e.g., null), consider it a boolean attribute
script.setAttribute(attr, val ? val : "");
});
const p = new Promise((resolve, reject) => {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
script.onload = (e: Event) => {
resolve(null);
};
script.onerror = (e: Event | string) => {
reject(e);
};
});
scriptPromises.push(p);
document.head.append(script);
});
await Promise.allSettled(scriptPromises);
}
function appendMetaTags(
dep: HtmlDepNormalized,
$head: JQuery<HTMLElement>
): void {
dep.meta.forEach((x) => {
const meta = document.createElement("meta");
for (const [attr, val] of Object.entries(x)) {
meta.setAttribute(attr, val);
}
$head.append(meta);
});
}
function appendAttachmentLinkTags(
dep: HtmlDepNormalized,
$head: JQuery<HTMLElement>
): void {
dep.attachment.forEach((x) => {
const link = $("<link rel='attachment'>")
.attr("id", dep.name + "-" + x.key + "-attachment")
.attr("href", encodeURI(x.href));
$head.append(link);
});
}
function appendExtraHeadContent(
dep: HtmlDepNormalized,
$head: JQuery<HTMLElement>
): void {
if (dep.head) {
const $newHead = $("<head></head>");
$newHead.html(dep.head);
$head.append($newHead.children());
}
}
// =============================================================================
// renderDependency
// =============================================================================
// Client-side dependency resolution and rendering
async function renderDependencyAsync(dep_: HtmlDep): Promise<boolean> {
const dep = normalizeHtmlDependency(dep_);
// If a restyle is needed, do that stuff and return. Note that other items
// (like scripts) aren't added, because they would have been added in a
// previous run.
if (needsRestyle(dep)) {
addStylesheetsAndRestyle(getStylesheetLinkTags(dep));
return true;
}
if (hasDefinedProperty(htmlDependencies, dep.name)) return false;
registerDependency(dep.name, dep.version);
const $head = $("head").first();
// Add each type of element to the DOM.
appendMetaTags(dep, $head);
appendStylesheetLinkTags(dep, $head);
await appendScriptTagsAsync(dep);
appendAttachmentLinkTags(dep, $head);
appendExtraHeadContent(dep, $head);
return true;
}
// Old-school synchronous version of renderDependencyAsync. This function is
// here to preserve compatibility with outside packages that use it. The
// implementation is the same except that it calls appendScriptTags() instead of
// appendScriptTagsAsync().
function renderDependency(dep_: HtmlDep): boolean {
const dep = normalizeHtmlDependency(dep_);
// If a restyle is needed, do that stuff and return. Note that other items
// (like scripts) aren't added, because they would have been added in a
// previous run.
if (needsRestyle(dep)) {
addStylesheetsAndRestyle(getStylesheetLinkTags(dep));
return true;
}
if (hasDefinedProperty(htmlDependencies, dep.name)) return false;
registerDependency(dep.name, dep.version);
const $head = $("head").first();
// Add each type of element to the DOM.
appendMetaTags(dep, $head);
appendStylesheetLinkTags(dep, $head);
appendScriptTags(dep, $head);
appendAttachmentLinkTags(dep, $head);
appendExtraHeadContent(dep, $head);
return true;
}
// Convert legacy HtmlDependency to new HTMLDependency format. This is
// idempotent; new HTMLDependency objects are returned unchanged.
function normalizeHtmlDependency(dep: HtmlDep): HtmlDepNormalized {
@@ -683,13 +470,5 @@ function normalizeHtmlDependency(dep: HtmlDep): HtmlDepNormalized {
return result;
}
export {
renderContentAsync,
renderContent,
renderHtmlAsync,
renderHtml,
renderDependenciesAsync,
renderDependencies,
registerDependency,
};
export { renderDependencies, renderContent, renderHtml, registerDependency };
export type { HtmlDep };

View File

@@ -10,7 +10,7 @@ import {
import { isQt } from "../utils/browser";
import { showNotification, removeNotification } from "./notifications";
import { showModal, removeModal } from "./modal";
import { renderContentAsync, renderHtmlAsync } from "./render";
import { renderContent, renderHtml } from "./render";
import type { HtmlDep } from "./render";
import { hideReconnectDialog, showReconnectDialog } from "./reconnectDialog";
import { resetBrush } from "../imageutils/resetBrush";
@@ -25,10 +25,9 @@ import type { InputBinding } from "../bindings";
import { indirectEval } from "../utils/eval";
import type { WherePosition } from "./singletons";
import type { UploadInitValue, UploadEndValue } from "../file/fileProcessor";
import { AsyncQueue } from "../utils/asyncQueue";
type ResponseValue = UploadEndValue | UploadInitValue;
type Handler = (message: any) => Promise<void> | void;
type Handler = (message: any) => void;
type ShinyWebSocket = WebSocket & {
allowReconnect?: boolean;
@@ -109,13 +108,6 @@ function addCustomMessageHandler(type: string, handler: Handler): void {
class ShinyApp {
$socket: ShinyWebSocket | null = null;
// An asynchronous queue of functions. This is sort of like an event loop for
// Shiny, to allow scheduling async callbacks so that they can run in order
// 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>();
config: {
workerId: string;
sessionId: string;
@@ -236,11 +228,9 @@ class ShinyApp {
socket.send(msg as string);
}
this.startActionQueueLoop();
};
socket.onmessage = (e) => {
this.actionQueue.enqueue(async () => await this.dispatchMessage(e.data));
this.dispatchMessage(e.data);
};
// Called when a successfully-opened websocket is closed, or when an
// attempt to open a connection fails.
@@ -263,19 +253,6 @@ class ShinyApp {
return socket;
}
async startActionQueueLoop(): Promise<void> {
// eslint-disable-next-line no-constant-condition
while (true) {
const action = await this.actionQueue.dequeue();
try {
await action();
} catch (e) {
console.error(e);
}
}
}
sendInput(values: InputValues): void {
const msg = JSON.stringify({
method: "update",
@@ -482,7 +459,7 @@ class ShinyApp {
}
}
async receiveOutput<T>(name: string, value: T): Promise<T | undefined> {
receiveOutput<T>(name: string, value: T): T | undefined {
const binding = this.$bindings[name];
const evt: ShinyEventValue = $.Event("shiny:value");
@@ -501,7 +478,7 @@ class ShinyApp {
$(binding ? binding.el : document).trigger(evt);
if (!evt.isDefaultPrevented() && binding) {
await binding.onValueChange(evt.value);
binding.onValueChange(evt.value);
}
return value;
@@ -621,7 +598,7 @@ class ShinyApp {
// // Added in shiny init method
// Shiny.addCustomMessageHandler = addCustomMessageHandler;
async dispatchMessage(data: ArrayBufferLike | string): Promise<void> {
dispatchMessage(data: ArrayBufferLike | string): void {
let msgObj: ShinyEventMessage["message"] = {};
if (typeof data === "string") {
@@ -650,7 +627,7 @@ class ShinyApp {
if (evt.isDefaultPrevented()) return;
// Send msgObj.foo and msgObj.bar to appropriate handlers
await this._sendMessagesToHandlers(
this._sendMessagesToHandlers(
evt.message,
messageHandlers,
messageHandlerOrder
@@ -663,11 +640,11 @@ class ShinyApp {
// A function for sending messages to the appropriate handlers.
// - msgObj: the object containing messages, with format {msgObj.foo, msObj.bar
private async _sendMessagesToHandlers(
private _sendMessagesToHandlers(
msgObj: { [key: string]: unknown },
handlers: { [key: string]: Handler },
handlerOrder: string[]
): Promise<void> {
): void {
// Dispatch messages to handlers, if handler is present
for (let i = 0; i < handlerOrder.length; i++) {
const msgType = handlerOrder[i];
@@ -675,7 +652,7 @@ class ShinyApp {
if (hasOwnProperty(msgObj, msgType)) {
// Execute each handler with 'this' referring to the present value of
// 'this'
await handlers[msgType].call(this, msgObj[msgType]);
handlers[msgType].call(this, msgObj[msgType]);
}
}
}
@@ -685,7 +662,7 @@ class ShinyApp {
// * Use arrow functions to allow the Types to propagate.
// * However, `_sendMessagesToHandlers()` will adjust the `this` context to the same _`this`_.
addMessageHandler("values", async (message: { [key: string]: any }) => {
addMessageHandler("values", (message: { [key: string]: any }) => {
for (const name in this.$bindings) {
if (hasOwnProperty(this.$bindings, name))
this.$bindings[name].showProgress(false);
@@ -693,14 +670,17 @@ class ShinyApp {
for (const key in message) {
if (hasOwnProperty(message, key)) {
await this.receiveOutput(key, message[key]);
this.receiveOutput(key, message[key]);
}
}
});
addMessageHandler(
"errors",
(message: { [key: string]: ErrorsMessageValue }) => {
function (
this: ShinyApp,
message: { [key: string]: ErrorsMessageValue }
) {
for (const key in message) {
if (hasOwnProperty(message, key))
this.receiveError(key, message[key]);
@@ -745,10 +725,13 @@ class ShinyApp {
addMessageHandler(
"progress",
async (message: { type: string; message: { id: string } }) => {
function (
this: ShinyApp,
message: { type: string; message: { id: string } }
) {
if (message.type && message.message) {
// @ts-expect-error; Unknown values handled with followup if statement
const handler = await this.progressHandlers[message.type];
const handler = this.progressHandlers[message.type];
if (handler) handler.call(this, message.message);
}
@@ -757,13 +740,13 @@ class ShinyApp {
addMessageHandler(
"notification",
async (
(
message:
| { type: "remove"; message: string }
| { type: "show"; message: Parameters<typeof showNotification>[0] }
| { type: void }
) => {
if (message.type === "show") await showNotification(message.message);
if (message.type === "show") showNotification(message.message);
else if (message.type === "remove") removeNotification(message.message);
else throw "Unkown notification type: " + message.type;
}
@@ -771,13 +754,13 @@ class ShinyApp {
addMessageHandler(
"modal",
async (
(
message:
| { type: "remove"; message: string }
| { type: "show"; message: Parameters<typeof showModal>[0] }
| { type: void }
) => {
if (message.type === "show") await showModal(message.message);
if (message.type === "show") showModal(message.message);
// For 'remove', message content isn't used
else if (message.type === "remove") removeModal();
else throw "Unkown modal type: " + message.type;
@@ -877,12 +860,12 @@ class ShinyApp {
addMessageHandler(
"shiny-insert-ui",
async (message: {
(message: {
selector: string;
content: { html: string; deps: HtmlDep[] };
multiple: boolean;
multiple: false | void;
where: WherePosition;
}): Promise<void> => {
}) => {
const targets = $(message.selector);
if (targets.length === 0) {
@@ -894,24 +877,19 @@ class ShinyApp {
message.selector +
'") could not be found in the DOM.'
);
await renderHtmlAsync(
message.content.html,
$([]),
message.content.deps
);
renderHtml(message.content.html, $([]), message.content.deps);
} else {
for (const target of targets) {
await renderContentAsync(target, message.content, message.where);
// If multiple is false, only render to the first target.
if (message.multiple === false) break;
}
targets.each(function (i, target) {
renderContent(target, message.content, message.where);
return message.multiple;
});
}
}
);
addMessageHandler(
"shiny-remove-ui",
(message: { selector: string; multiple: boolean }) => {
(message: { selector: string; multiple: false | void }) => {
const els = $(message.selector);
els.each(function (i, el) {
@@ -919,8 +897,8 @@ class ShinyApp {
$(el).remove();
// If `multiple` is false, returning false terminates the function
// and no other elements are removed; if `multiple` is true,
// returning nothing continues removing all remaining elements.
return message.multiple === false ? false : undefined;
// returning true continues removing all remaining elements.
return message.multiple;
});
}
);
@@ -1000,7 +978,7 @@ class ShinyApp {
addMessageHandler(
"shiny-insert-tab",
async (message: {
(message: {
inputId: string;
divTag: { html: string; deps: HtmlDep[] };
liTag: { html: string; deps: HtmlDep[] };
@@ -1008,7 +986,7 @@ class ShinyApp {
position: "after" | "before" | void;
select: boolean;
menuName: string;
}): Promise<void> => {
}) => {
const $parentTabset = getTabset(message.inputId);
let $tabset = $parentTabset;
const $tabContent = getTabContent($tabset);
@@ -1082,7 +1060,7 @@ class ShinyApp {
}
}
await renderContentAsync($liTag[0], {
renderContent($liTag[0], {
html: $liTag.html(),
deps: message.liTag.deps,
});
@@ -1117,13 +1095,13 @@ class ShinyApp {
// lower-level functions that renderContent uses. Like if we pre-process
// the value of message.divTag.html for singletons, we could do that, then
// render dependencies, then do $tabContent.append($divTag).
await renderContentAsync(
renderContent(
$tabContent[0],
{ html: "", deps: message.divTag.deps },
// @ts-expect-error; TODO-barret; There is no usage of beforeend
"beforeend"
);
for (const el of $divTag.get()) {
$divTag.get().forEach((el) => {
// Must not use jQuery for appending el to the doc, we don't want any
// scripts to run (since they will run when renderContent takes a crack).
$tabContent[0].appendChild(el);
@@ -1132,8 +1110,8 @@ class ShinyApp {
// and not the whole tag. That's fine in this case because we control the
// R code that generates this HTML, and we know that the element is not
// a script tag.
await renderContentAsync(el, el.innerHTML || el.textContent);
}
renderContent(el, el.innerHTML || el.textContent);
});
if (message.select) {
$liTag.find("a").tab("show");
@@ -1401,17 +1379,17 @@ class ShinyApp {
},
// Open a page-level progress bar
open: async function (message: {
open: function (message: {
style: "notification" | "old";
id: string;
}): Promise<void> {
}): void {
if (message.style === "notification") {
// For new-style (starting in Shiny 0.14) progress indicators that use
// the notification API.
// Progress bar starts hidden; will be made visible if a value is provided
// during updates.
await showNotification({
showNotification({
html:
`<div id="shiny-progress-${message.id}" class="shiny-progress-notification">` +
'<div class="progress active" style="display: none;"><div class="progress-bar"></div></div>' +

View File

@@ -1,42 +0,0 @@
// Adapted from https://stackoverflow.com/a/47157945/412655
export class AsyncQueue<T> {
private $promises: Array<Promise<T>> = [];
private $resolvers: Array<(x: T) => void> = [];
private _add() {
const p: Promise<T> = new Promise((resolve) => {
this.$resolvers.push(resolve);
});
this.$promises.push(p);
}
enqueue(x: T): void {
if (!this.$resolvers.length) this._add();
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const resolve = this.$resolvers.shift()!;
resolve(x);
}
async dequeue(): Promise<T> {
if (!this.$promises.length) this._add();
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const promise = this.$promises.shift()!;
return promise;
}
isEmpty(): boolean {
return !this.$promises.length;
}
isBlocked(): boolean {
return !!this.$resolvers.length;
}
get length(): number {
return this.$promises.length - this.$resolvers.length;
}
}

View File

@@ -125,8 +125,7 @@ function makeResizeFilter(
let lastSize: LastSizeInterface = {};
return function () {
const rect = el.getBoundingClientRect();
const size = { w: rect.width, h: rect.height };
const size = { w: el.offsetWidth, h: el.offsetHeight };
if (size.w === 0 && size.h === 0) return;
if (size.w === lastSize.w && size.h === lastSize.h) return;

View File

@@ -33,7 +33,7 @@ declare class SelectInputBinding extends InputBinding {
subscribe(el: SelectHTMLElement, callback: (x: boolean) => void): void;
unsubscribe(el: HTMLElement): void;
initialize(el: SelectHTMLElement): void;
protected _selectize(el: SelectHTMLElement, update?: boolean): SelectizeInfo | undefined;
protected _selectize(el: SelectHTMLElement, update?: boolean): SelectizeInfo;
}
export { SelectInputBinding };
export type { SelectInputReceiveMessageData };

View File

@@ -1,9 +1,9 @@
import { OutputBinding } from "./outputBinding";
import { renderContentAsync } from "../../shiny/render";
import { renderContent } from "../../shiny/render";
import type { ErrorsMessageValue } from "../../shiny/shinyapp";
declare class HtmlOutputBinding extends OutputBinding {
find(scope: HTMLElement): JQuery<HTMLElement>;
onValueError(el: HTMLElement, err: ErrorsMessageValue): void;
renderValue(el: HTMLElement, data: Parameters<typeof renderContentAsync>[1]): Promise<void>;
renderValue(el: HTMLElement, data: Parameters<typeof renderContent>[1]): void;
}
export { HtmlOutputBinding };

View File

@@ -2,9 +2,9 @@ import type { ErrorsMessageValue } from "../../shiny/shinyapp";
declare class OutputBinding {
name: string;
find(scope: HTMLElement | JQuery<HTMLElement>): JQuery<HTMLElement>;
renderValue(el: HTMLElement, data: unknown): Promise<void> | void;
renderValue(el: HTMLElement, data: unknown): void;
getId(el: HTMLElement): string;
onValueChange(el: HTMLElement, data: unknown): Promise<void>;
onValueChange(el: HTMLElement, data: unknown): void;
onValueError(el: HTMLElement, err: ErrorsMessageValue): void;
renderError(el: HTMLElement, err: ErrorsMessageValue): void;
clearError(el: HTMLElement): void;

View File

@@ -8,7 +8,7 @@ declare class OutputBindingAdapter {
binding: OutputBinding;
constructor(el: HTMLElement, binding: OutpuBindingWithResize);
getId(): string;
onValueChange(data: unknown): Promise<void>;
onValueChange(data: unknown): void;
onValueError(err: ErrorsMessageValue): void;
showProgress(show: boolean): void;
onResize(): void;

View File

@@ -3,11 +3,11 @@ import type { ShinyApp } from "../shiny/shinyapp";
declare class InputBatchSender implements InputPolicy {
target: InputPolicy;
shinyapp: ShinyApp;
timerId: ReturnType<typeof setTimeout> | null;
pendingData: {
[key: string]: unknown;
};
reentrant: boolean;
sendIsEnqueued: boolean;
lastChanceCallback: Array<() => void>;
constructor(shinyapp: ShinyApp);
setInput(nameType: string, value: unknown, opts: InputPolicyOpts): void;

View File

@@ -4,7 +4,7 @@ import { $escape, compareVersion } from "../utils";
import { showNotification, removeNotification } from "./notifications";
import { showModal, removeModal } from "./modal";
import { showReconnectDialog, hideReconnectDialog } from "./reconnectDialog";
import { renderContentAsync, renderContent, renderDependenciesAsync, renderDependencies, renderHtmlAsync, renderHtml } from "./render";
import { renderContent, renderDependencies, renderHtml } from "./render";
import type { shinyBindAll, shinyForgetLastInputValue, shinySetInputValue, shinyInitializeInputs, shinyUnbindAll } from "./initedMethods";
import type { Handler, ShinyApp } from "./shinyapp";
import { addCustomMessageHandler } from "./shinyapp";
@@ -30,11 +30,8 @@ interface Shiny {
createSocket?: () => WebSocket;
showReconnectDialog: typeof showReconnectDialog;
hideReconnectDialog: typeof hideReconnectDialog;
renderDependenciesAsync: typeof renderDependenciesAsync;
renderDependencies: typeof renderDependencies;
renderContentAsync: typeof renderContentAsync;
renderContent: typeof renderContent;
renderHtmlAsync: typeof renderHtmlAsync;
renderHtml: typeof renderHtml;
user: string;
progressHandlers?: ShinyApp["progressHandlers"];

View File

@@ -1,7 +1,6 @@
import type { HtmlDep } from "./render";
declare function show({ html, deps, }?: {
html?: string;
deps?: HtmlDep[];
}): Promise<void>;
declare function show({ html, deps }?: {
html?: string | undefined;
deps?: never[] | undefined;
}): void;
declare function remove(): void;
export { show as showModal, remove as removeModal };

View File

@@ -8,6 +8,6 @@ declare function show({ html, action, deps, duration, id, closeButton, type, }?:
id?: string | null;
closeButton?: boolean;
type?: string | null;
}): Promise<ReturnType<typeof randomId>>;
}): ReturnType<typeof randomId>;
declare function remove(id: string): void;
export { show as showNotification, remove as removeNotification };

View File

@@ -1,18 +1,12 @@
import type { BindScope } from "./bind";
import { renderHtml as singletonsRenderHtml } from "./singletons";
import type { WherePosition } from "./singletons";
declare function renderContentAsync(el: BindScope, content: string | {
html: string;
deps?: HtmlDep[];
} | null, where?: WherePosition): Promise<void>;
declare function renderDependencies(dependencies: HtmlDep[] | null): void;
declare function renderContent(el: BindScope, content: string | {
html: string;
deps?: HtmlDep[];
} | null, where?: WherePosition): void;
declare function renderHtmlAsync(html: string, el: BindScope, dependencies: HtmlDep[], where?: WherePosition): Promise<ReturnType<typeof singletonsRenderHtml>>;
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 = {
name: string;
@@ -51,5 +45,5 @@ declare type HtmlDep = {
head?: string;
};
declare function registerDependency(name: string, version: HtmlDepVersion): void;
export { renderContentAsync, renderContent, renderHtmlAsync, renderHtml, renderDependenciesAsync, renderDependencies, registerDependency, };
export { renderDependencies, renderContent, renderHtml, registerDependency };
export type { HtmlDep };

View File

@@ -1,8 +1,7 @@
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 Handler = (message: any) => void;
declare type ShinyWebSocket = WebSocket & {
allowReconnect?: boolean;
};
@@ -20,7 +19,6 @@ declare 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>;
config: {
workerId: string;
sessionId: string;
@@ -52,7 +50,6 @@ declare class ShinyApp {
private scheduledReconnect;
reconnect(): void;
createSocket(): ShinyWebSocket;
startActionQueueLoop(): Promise<void>;
sendInput(values: InputValues): void;
$notifyDisconnected(): void;
$removeSocket(): void;
@@ -66,13 +63,13 @@ declare class ShinyApp {
makeRequest(method: string, args: unknown[], onSuccess: OnSuccessRequest, onError: OnErrorRequest, blobs: Array<ArrayBuffer | Blob | string> | undefined): void;
$sendMsg(msg: MessageValue): void;
receiveError(name: string, error: ErrorsMessageValue): void;
receiveOutput<T>(name: string, value: T): Promise<T | undefined>;
receiveOutput<T>(name: string, value: T): T | undefined;
bindOutput(id: string, binding: OutputBindingAdapter): OutputBindingAdapter;
unbindOutput(id: string, binding: OutputBindingAdapter): boolean;
private _narrowScopeComponent;
private _narrowScope;
$updateConditionals(): void;
dispatchMessage(data: ArrayBufferLike | string): Promise<void>;
dispatchMessage(data: ArrayBufferLike | string): void;
private _sendMessagesToHandlers;
private _init;
progressHandlers: {
@@ -82,7 +79,7 @@ declare class ShinyApp {
open: (message: {
style: "notification" | "old";
id: string;
}) => Promise<void>;
}) => void;
update: (message: {
style: "notification" | "old";
id: string;

View File

@@ -1,10 +0,0 @@
export declare class AsyncQueue<T> {
private $promises;
private $resolvers;
private _add;
enqueue(x: T): void;
dequeue(): Promise<T>;
isEmpty(): boolean;
isBlocked(): boolean;
get length(): number;
}

View File

@@ -142,41 +142,6 @@
<div class="content-footer"></div>
</div>
---
Code
dropdown_active
Output
<div class="tabbable">
<ul class="nav nav-tabs" data-tabsetid="4785">
<li>
<a href="#tab-4785-1" data-toggle="tab" data-bs-toggle="tab" data-value="A">A</a>
</li>
<li>
<a href="#tab-4785-2" data-toggle="tab" data-bs-toggle="tab" data-value="B">
<i aria-label="github icon" class="fab fa-github fa-fw" role="presentation"></i>
B
</a>
</li>
<li class="dropdown active">
<a href="#" class="dropdown-toggle" data-toggle="dropdown" data-bs-toggle="dropdown" data-value="Menu">
Menu
<b class="caret"></b>
</a>
<ul class="dropdown-menu" data-tabsetid="1502">
<li class="active">
<a href="#tab-1502-1" data-toggle="tab" data-bs-toggle="tab" data-value="C">C</a>
</li>
</ul>
</li>
</ul>
<div class="tab-content" data-tabsetid="4785">
<div class="tab-pane" data-value="A" id="tab-4785-1">a</div>
<div class="tab-pane" data-value="B" data-icon-class="fab fa-github fa-fw" id="tab-4785-2">b</div>
<div class="tab-pane active" data-value="C" id="tab-1502-1">c</div>
</div>
</div>
# navbarPage() markup is correct
Code

View File

@@ -0,0 +1,54 @@
# app template works with runTests: all
Code
out
Output
Shiny App Test Results
* Success
- shinyAppTemplate-all/tests/testthat.R
# app template works with runTests: app_tests
Code
out
Output
Shiny App Test Results
* Success
- shinyAppTemplate-app_tests/tests/testthat.R
# app template works with runTests: app_module_tests
Code
out
Output
Shiny App Test Results
* Success
- shinyAppTemplate-app_module_tests/tests/testthat.R
# app template works with runTests: app_rdir_tests
Code
out
Output
Shiny App Test Results
* Success
- shinyAppTemplate-app_rdir_tests/tests/testthat.R
# app template works with runTests: app_module_rdir_tests
Code
out
Output
Shiny App Test Results
* Success
- shinyAppTemplate-app_module_rdir_tests/tests/testthat.R
# app template works with runTests: app_shinytest_testthat
Code
out
Output
Shiny App Test Results
* Success
- shinyAppTemplate-app_shinytest_testthat/tests/testthat.R

View File

@@ -50,7 +50,7 @@ test_that("Repeated names for selectInput and radioButtons choices", {
choices <- x$children
expect_equal(choices[[2]]$children[[1]][[1]]$children[[1]]$children[[2]]$children[[1]],
HTML('<i class="far fa-calendar" role="presentation" aria-label="calendar icon"></i>'))
HTML('<i class="fa fa-calendar" role="presentation" aria-label="calendar icon"></i>'))
expect_equal(choices[[2]]$children[[1]][[1]]$children[[1]]$children[[1]]$attribs$value, 'icon')
expect_equal(choices[[2]]$children[[1]][[1]]$children[[1]]$children[[1]]$attribs$checked, 'checked')

View File

@@ -1,6 +0,0 @@
test_that("plotPNG()/startPNG() ignores NULL dimensions", {
f <- plotPNG(function() plot(1), width = NULL, height = NULL)
on.exit(unlink(f))
bits <- readBin(f, "raw", file.info(f)$size)
expect_true(length(bits) > 0)
})

View File

@@ -41,6 +41,7 @@ panels <- list(
)
test_that("tabsetPanel() markup is correct", {
default <- tabset_panel(!!!panels)
pills <- tabset_panel(
!!!panels, type = "pills", selected = "B",
@@ -53,11 +54,6 @@ test_that("tabsetPanel() markup is correct", {
# BS4
expect_snapshot_bslib(default)
expect_snapshot_bslib(pills)
# Make sure .active class gets added to both the .dropdown as well as the
# .dropdown-menu's tab
dropdown_active <- tabset_panel(!!!panels, selected = "C")
expect_snapshot2(dropdown_active)
})
test_that("navbarPage() markup is correct", {

View File

@@ -413,11 +413,11 @@ test_that("captures ggplot2 outputs", {
server <- function(input, output, session){
output$fixed <- renderPlot({
withr::with_namespace("ggplot2", { ggplot(iris) + geom_point(aes(Sepal.Length, Sepal.Width)) })
ggplot2::qplot(iris$Sepal.Length, iris$Sepal.Width)
}, width=300, height=350)
output$dynamic <- renderPlot({
withr::with_namespace("ggplot2", { ggplot(iris) + geom_point(aes(Sepal.Length, Sepal.Width)) })
ggplot2::qplot(iris$Sepal.Length, iris$Sepal.Width)
})
}

View File

@@ -1,6 +1,5 @@
# testthat::skip_on_cran()
suppressWarnings(testthat::skip_if_not_installed("shinytest2"))
testthat::skip("Refactor test for next release")
# test all combos
make_combos <- function(...) {

View File

@@ -218,7 +218,3 @@ reference:
- shinyUI
- shinyServer
- exprToFunction
# This section is silently dropped by pkgdown https://github.com/r-lib/pkgdown/pull/1783
- title: internal
contents:
- shiny-package

View File

@@ -11,7 +11,7 @@ index e3cd799e..1d494f88 100644
+ // random IDs are assigned when .selectize() is called, but we're
+ // doing it here to limit the scope of changes.
+ var kids = self.$dropdown_content[0].children;
+ for (var i = 0; i < kids.length; i++) {
+ for (i = 0; i < kids.length; i++) {
+ var attrs = kids[i].attributes;
+ if (!attrs.role) {
+ kids[i].setAttribute("role", "option");

View File

@@ -1,13 +1,12 @@
{
"declaration": true,
"compilerOptions": {
"target": "es2020",
"target": "ES5",
"isolatedModules": true,
"esModuleInterop": true,
"declaration": true,
"declarationDir": "./srcts/types",
"emitDeclarationOnly": true,
"moduleResolution": "node",
// Can not use `types: []` to disable injecting NodeJS types. More types are
// needed than just the DOM's `window.setTimeout`
// "types": [],