Compare commits

...

18 Commits

Author SHA1 Message Date
Carson
42b40e2fd1 Attempt to fetch a session ID before establishing websocket connection (and include the ID if successful) 2025-07-24 11:58:30 -05:00
Carson Sievert
6325067130 Update DESCRIPTION (#4255)
* Change maintainer to Carson

* Add Garrick

* Add Posit ROR link

* `usethis::use_tidy_description()` (GitHub Actions)

* `devtools::document()` (GitHub Actions)

---------

Co-authored-by: cpsievert <cpsievert@users.noreply.github.com>
2025-07-22 13:19:24 -05:00
Carson Sievert
1a4e52dc73 Run routine (#4257)
* Touch news

* `usethis::use_tidy_description()` (GitHub Actions)

* Revert "Touch news"

This reverts commit 854e7e10ae.

* `usethis::use_tidy_description()` (GitHub Actions)

---------

Co-authored-by: cpsievert <cpsievert@users.noreply.github.com>
2025-07-22 12:38:23 -05:00
Winston Chang
08383ad8b9 Update srcts/README with info about window.Shiny (#4198) 2025-07-18 17:22:52 -05:00
Carson Sievert
ecf6bfe9a7 Put actionButton()s icon and label into containers (#4249)
* Put action icon and label into containers

* Update snaps

* More robust test

* Don't include container if icon/label isn't specified

* `yarn build` (GitHub Actions)

* Send HTML string/deps on update; update news

---------

Co-authored-by: cpsievert <cpsievert@users.noreply.github.com>
2025-07-14 16:22:03 -05:00
Carson Sievert
f7528568e5 Increment version number to 1.11.1.9000 (#4253)
* Increment version number to 1.11.1.9000

* `yarn build` (GitHub Actions)

* Sync package version (GitHub Actions)

---------

Co-authored-by: cpsievert <cpsievert@users.noreply.github.com>
2025-07-07 22:02:14 -07:00
Carson Sievert
51f653b66f v1.11.1 release candidate (#4245)
* v1.11.1 release candidate

* `yarn build` (GitHub Actions)

* Revert actionButton()/actionLink() implementation to v1.11.0's behavior (re-introducing #4239)

* Minimal fix to address the regression in #4239

Ideally we'd fix this issue, and also get updateActionButton() working with HTML labels, but thanks to today's release of kinesis (which snapshots all of actionButton()s markup), and CRAN dragging their feet to accept our original submission (which was fine, by the way), we can't have nice things

* `yarn build` (GitHub Actions)

---------

Co-authored-by: cpsievert <cpsievert@users.noreply.github.com>
2025-07-07 21:53:08 -07:00
Carson Sievert
460a93a5fd Revert the addition of spacing between icon and label in actionButton() (#4248)
* Revert the addition of spacing between icon and label in actionButton()

* `yarn build` (GitHub Actions)

---------

Co-authored-by: cpsievert <cpsievert@users.noreply.github.com>
2025-06-27 10:58:17 -05:00
Carson Sievert
3ea4c8eb1d Restrict icon/label separator spacing to actionButton() (#4247)
* Follow up to #4242: Restrict icon/label separator spacing to actionButton()

* `yarn build` (GitHub Actions)

* Add spacing only when both icon and label are truthy

* Update snapshot

* `yarn build` (GitHub Actions)

* Slightly more readable JS

* `yarn build` (GitHub Actions)

---------

Co-authored-by: cpsievert <cpsievert@users.noreply.github.com>
2025-06-26 19:12:13 -05:00
Carson Sievert
f237de559d Fix front-end action button label updating logic (#4242)
* Close #4239: fix front-end action button label updating logic (follow up to #3996)

* Update news

* Use a separator instead of putting markup in attributes

* `yarn build` (GitHub Actions)

* Address feedback

* Cleanup

* Refactor into a single method to split icon/label

* `yarn build` (GitHub Actions)

* Better naming

* Add some padding to the separator

* Add some unit tests for R logic

* Update NEWS.md

* Update NEWS.md

* Update NEWS.md

* Update NEWS.md

* Increase backcompat (keep same R structure when no icon is provided)

* Refine comment

---------

Co-authored-by: cpsievert <cpsievert@users.noreply.github.com>
2025-06-26 16:32:17 -05:00
Carson
8c7abbac44 Update news 2025-06-26 13:56:35 -05:00
Carson Sievert
1710316142 Properly handle undefined value for input subscribe callback (#4243)
* Close #4240: properly handle undefined value for input subscribe callback

* Refactor normalization into a function
2025-06-26 09:36:09 -05:00
Carson Sievert
2d856f4f09 Start new version (#4241)
* Start new version

* `yarn build` (GitHub Actions)

* Sync package version (GitHub Actions)

---------

Co-authored-by: cpsievert <cpsievert@users.noreply.github.com>
2025-06-25 17:05:03 -05:00
Carson Sievert
ab219e3408 v1.11.0 release candidate (#4232) 2025-06-25 16:28:51 -05:00
Carson Sievert
673be3dd77 Follow up to #3996: fix front-end checkbox label updating logic (#4238)
* Follow up to #3996: fix front-end checkbox label updating logic

* More descriptive name
2025-06-20 15:15:30 -05:00
Carson Sievert
b25e6feabb feat(InputBinding): subscribe callback now supports event priority (#4211)
* feat(InputBinding): subscribe callback now supports event priority

* Update NEWS.md

* Update srcts/src/shiny/bind.ts

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* `yarn build` (GitHub Actions)

* Simpler and more consistent typing

* Support a suitable object as input

* Provide a type for the callback itself, not just the valueit's given

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: cpsievert <cpsievert@users.noreply.github.com>
2025-06-19 10:27:45 -05:00
Carson Sievert
e6b22d86b6 Follow up to #3996 when label is unspecified (i.e., NULL), don't include it in the message (#4237) 2025-06-19 09:48:44 -05:00
Carson Sievert
9c5196ee63 Run routine (#4234)
* Run routine

* `devtools::document()` (GitHub Actions)

* `yarn build` (GitHub Actions)

* Update NEWS.md

---------

Co-authored-by: cpsievert <cpsievert@users.noreply.github.com>
2025-06-16 12:52:48 -05:00
39 changed files with 606 additions and 407 deletions

View File

@@ -1,122 +1,132 @@
Package: shiny
Type: Package
Package: shiny
Title: Web Application Framework for R
Version: 1.10.0.9001
Version: 1.11.1.9000
Authors@R: c(
person("Winston", "Chang", role = c("aut", "cre"), email = "winston@posit.co", comment = c(ORCID = "0000-0002-1576-2126")),
person("Joe", "Cheng", role = "aut", email = "joe@posit.co"),
person("JJ", "Allaire", role = "aut", email = "jj@posit.co"),
person("Carson", "Sievert", role = "aut", email = "carson@posit.co", comment = c(ORCID = "0000-0002-4958-2844")),
person("Barret", "Schloerke", role = "aut", email = "barret@posit.co", comment = c(ORCID = "0000-0001-9986-114X")),
person("Yihui", "Xie", role = "aut", email = "yihui@posit.co"),
person("Winston", "Chang", , "winston@posit.co", role = "aut",
comment = c(ORCID = "0000-0002-1576-2126")),
person("Joe", "Cheng", , "joe@posit.co", role = "aut"),
person("JJ", "Allaire", , "jj@posit.co", role = "aut"),
person("Carson", "Sievert", , "carson@posit.co", role = c("aut", "cre"),
comment = c(ORCID = "0000-0002-4958-2844")),
person("Barret", "Schloerke", , "barret@posit.co", role = "aut",
comment = c(ORCID = "0000-0001-9986-114X")),
person("Garrick", "Aden-Buie", , "garrick@adenbuie.com", role = "aut",
comment = c(ORCID = "0000-0002-7111-0077")),
person("Yihui", "Xie", , "yihui@posit.co", role = "aut"),
person("Jeff", "Allen", role = "aut"),
person("Jonathan", "McPherson", role = "aut", email = "jonathan@posit.co"),
person("Jonathan", "McPherson", , "jonathan@posit.co", role = "aut"),
person("Alan", "Dipert", role = "aut"),
person("Barbara", "Borges", role = "aut"),
person("Posit Software, PBC", role = c("cph", "fnd")),
person(family = "jQuery Foundation", role = "cph",
comment = "jQuery library and jQuery UI library"),
person(family = "jQuery contributors", role = c("ctb", "cph"),
comment = "jQuery library; authors listed in inst/www/shared/jquery-AUTHORS.txt"),
person(family = "jQuery UI contributors", role = c("ctb", "cph"),
comment = "jQuery UI library; authors listed in inst/www/shared/jqueryui/AUTHORS.txt"),
person("Posit Software, PBC", role = c("cph", "fnd"),
comment = c(ROR = "03wc8by49")),
person(, "jQuery Foundation", role = "cph",
comment = "jQuery library and jQuery UI library"),
person(, "jQuery contributors", role = c("ctb", "cph"),
comment = "jQuery library; authors listed in inst/www/shared/jquery-AUTHORS.txt"),
person(, "jQuery UI contributors", role = c("ctb", "cph"),
comment = "jQuery UI library; authors listed in inst/www/shared/jqueryui/AUTHORS.txt"),
person("Mark", "Otto", role = "ctb",
comment = "Bootstrap library"),
comment = "Bootstrap library"),
person("Jacob", "Thornton", role = "ctb",
comment = "Bootstrap library"),
person(family = "Bootstrap contributors", role = "ctb",
comment = "Bootstrap library"),
person(family = "Twitter, Inc", role = "cph",
comment = "Bootstrap library"),
comment = "Bootstrap library"),
person(, "Bootstrap contributors", role = "ctb",
comment = "Bootstrap library"),
person(, "Twitter, Inc", role = "cph",
comment = "Bootstrap library"),
person("Prem Nawaz", "Khan", role = "ctb",
comment = "Bootstrap accessibility plugin"),
comment = "Bootstrap accessibility plugin"),
person("Victor", "Tsaran", role = "ctb",
comment = "Bootstrap accessibility plugin"),
comment = "Bootstrap accessibility plugin"),
person("Dennis", "Lembree", role = "ctb",
comment = "Bootstrap accessibility plugin"),
comment = "Bootstrap accessibility plugin"),
person("Srinivasu", "Chakravarthula", role = "ctb",
comment = "Bootstrap accessibility plugin"),
comment = "Bootstrap accessibility plugin"),
person("Cathy", "O'Connor", role = "ctb",
comment = "Bootstrap accessibility plugin"),
person(family = "PayPal, Inc", role = "cph",
comment = "Bootstrap accessibility plugin"),
comment = "Bootstrap accessibility plugin"),
person(, "PayPal, Inc", role = "cph",
comment = "Bootstrap accessibility plugin"),
person("Stefan", "Petre", role = c("ctb", "cph"),
comment = "Bootstrap-datepicker library"),
comment = "Bootstrap-datepicker library"),
person("Andrew", "Rowls", role = c("ctb", "cph"),
comment = "Bootstrap-datepicker library"),
comment = "Bootstrap-datepicker library"),
person("Brian", "Reavis", role = c("ctb", "cph"),
comment = "selectize.js library"),
comment = "selectize.js library"),
person("Salmen", "Bejaoui", role = c("ctb", "cph"),
comment = "selectize-plugin-a11y library"),
comment = "selectize-plugin-a11y library"),
person("Denis", "Ineshin", role = c("ctb", "cph"),
comment = "ion.rangeSlider library"),
comment = "ion.rangeSlider library"),
person("Sami", "Samhuri", role = c("ctb", "cph"),
comment = "Javascript strftime library"),
person(family = "SpryMedia Limited", role = c("ctb", "cph"),
comment = "DataTables library"),
comment = "Javascript strftime library"),
person(, "SpryMedia Limited", role = c("ctb", "cph"),
comment = "DataTables library"),
person("John", "Fraser", role = c("ctb", "cph"),
comment = "showdown.js library"),
comment = "showdown.js library"),
person("John", "Gruber", role = c("ctb", "cph"),
comment = "showdown.js library"),
comment = "showdown.js library"),
person("Ivan", "Sagalaev", role = c("ctb", "cph"),
comment = "highlight.js library"),
person(given = "R Core Team", role = c("ctb", "cph"),
comment = "tar implementation from R")
)
comment = "highlight.js library"),
person("R Core Team", role = c("ctb", "cph"),
comment = "tar implementation from R")
)
Description: Makes it incredibly easy to build interactive web
applications with R. Automatic "reactive" binding between inputs and
outputs and extensive prebuilt widgets make it possible to build
beautiful, responsive, and powerful applications with minimal effort.
License: GPL-3 | file LICENSE
URL: https://shiny.posit.co/, https://github.com/rstudio/shiny
BugReports: https://github.com/rstudio/shiny/issues
Depends:
R (>= 3.0.2),
methods
methods,
R (>= 3.0.2)
Imports:
utils,
grDevices,
httpuv (>= 1.5.2),
mime (>= 0.3),
jsonlite (>= 0.9.16),
xtable,
fontawesome (>= 0.4.0),
htmltools (>= 0.5.4),
R6 (>= 2.0),
sourcetools,
later (>= 1.0.0),
promises (>= 1.3.2),
tools,
cli,
rlang (>= 0.4.10),
fastmap (>= 1.1.1),
withr,
commonmark (>= 1.7),
glue (>= 1.3.2),
bslib (>= 0.6.0),
cachem (>= 1.1.0),
lifecycle (>= 0.2.0)
cli,
commonmark (>= 1.7),
fastmap (>= 1.1.1),
fontawesome (>= 0.4.0),
glue (>= 1.3.2),
grDevices,
htmltools (>= 0.5.4),
httpuv (>= 1.5.2),
jsonlite (>= 0.9.16),
later (>= 1.0.0),
lifecycle (>= 0.2.0),
mime (>= 0.3),
promises (>= 1.3.2),
R6 (>= 2.0),
rlang (>= 0.4.10),
sourcetools,
tools,
utils,
withr,
xtable
Suggests:
Cairo (>= 1.5-5),
coro (>= 1.1.0),
datasets,
DT,
Cairo (>= 1.5-5),
testthat (>= 3.2.1),
knitr (>= 1.6),
markdown,
rmarkdown,
ggplot2,
reactlog (>= 1.0.0),
magrittr,
yaml,
mirai,
future,
dygraphs,
future,
ggplot2,
knitr (>= 1.6),
magrittr,
markdown,
mirai,
ragg,
showtext,
reactlog (>= 1.0.0),
rmarkdown,
sass,
watcher
URL: https://shiny.posit.co/,
https://github.com/rstudio/shiny
BugReports: https://github.com/rstudio/shiny/issues
showtext,
testthat (>= 3.2.1),
watcher,
yaml
Config/Needs/check: shinytest2
Config/testthat/edition: 3
Encoding: UTF-8
Roxygen: list(markdown = TRUE)
RoxygenNote: 7.3.2
Collate:
'globals.R'
'app-state.R'
@@ -202,6 +212,7 @@ Collate:
'test.R'
'update-input.R'
'utils-lang.R'
'utils-tags.R'
'version_bs_date_picker.R'
'version_ion_range_slider.R'
'version_jquery.R'
@@ -209,9 +220,3 @@ Collate:
'version_selectize.R'
'version_strftime.R'
'viewer.R'
RoxygenNote: 7.3.2
Encoding: UTF-8
Roxygen: list(markdown = TRUE)
Config/testthat/edition: 3
Config/Needs/check:
shinytest2

54
NEWS.md
View File

@@ -2,15 +2,27 @@
## New features
* `textInput()`, `textAreaInput()`, `numericInput()` and `passwordInput()` all gain an `updateOn` option. `updateOn = "change"` is the default and previous behavior, where the input value updates immediately whenever the value changes. With `updateOn = "blur"`, the input value will update only when the text input loses focus or when the user presses Enter (or Cmd/Ctrl + Enter for `textAreaInput()`). (#4183)
* The `icon` argument of `updateActionButton()`/`updateActionLink()` nows allows values other than `shiny::icon()` (e.g., `fontawesome::fa()`, `bsicons::bs_icon()`, etc). (#4249)
* `textAreaInput()` gains a `autoresize` option, which automatically resizes the text area to fit its content. (#4210)
## Bug fixes
* The family of `update*Input()` functions can now render HTML content passed to the `label` argument (e.g., `updateInputText(label = tags$b("New label"))`). (#3996)
* `updateActionButton()`/`updateActionLink()` now correctly renders HTML content passed to the `label` argument. (#4249)
## Changes
* Shiny no longer suspends input changes when _any_ `<input type="submit">` or `<button type="submit">` is on the page. Instead, it now only suspends when a `submitButton()` is present. If you have reason for creating a submit button from custom HTML, add a CSS class of `shiny-submit-button` to the button. (#4209)
* The return value of `actionButton()`/`actionLink()` changed slightly: `label` and `icon` are wrapped in an additional HTML container element. This allows for: 1. `updateActionButton()`/`updateActionLink()` to distinguish between the `label` and `icon` when making updates and 2. spacing between `label` and `icon` to be more easily customized via CSS.
# shiny 1.11.1
This is a patch release primarily for addressing the bugs introduced in v1.11.0.
## Bug fixes
* Fixed an issue where `InputBinding` implementations that don't pass a value to their `subscribe` callback were no longer notifying Shiny of input changes. (#4243)
* `updateActionButton()` and `updateActionLink()` once again handle `label` updates correctly. (#4245)
# shiny 1.11.0
## Improvements
@@ -21,13 +33,29 @@
* Shows a spinner on `tableOutput()`. (#4172)
* Places a minimum height on recalculating outputs so that the spinner is always visible. (#4172)
* Shiny now uses `{cli}` instead of `{crayon}` for rich log messages. (@olivroy #4170)
* Shiny now uses `{cli}` instead of `{crayon}` for rich log messages. (thanks @olivroy, #4170)
* Shiny's Typescript assets are now compiled to ES2021 instead of ES5. (#4066)
* `renderPlot()` was updated to accommodate changes in ggplot2 v4.0.0. (#4226)
* When adding the new tab via `insertTab()` or `bslib::nav_insert()`, the underlying JavaScript no longer renders content twice. (#4179)
## New features
* `textInput()`, `textAreaInput()`, `numericInput()` and `passwordInput()` all gain an `updateOn` option. `updateOn = "change"` is the default and previous behavior, where the input value updates immediately whenever the value changes. With `updateOn = "blur"`, the input value will update only when the text input loses focus or when the user presses Enter (or Cmd/Ctrl + Enter for `textAreaInput()`). (#4183)
* `textAreaInput()` gains a `autoresize` option, which automatically resizes the text area to fit its content. (#4210)
* The family of `update*Input()` functions can now render HTML content passed to the `label` argument (e.g., `updateInputText(label = tags$b("New label"))`). (#3996)
* `ExtendedTask` now catches synchronous values and errors and returns them via `$result()`. Previously, the extended task function was required to always return a promise. This change makes it easier to use `ExtendedTask` with a function that may return early or do some synchronous work before returning a promise. (#4225)
* `renderPlot()` was updated to accomodate changes in ggplot2 v4.0.0. (#4226)
* The `callback` argument of Shiny.js' `InputBinding.subscribe()` method gains support for a value of `"event"`. This makes it possible for an input binding to use event priority when updating the value (i.e., send immediately and always resend, even if the value hasn't changed). (#4211)
## Changes
* Shiny no longer suspends input changes when _any_ `<input type="submit">` or `<button type="submit">` is on the page. Instead, it now only suspends when a `submitButton()` is present. If you have reason for creating a submit button from custom HTML, add a CSS class of `shiny-submit-button` to the button. (#4209)
* Shiny's JavaScript assets are now compiled to ES2021 instead of ES5. (#4066)
* Upgraded jQuery from 3.6.0 to 3.7.1. (#3969)
@@ -35,15 +63,13 @@
## Bug fixes
* `runExample("08_html")` now (correctly) requests to 'shiny.min.css', eliminating a network request failure. (#4220)
* Fixed a bug with modals where calling `removeModal()` too quickly after `showModal()` would fail to remove the modal if the remove modal message was received while the modal was in the process of being revealed. (#4173)
* The Shiny Client Console (enabled with `shiny::devmode()`) no longer displays duplicate warning or error message. (#4177)
* Updated the JavaScript used when inserting a tab to avoid rendering dynamic UI elements twice when adding the new tab via `insertTab()` or `bslib::nav_insert()`. (#4179)
* Synchronous errors that occur inside a `ExtendedTask` no longer stop the session. (#4225)
* Fixed an issue with `ExtendedTask` where synchronous errors would cause an error that would stop the current session. (#4225)
* Calling `removeModal()` immediately after `showModal()` no longer fails to remove the modal (this would sometimes happen if the remove message was received while the modal was in the process of being revealed). (#4173)
* `runExample("08_html")` now (correctly) requests to 'shiny.min.css', eliminating a network request failure. (#4220)
* `shiny::shinyAppTemplate()` no longer errors without a call to `library(shiny)`. (#3870)
@@ -394,7 +420,7 @@ This release focuses on improvements in three main areas:
* Fixed #2951: screen readers correctly announce labels and date formats for `dateInput()` and `dateRangeInput()` widgets. (#2978)
* Closed #2847: `selectInput()` is reasonably accessible for screen readers even when `selectize` option is set to TRUE. To improve `selectize.js` accessibility, we have added [selectize-plugin-a11y](https://github.com/SLMNBJ/selectize-plugin-a11y) by default. (#2993)
* Closed #2847: `selectInput()` is reasonably accessible for screen readers even when `selectize` option is set to TRUE. To improve `selectize.js` accessibility, we have added [selectize-plugin-a11y](https://github.com/SalmenBejaoui/selectize-plugin-a11y) by default. (#2993)
* Closed #612: Added `alt` argument to `renderPlot()` and `renderCachedPlot()` to specify descriptive texts for `plotOutput()` objects, which is essential for screen readers. By default, alt text is set to the static text, "Plot object," but even dynamic text can be made with reactive function. (#3006, thanks @trafficonese and @leonawicz for the original PR and discussion via #2494)

View File

@@ -56,13 +56,24 @@ actionButton <- function(inputId, label, icon = NULL, width = NULL,
value <- restoreInput(id = inputId, default = NULL)
tags$button(id=inputId,
icon <- validateIcon(icon)
if (!is.null(icon)) {
icon <- span(icon, class = "action-icon")
}
if (!is.null(label)) {
label <- span(label, class = "action-label")
}
tags$button(
id = inputId,
style = css(width = validateCssUnit(width)),
type="button",
class="btn btn-default action-button",
type = "button",
class = "btn btn-default action-button",
`data-val` = value,
disabled = if (isTRUE(disabled)) NA else NULL,
list(validateIcon(icon), label),
icon, label,
...
)
}
@@ -72,30 +83,40 @@ actionButton <- function(inputId, label, icon = NULL, width = NULL,
actionLink <- function(inputId, label, icon = NULL, ...) {
value <- restoreInput(id = inputId, default = NULL)
tags$a(id=inputId,
href="#",
class="action-button",
icon <- validateIcon(icon)
if (!is.null(icon)) {
icon <- span(icon, class = "action-icon")
}
if (!is.null(label)) {
label <- span(label, class = "action-label")
}
tags$a(
id = inputId,
href = "#",
class = "action-button action-link",
`data-val` = value,
list(validateIcon(icon), label),
icon, label,
...
)
}
# Check that the icon parameter is valid:
# 1) Check if the user wants to actually add an icon:
# -- if icon=NULL, it means leave the icon unchanged
# -- if icon=character(0), it means don't add an icon or, more usefully,
# remove the previous icon
# 2) If so, check that the icon has the right format (this does not check whether
# it is a *real* icon - currently that would require a massive cross reference
# with the "font-awesome" and the "glyphicon" libraries)
# Throw an informative warning if icon isn't html-ish
validateIcon <- function(icon) {
if (is.null(icon) || identical(icon, character(0))) {
if (length(icon) == 0) {
return(icon)
} else if (inherits(icon, "shiny.tag") && icon$name == "i") {
return(icon)
} else {
stop("Invalid icon. Use Shiny's 'icon()' function to generate a valid icon")
}
if (!isTagLike(icon)) {
rlang::warn(
c(
"It appears that a non-HTML value was provided to `icon`.",
i = "Try using a `shiny::icon()` (or an equivalent) to get an icon."
),
class = "shiny-validate-icon"
)
}
icon
}

View File

@@ -38,8 +38,8 @@ updateTextInput <- function(session = getDefaultReactiveDomain(), inputId, label
validate_session_object(session)
message <- dropNulls(list(
label = processDeps(label, session),
value = value,
label = if (!is.null(label)) processDeps(label, session),
value = value,
placeholder = placeholder
))
session$sendInputMessage(inputId, message)
@@ -116,7 +116,7 @@ updateCheckboxInput <- function(session = getDefaultReactiveDomain(), inputId, l
validate_session_object(session)
message <- dropNulls(list(
label = processDeps(label, session),
label = if (!is.null(label)) processDeps(label, session),
value = value
))
session$sendInputMessage(inputId, message)
@@ -181,10 +181,9 @@ updateCheckboxInput <- function(session = getDefaultReactiveDomain(), inputId, l
updateActionButton <- function(session = getDefaultReactiveDomain(), inputId, label = NULL, icon = NULL, disabled = NULL) {
validate_session_object(session)
if (!is.null(icon)) icon <- as.character(validateIcon(icon))
message <- dropNulls(list(
label = processDeps(label, session),
icon = icon,
label = if (!is.null(label)) processDeps(label, session),
icon = if (!is.null(icon)) processDeps(validateIcon(icon), session),
disabled = disabled
))
session$sendInputMessage(inputId, message)
@@ -192,7 +191,7 @@ updateActionButton <- function(session = getDefaultReactiveDomain(), inputId, la
#' @rdname updateActionButton
#' @export
updateActionLink <- function(session = getDefaultReactiveDomain(), inputId, label = NULL, icon = NULL) {
updateActionButton(session, inputId = inputId, label = processDeps(label, session), icon = icon)
updateActionButton(session, inputId=inputId, label=label, icon=icon)
}
@@ -237,7 +236,7 @@ updateDateInput <- function(session = getDefaultReactiveDomain(), inputId, label
max <- dateYMD(max, "max")
message <- dropNulls(list(
label = processDeps(label, session),
label = if (!is.null(label)) processDeps(label, session),
value = value,
min = min,
max = max
@@ -291,7 +290,7 @@ updateDateRangeInput <- function(session = getDefaultReactiveDomain(), inputId,
max <- dateYMD(max, "max")
message <- dropNulls(list(
label = processDeps(label, session),
label = if (!is.null(label)) processDeps(label, session),
value = dropNulls(list(start = start, end = end)),
min = min,
max = max
@@ -395,7 +394,7 @@ updateNumericInput <- function(session = getDefaultReactiveDomain(), inputId, la
validate_session_object(session)
message <- dropNulls(list(
label = processDeps(label, session),
label = if (!is.null(label)) processDeps(label, session),
value = formatNoSci(value),
min = formatNoSci(min),
max = formatNoSci(max),
@@ -479,7 +478,7 @@ updateSliderInput <- function(session = getDefaultReactiveDomain(), inputId, lab
}
message <- dropNulls(list(
label = processDeps(label, session),
label = if (!is.null(label)) processDeps(label, session),
value = formatNoSci(value),
min = formatNoSci(min),
max = formatNoSci(max),
@@ -511,8 +510,8 @@ updateInputOptions <- function(session, inputId, label = NULL, choices = NULL,
}
message <- dropNulls(list(
label = processDeps(label, session),
options = options,
label = if (!is.null(label)) processDeps(label, session),
options = options,
value = selected
))
@@ -668,8 +667,8 @@ updateSelectInput <- function(session = getDefaultReactiveDomain(), inputId, lab
if (!is.null(selected)) selected <- as.character(selected)
options <- if (!is.null(choices)) selectOptions(choices, selected, inputId, FALSE)
message <- dropNulls(list(
label = processDeps(label, session),
options = options,
label = if (!is.null(label)) processDeps(label, session),
options = options,
value = selected
))
session$sendInputMessage(inputId, message)

21
R/utils-tags.R Normal file
View File

@@ -0,0 +1,21 @@
# Check if `x` is a tag(), tagList(), or HTML()
# @param strict If `FALSE`, also consider a normal list() of 'tags' to be a tag list.
isTagLike <- function(x, strict = FALSE) {
isTag(x) || isTagList(x, strict = strict) || isTRUE(attr(x, "html"))
}
isTag <- function(x) {
inherits(x, "shiny.tag")
}
isTagList <- function(x, strict = TRUE) {
if (strict) {
return(inherits(x, "shiny.tag.list"))
}
if (!is.list(x)) {
return(FALSE)
}
all(vapply(x, isTagLike, logical(1)))
}

View File

@@ -1,2 +1,2 @@
/*! shiny 1.10.0.9001 | (c) 2012-2025 Posit Software, PBC. | License: GPL-3 | file LICENSE */
/*! shiny 1.11.1.9000 | (c) 2012-2025 Posit Software, PBC. | License: GPL-3 | file LICENSE */
:where([data-shiny-busy-spinners] .recalculating){position:relative}[data-shiny-busy-spinners] .recalculating{min-height:var(--shiny-spinner-size, 32px)}[data-shiny-busy-spinners] .recalculating:after{position:absolute;content:"";--_shiny-spinner-url: var(--shiny-spinner-url, url(spinners/ring.svg));--_shiny-spinner-color: var(--shiny-spinner-color, var(--bs-primary, #007bc2));--_shiny-spinner-size: var(--shiny-spinner-size, 32px);--_shiny-spinner-delay: var(--shiny-spinner-delay, 1s);background:var(--_shiny-spinner-color);width:var(--_shiny-spinner-size);height:var(--_shiny-spinner-size);inset:calc(50% - var(--_shiny-spinner-size) / 2);mask-image:var(--_shiny-spinner-url);-webkit-mask-image:var(--_shiny-spinner-url);opacity:0;animation-delay:var(--_shiny-spinner-delay);animation-name:fade-in;animation-duration:.25s;animation-fill-mode:forwards}[data-shiny-busy-spinners] .recalculating:has(>*),[data-shiny-busy-spinners] .recalculating:empty{opacity:1}[data-shiny-busy-spinners] .recalculating>*:not(.recalculating){opacity:var(--_shiny-fade-opacity);transition:opacity .25s ease var(--shiny-spinner-delay, 1s)}[data-shiny-busy-spinners] .recalculating.html-widget-output{visibility:inherit!important}[data-shiny-busy-spinners] .recalculating.html-widget-output>*{visibility:hidden}[data-shiny-busy-spinners] .recalculating.html-widget-output :after{visibility:visible}[data-shiny-busy-spinners] .recalculating.shiny-html-output:not(.shiny-table-output):after{display:none}[data-shiny-busy-spinners][data-shiny-busy-pulse].shiny-busy:after{--_shiny-pulse-background: var( --shiny-pulse-background, linear-gradient( 120deg, transparent, var(--bs-indigo, #4b00c1), var(--bs-purple, #74149c), var(--bs-pink, #bf007f), transparent ) );--_shiny-pulse-height: var(--shiny-pulse-height, 3px);--_shiny-pulse-speed: var(--shiny-pulse-speed, 1.2s);position:fixed;top:0;left:0;height:var(--_shiny-pulse-height);background:var(--_shiny-pulse-background);z-index:9999;animation-name:busy-page-pulse;animation-duration:var(--_shiny-pulse-speed);animation-direction:alternate;animation-iteration-count:infinite;animation-timing-function:ease-in-out;content:""}[data-shiny-busy-spinners][data-shiny-busy-pulse].shiny-busy:has(.recalculating:not(.shiny-html-output)):after{display:none}[data-shiny-busy-spinners][data-shiny-busy-pulse].shiny-busy:has(.recalculating.shiny-table-output):after{display:none}[data-shiny-busy-spinners][data-shiny-busy-pulse].shiny-busy:has(#shiny-disconnected-overlay):after{display:none}[data-shiny-busy-pulse]:not([data-shiny-busy-spinners]).shiny-busy:after{--_shiny-pulse-background: var( --shiny-pulse-background, linear-gradient( 120deg, transparent, var(--bs-indigo, #4b00c1), var(--bs-purple, #74149c), var(--bs-pink, #bf007f), transparent ) );--_shiny-pulse-height: var(--shiny-pulse-height, 3px);--_shiny-pulse-speed: var(--shiny-pulse-speed, 1.2s);position:fixed;top:0;left:0;height:var(--_shiny-pulse-height);background:var(--_shiny-pulse-background);z-index:9999;animation-name:busy-page-pulse;animation-duration:var(--_shiny-pulse-speed);animation-direction:alternate;animation-iteration-count:infinite;animation-timing-function:ease-in-out;content:""}[data-shiny-busy-pulse]:not([data-shiny-busy-spinners]).shiny-busy:has(#shiny-disconnected-overlay):after{display:none}@keyframes fade-in{0%{opacity:0}to{opacity:1}}@keyframes busy-page-pulse{0%{left:-14%;right:97%}45%{left:0%;right:14%}55%{left:14%;right:0%}to{left:97%;right:-14%}}.shiny-spinner-output-container{--shiny-spinner-size: 0px}

View File

@@ -1,3 +1,3 @@
/*! shiny 1.10.0.9001 | (c) 2012-2025 Posit Software, PBC. | License: GPL-3 | file LICENSE */
/*! shiny 1.11.1.9000 | (c) 2012-2025 Posit Software, PBC. | License: GPL-3 | file LICENSE */
"use strict";(()=>{document.documentElement.classList.add("autoreload-enabled");var c=window.location.protocol==="https:"?"wss:":"ws:",s=window.location.pathname.replace(/\/?$/,"/")+"autoreload/",i=`${c}//${window.location.host}${s}`,l=document.currentScript?.dataset?.wsUrl||i;async function u(o){let e=new WebSocket(o),n=!1;return new Promise((a,r)=>{e.onopen=()=>{n=!0},e.onerror=t=>{r(t)},e.onclose=()=>{n?a(!1):r(new Error("WebSocket connection failed"))},e.onmessage=function(t){t.data==="autoreload"&&a(!0)}})}async function d(o){return new Promise(e=>setTimeout(e,o))}async function w(){for(;;){try{if(await u(l)){window.location.reload();return}}catch{console.debug("Giving up on autoreload");return}await d(1e3)}}w().catch(o=>{console.error(o)});})();
//# sourceMappingURL=shiny-autoreload.js.map

View File

@@ -1,2 +1,2 @@
/*! shiny 1.10.0.9001 | (c) 2012-2025 Posit Software, PBC. | License: GPL-3 | file LICENSE */
/*! shiny 1.11.1.9000 | (c) 2012-2025 Posit Software, PBC. | License: GPL-3 | file LICENSE */
#showcase-well{border-radius:0}.shiny-code{background-color:#fff;margin-bottom:0}.shiny-code code{font-family:Menlo,Consolas,Courier New,monospace}.shiny-code-container{margin-top:20px;clear:both}.shiny-code-container h3{display:inline;margin-right:15px}.showcase-header{font-size:16px;font-weight:400}.showcase-code-link{text-align:right;padding:15px}#showcase-app-container{vertical-align:top}#showcase-code-tabs{margin-right:15px}#showcase-code-tabs pre{border:none;line-height:1em}#showcase-code-tabs .nav,#showcase-code-tabs ul{margin-bottom:0}#showcase-code-tabs .tab-content{border-style:solid;border-color:#e5e5e5;border-width:0px 1px 1px 1px;overflow:auto;border-bottom-right-radius:4px;border-bottom-left-radius:4px}#showcase-app-code{width:100%}#showcase-code-position-toggle{float:right}#showcase-sxs-code{padding-top:20px;vertical-align:top}.showcase-code-license{display:block;text-align:right}#showcase-code-content pre{background-color:#fff}

View File

@@ -1,3 +1,3 @@
/*! shiny 1.10.0.9001 | (c) 2012-2025 Posit Software, PBC. | License: GPL-3 | file LICENSE */
/*! shiny 1.11.1.9000 | (c) 2012-2025 Posit Software, PBC. | License: GPL-3 | file LICENSE */
"use strict";(()=>{var f=400;function c(e,i){let t=0;if(e.nodeType===3){let n=e.nodeValue.replace(/\n/g,"").length;if(n>=i)return{element:e,offset:i};t+=n}else if(e.nodeType===1&&e.firstChild){let n=c(e.firstChild,i);if(n.element!==null)return n;t+=n.offset}return e.nextSibling?c(e.nextSibling,i-t):{element:null,offset:t}}function r(e,i,t){let n=0;for(let s=0;s<e.childNodes.length;s++){let l=e.childNodes[s];if(l.nodeType===3){let o=/\n/g,d;for(;(d=o.exec(l.nodeValue))!==null;)if(n++,n===i)return c(l,d.index+t+1)}else if(l.nodeType===1){let o=r(l,i-n,t);if(o.element!==null)return o;n+=o.offset}}return{element:null,offset:n}}function w(e,i){if(!document.createRange)return;let t=document.getElementById("srcref_"+e);if(!t){t=document.createElement("span"),t.id="srcref_"+e;let n=e,s=document.getElementById(i.replace(/\./g,"_")+"_code");if(!s)return;let l=r(s,n[0],n[4]),o=r(s,n[2],n[5]);if(l.element===null||o.element===null)return;let d=document.createRange();l.element.parentNode.nodeName==="SPAN"&&l.element!==o.element?d.setStartBefore(l.element.parentNode):d.setStart(l.element,l.offset),o.element.parentNode.nodeName==="SPAN"&&l.element!==o.element?d.setEndAfter(o.element.parentNode):d.setEnd(o.element,o.offset),d.surroundContents(t)}$(t).stop(!0,!0).effect("highlight",null,1600)}Shiny&&Shiny.addCustomMessageHandler("showcase-src",function(e){e.srcref&&e.srcfile&&w(e.srcref,e.srcfile)});var a=!1,m=function(e,i){let t=i?f:1,n=e?document.getElementById("showcase-sxs-code"):document.getElementById("showcase-code-inline"),s=e?document.getElementById("showcase-code-inline"):document.getElementById("showcase-sxs-code");if(document.getElementById("showcase-app-metadata")===null){let o=$("#showcase-well");e?o.fadeOut(t):o.fadeIn(t)}$(n).hide(),$(s).fadeOut(t,function(){let o=document.getElementById("showcase-code-tabs");s.removeChild(o),n.appendChild(o),e?h():document.getElementById("showcase-code-content").removeAttribute("style"),$(n).fadeIn(t),e||(document.getElementById("showcase-app-container").removeAttribute("style"),i&&$(document.body).animate({scrollTop:$(n).offset().top}));let d=document.getElementById("readme-md");d!==null&&(d.parentElement.removeChild(d),e?(s.appendChild(d),$(s).fadeIn(t)):document.getElementById("showcase-app-metadata").appendChild(d)),document.getElementById("showcase-code-position-toggle").innerHTML=e?'<i class="fa fa-level-down"></i> show below':'<i class="fa fa-level-up"></i> show with app'}),e&&$(document.body).animate({scrollTop:0},t),a=e,u(e&&i),$(window).trigger("resize")};function u(e){let t=960,n=1,s=document.getElementById("showcase-app-code").offsetWidth;s/2>960?t=s/2:s*.66>960?t=960:(t=s*.66,n=t/960);let l=document.getElementById("showcase-app-container");$(l).animate({width:t+"px",zoom:n*100+"%"},e?f:0)}var g=function(){m(!a,!0)},p=function(){document.body.offsetWidth>1350&&m(!0,!1)};function h(){document.getElementById("showcase-code-content").style.height=$(window).height()+"px"}function y(){let e=document.getElementById("showcase-markdown-content");if(e!==null){let i=e.innerText||e.innerHTML,t=window.Showdown.converter;document.getElementById("readme-md").innerHTML=new t().makeHtml(i)}}$(window).resize(function(){a&&(u(!1),h())});window.toggleCodePosition=g;$(window).on("load",p);$(window).on("load",y);window.hljs&&window.hljs.initHighlightingOnLoad();})();
//# sourceMappingURL=shiny-showcase.js.map

View File

@@ -1,3 +1,3 @@
/*! shiny 1.10.0.9001 | (c) 2012-2025 Posit Software, PBC. | License: GPL-3 | file LICENSE */
/*! shiny 1.11.1.9000 | (c) 2012-2025 Posit Software, PBC. | License: GPL-3 | file LICENSE */
"use strict";(()=>{var t=eval;window.addEventListener("message",function(a){let e=a.data;e.code&&t(e.code)});})();
//# sourceMappingURL=shiny-testmode.js.map

View File

@@ -1,4 +1,4 @@
/*! shiny 1.10.0.9001 | (c) 2012-2025 Posit Software, PBC. | License: GPL-3 | file LICENSE */
/*! shiny 1.11.1.9000 | (c) 2012-2025 Posit Software, PBC. | License: GPL-3 | file LICENSE */
"use strict";
(() => {
var __create = Object.create;
@@ -1165,30 +1165,34 @@
getState(el) {
return { value: this.getValue(el) };
}
receiveMessage(el, data) {
const $el = (0, import_jquery7.default)(el);
if (hasDefinedProperty(data, "label") || hasDefinedProperty(data, "icon")) {
let label = $el.text();
let icon = "";
if ($el.find("i[class]").length > 0) {
const iconHtml = $el.find("i[class]")[0];
if (iconHtml === $el.children()[0]) {
icon = (0, import_jquery7.default)(iconHtml).prop("outerHTML");
}
async receiveMessage(el, data) {
if (hasDefinedProperty(data, "icon")) {
let iconContainer = el.querySelector(
":scope > .action-icon"
);
if (!iconContainer) {
iconContainer = document.createElement("span");
iconContainer.className = "action-icon";
el.prepend(iconContainer);
}
if (hasDefinedProperty(data, "label")) {
label = data.label;
await renderContent(iconContainer, data.icon);
}
if (hasDefinedProperty(data, "label")) {
let labelContainer = el.querySelector(
":scope > .action-label"
);
if (!labelContainer) {
labelContainer = document.createElement("span");
labelContainer.className = "action-label";
el.appendChild(labelContainer);
}
if (hasDefinedProperty(data, "icon")) {
icon = Array.isArray(data.icon) ? "" : data.icon ?? "";
}
$el.html(icon + " " + label);
await renderContent(labelContainer, data.label);
}
if (hasDefinedProperty(data, "disabled")) {
if (data.disabled) {
$el.attr("disabled", "");
el.setAttribute("disabled", "");
} else {
$el.attr("disabled", null);
el.removeAttribute("disabled");
}
}
}
@@ -1226,12 +1230,13 @@
value: el.checked
};
}
receiveMessage(el, data) {
async receiveMessage(el, data) {
if (hasDefinedProperty(data, "value")) {
el.checked = data.value;
}
if (hasDefinedProperty(data, "label")) {
(0, import_jquery8.default)(el).parent().find("span").text(data.label);
const labelSpan = (0, import_jquery8.default)(el).parent().find("span");
await renderContent(labelSpan, data.label);
}
(0, import_jquery8.default)(el).trigger("change");
}
@@ -5611,21 +5616,29 @@
function isJQuery(value) {
return Boolean(value && value.jquery);
}
function valueChangeCallback(inputs, binding, el, allowDeferred) {
function valueChangeCallback(inputs, binding, el, priority) {
let id = binding.getId(el);
if (id) {
const value = binding.getValue(el);
const type = binding.getType(el);
if (type)
id = id + ":" + type;
const opts = {
priority: allowDeferred ? "deferred" : "immediate",
binding,
el
};
inputs.setInput(id, value, opts);
const normalizedPriority = normalizeEventPriority(priority);
inputs.setInput(id, value, { priority: normalizedPriority, binding, el });
}
}
function normalizeEventPriority(priority) {
if (priority === false || priority === void 0) {
return "immediate";
}
if (priority === true) {
return "deferred";
}
if (typeof priority === "object" && "priority" in priority) {
return priority.priority;
}
return priority;
}
var bindingsRegistry = (() => {
const bindings = /* @__PURE__ */ new Map();
function checkValidity(scope) {
@@ -5738,8 +5751,8 @@ ${duplicateIdMsg}`;
const thisCallback = function() {
const thisBinding = binding;
const thisEl = el;
return function(allowDeferred) {
valueChangeCallback(inputs, thisBinding, thisEl, allowDeferred);
return function(priority) {
valueChangeCallback(inputs, thisBinding, thisEl, priority);
};
}();
binding.subscribe(el, thisCallback);
@@ -6420,10 +6433,10 @@ ${duplicateIdMsg}`;
};
this._init();
}
connect(initialInput) {
async connect(initialInput) {
if (this.$socket)
throw "Connect was already called on this application object";
this.$socket = this.createSocket();
this.$socket = await this.createSocket();
this.$initialInput = initialInput;
import_jquery38.default.extend(this.$inputValues, initialInput);
this.$updateConditionals();
@@ -6431,16 +6444,16 @@ ${duplicateIdMsg}`;
isConnected() {
return !!this.$socket;
}
reconnect() {
async reconnect() {
clearTimeout(this.scheduledReconnect);
if (this.isConnected())
throw "Attempted to reconnect, but already connected.";
this.$socket = this.createSocket();
this.$socket = await this.createSocket();
this.$initialInput = import_jquery38.default.extend({}, this.$inputValues);
this.$updateConditionals();
}
createSocket() {
const createSocketFunc = getShinyCreateWebsocket() || (() => {
async createSocket() {
const createSocketFunc = getShinyCreateWebsocket() || (async () => {
let protocol = "ws:";
if (window.location.protocol === "https:")
protocol = "wss:";
@@ -6454,13 +6467,17 @@ ${duplicateIdMsg}`;
if (!/\/$/.test(defaultPath))
defaultPath += "/";
defaultPath += "websocket/";
const sessionId = await this.$getSessionId();
if (sessionId) {
defaultPath += "?session_id=" + sessionId;
}
const ws = new WebSocket(
protocol + "//" + window.location.host + defaultPath
);
ws.binaryType = "arraybuffer";
return ws;
});
const socket = createSocketFunc();
const socket = await createSocketFunc();
let hasOpened = false;
socket.onopen = () => {
hasOpened = true;
@@ -6498,6 +6515,26 @@ ${duplicateIdMsg}`;
};
return socket;
}
async $getSessionId() {
if (this.config) {
return this.config.sessionId;
}
try {
const response = await fetch("/new_shiny_session/");
if (response.ok) {
const data = await response.json();
return data.session_id;
}
if (response.status === 404) {
return null;
}
console.error("Error fetching session ID:", response);
return null;
} catch (error) {
console.error("Error fetching session ID:", error);
return null;
}
}
async startActionQueueLoop() {
while (true) {
try {
@@ -6527,8 +6564,8 @@ ${duplicateIdMsg}`;
this.$socket = null;
}
$scheduleReconnect(delay) {
this.scheduledReconnect = window.setTimeout(() => {
this.reconnect();
this.scheduledReconnect = window.setTimeout(async () => {
await this.reconnect();
}, delay);
}
onDisconnected(reloading = false) {
@@ -7188,7 +7225,7 @@ ${duplicateIdMsg}`;
// srcts/src/shiny/index.ts
var ShinyClass = class {
constructor() {
this.version = "1.10.0.9001";
this.version = "1.11.1.9000";
const { inputBindings, fileInputBinding: fileInputBinding2 } = initInputBindings();
const { outputBindings } = initOutputBindings();
setFileInputBinding(fileInputBinding2);
@@ -7566,7 +7603,7 @@ ${duplicateIdMsg}`;
}
});
inputsNoResend.reset(initialValues);
shinyapp.connect(initialValues);
await shinyapp.connect(initialValues);
(0, import_jquery39.default)(document).one("shiny:connected", () => {
initDeferredIframes();
});

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

@@ -463,6 +463,13 @@ textarea.textarea-autoresize.form-control {
}
}
// Add spacing between icon and label for actionButton()
.action-button:not(.action-link) {
.action-icon + .action-label {
padding-left: 0.5ch;
}
}
/* Overrides bootstrap-datepicker3.css styling for invalid date ranges.
See https://github.com/rstudio/shiny/issues/2042 for details. */
.datepicker table tbody tr td.disabled,

View File

@@ -20,14 +20,15 @@ includes extensive annotated examples.
\link{shiny-options} for documentation about global options.
}
\author{
\strong{Maintainer}: Winston Chang \email{winston@posit.co} (\href{https://orcid.org/0000-0002-1576-2126}{ORCID})
\strong{Maintainer}: Carson Sievert \email{carson@posit.co} (\href{https://orcid.org/0000-0002-4958-2844}{ORCID})
Authors:
\itemize{
\item Winston Chang \email{winston@posit.co} (\href{https://orcid.org/0000-0002-1576-2126}{ORCID})
\item Joe Cheng \email{joe@posit.co}
\item JJ Allaire \email{jj@posit.co}
\item Carson Sievert \email{carson@posit.co} (\href{https://orcid.org/0000-0002-4958-2844}{ORCID})
\item Barret Schloerke \email{barret@posit.co} (\href{https://orcid.org/0000-0001-9986-114X}{ORCID})
\item Garrick Aden-Buie \email{garrick@adenbuie.com} (\href{https://orcid.org/0000-0002-7111-0077}{ORCID})
\item Yihui Xie \email{yihui@posit.co}
\item Jeff Allen
\item Jonathan McPherson \email{jonathan@posit.co}
@@ -37,7 +38,7 @@ Authors:
Other contributors:
\itemize{
\item Posit Software, PBC [copyright holder, funder]
\item Posit Software, PBC (03wc8by49) [copyright holder, funder]
\item jQuery Foundation (jQuery library and jQuery UI library) [copyright holder]
\item jQuery contributors (jQuery library; authors listed in inst/www/shared/jquery-AUTHORS.txt) [contributor, copyright holder]
\item jQuery UI contributors (jQuery UI library; authors listed in inst/www/shared/jqueryui/AUTHORS.txt) [contributor, copyright holder]

View File

@@ -72,7 +72,7 @@ be done 100\% correctly.}
\code{\link[=runApp]{runApp()}} for more information.}
\item{shiny.jquery.version (defaults to \code{3})}{The major version of jQuery to use.
Currently only values of \code{3} or \code{1} are supported. If \code{1}, then jQuery 1.12.4 is used. If \code{3},
then jQuery 3.6.0 is used.}
then jQuery 3.7.1 is used.}
\item{shiny.json.digits (defaults to \code{I(16)})}{Max number of digits to use when converting
numbers to JSON format to send to the client web browser. Use \code{\link[=I]{I()}} to specify significant digits.
Use \code{NA} for max precision.}

View File

@@ -3,7 +3,7 @@
"homepage": "https://shiny.rstudio.com",
"repository": "github:rstudio/shiny",
"name": "@types/rstudio-shiny",
"version": "1.10.0-alpha.9001",
"version": "1.11.1-alpha.9000",
"license": "GPL-3.0-only",
"main": "",
"browser": "",

View File

@@ -1,34 +1,28 @@
# Revdeps
## Failed to check (20)
|package |version |error |warning |note |
|:------------------|:-------|:-----|:-------|:----|
|animalEKF |1.2 |1 | | |
|AovBay |0.1.0 |1 | | |
|Certara.VPCResults |3.0.2 |1 | | |
|chipPCR |1.0-2 |1 | | |
|ctsem |3.10.1 |1 | | |
|dartR.sim |? | | | |
|diveR |? | | | |
|gap |? | | | |
|jsmodule |? | | | |
|loon.shiny |? | | | |
|robmedExtra |0.1.1 |1 | | |
|rstanarm |2.32.1 |1 | | |
|SensMap |0.7 |1 | | |
|Seurat |5.1.0 |1 | |1 |
|shinyTempSignal |0.0.8 |1 | | |
|Signac |1.14.0 |1 | | |
|statsr |0.3.0 |1 | | |
|TestAnaAPP |1.1.2 |1 | | |
|tidyvpc |1.5.2 |1 | | |
|visR |? | | | |
## New problems (2)
|package |version |error |warning |note |
|:-------|:-------|:-----|:-------|:------|
|[HH](problems.md#hh)|3.1-52 | | |__+1__ |
|[PopED](problems.md#poped)|0.7.0 | | |__+1__ |
## Failed to check (22)
|package |version |error |warning |note |
|:--------------------|:-------|:-----|:-------|:----|
|bigPint |? | | | |
|bioCancer |? | | | |
|EBImage |? | | | |
|FAfA |0.3 |1 | | |
|fio |0.1.6 |1 | | |
|GeneNetworkBuilder |? | | | |
|gradientPickerD3 |? | | | |
|InterCellar |? | | | |
|LACE |? | | | |
|lavaan.shiny |1.2 |1 | | |
|LDABiplots |? | | | |
|LDAShiny |? | | | |
|loon.shiny |? | | | |
|MatrixQCvis |? | | | |
|metricsgraphics |? | | | |
|modchart |? | | | |
|omicsViewer |? | | | |
|RSP |0.4 |1 | | |
|rstanarm |2.32.1 |1 | | |
|sphereML |0.1.1 |1 | | |
|StatTeacherAssistant |? | | | |
|TestAnaAPP |1.1.2 |1 | | |

View File

@@ -1,39 +1,19 @@
## revdepcheck results
We checked 1278 reverse dependencies (1277 from CRAN + 1 from Bioconductor), comparing R CMD check results across CRAN and dev versions of this package.
We checked 1344 reverse dependencies (1330 from CRAN + 14 from Bioconductor), comparing R CMD check results across CRAN and dev versions of this package.
* We saw 2 new problems
* We failed to check 19 packages
* We saw 0 new problems
* We failed to check 8 packages
Issues with CRAN packages are summarised below.
### New problems
(This reports the first line of each new failure)
* HH
checking installed package size ... NOTE
* PopED
checking installed package size ... NOTE
### Failed to check
* animalEKF (NA)
* AovBay (NA)
* Certara.VPCResults (NA)
* chipPCR (NA)
* ctsem (NA)
* dartR.sim (NA)
* diveR (NA)
* gap (NA)
* jsmodule (NA)
* loon.shiny (NA)
* robmedExtra (NA)
* rstanarm (NA)
* SensMap (NA)
* Seurat (NA)
* shinyTempSignal (NA)
* Signac (NA)
* statsr (NA)
* TestAnaAPP (NA)
* tidyvpc (NA)
* FAfA (NA)
* fio (NA)
* lavaan.shiny (NA)
* loon.shiny (NA)
* RSP (NA)
* rstanarm (NA)
* sphereML (NA)
* TestAnaAPP (NA)

View File

@@ -1,49 +1 @@
# HH
<details>
* Version: 3.1-52
* GitHub: NA
* Source code: https://github.com/cran/HH
* Date/Publication: 2024-02-11 00:00:02 UTC
* Number of recursive dependencies: 165
Run `revdepcheck::cloud_details(, "HH")` for more info
</details>
## Newly broken
* checking installed package size ... NOTE
```
installed size is 5.1Mb
sub-directories of 1Mb or more:
R 1.5Mb
help 1.5Mb
```
# PopED
<details>
* Version: 0.7.0
* GitHub: https://github.com/andrewhooker/PopED
* Source code: https://github.com/cran/PopED
* Date/Publication: 2024-10-07 19:30:02 UTC
* Number of recursive dependencies: 140
Run `revdepcheck::cloud_details(, "PopED")` for more info
</details>
## Newly broken
* checking installed package size ... NOTE
```
installed size is 5.5Mb
sub-directories of 1Mb or more:
R 1.5Mb
doc 1.4Mb
test 1.1Mb
```
*Wow, no problems at all. :)*

View File

@@ -1,14 +1,22 @@
# Using Shiny TypeScript Definitions
When developing TypeScript projects that use `window.Shiny`, we recommend installing the Shiny TypeScript definitions to your package. To install the latest stable definitions, call
When developing TypeScript projects that use `window.Shiny`, we recommend installing the Shiny TypeScript definitions to your package. To install the latest stable definitions, run one of the following (depending on if you're using `npm` or `yarn`):
```bash
yarn add https://github.com/rstudio/shiny\#v1.7.0
npm install https://github.com/rstudio/shiny\#v1.10.0 --save-dev
# or
yarn add https://github.com/rstudio/shiny\#v1.10.0 --dev
```
, matching the GitHub tag to your current the Shiny CRAN release (ex: `v1.7.0`). If you are asked to select a version of `@types/jquery`, please select the closest matching version.
, matching the GitHub tag to your current the Shiny CRAN release (ex: `v1.10.0`). If you are asked to select a version of `@types/jquery`, please select the closest matching version.
This will provide a global type definition of `Shiny`, let your IDE know that `window.Shiny` is of type `Shiny`, and declare a globally available variable `Shiny` within your project. You **should not** need to import anything. Similar to `jQuery`, it should _Just Work_<sup>TM</sup>.
This will provide a global type definition of `window.Shiny`. In your code, you can access the Shiny object via `window.Shiny` or just `Shiny`. However, note that if you are using TypeScript, it will be OK with `window.Shiny` but it will flag uses of `Shiny` (without the `window.` prefix), because TypeScript won't know that it's a global variable. We consider it better practice to use `window.Shiny` instead of `Shiny`, but if you want TypeScript to know that `Shiny` is available as a global variable, you can add the following to a TypeScript file in your code base.
```ts
declare global {
const Shiny: ShinyClass;
}
```
When loading your compiled file, it should be loaded after `shiny.js` is loaded. If you are using an `htmlDependency()` to add your code to the page, your script will automatically be loaded after has been loaded.
@@ -186,7 +194,7 @@ Both JavaScript files will produce a sourcemap (`**.js.map`) that the browser wi
### Exported types
`./extras/globalShiny.ts` contains global declarations to define `window.Shiny`, a globally available `Shiny` variable, and a globally available `Shiny` type. This file is in a parallel folder to `./src` to avoid `Shiny` from being globally accessable within the source code. However, this file is the default type definition when the Type definitions are installed by external developers.
`./extras/globalShiny.ts` contains global declarations to define `window.Shiny`, a globally available `Shiny` variable, and a globally available `ShinyClass` type. This file is in a parallel folder to `./src` to avoid `Shiny` from being globally accessable within the source code. However, this file is the default type definition when the Type definitions are installed by external developers.
### GitHub Actions

View File

@@ -1,10 +1,12 @@
import $ from "jquery";
import type { HtmlDep } from "../../shiny/render";
import { renderContent } from "../../shiny/render";
import { hasDefinedProperty } from "../../utils";
import { InputBinding } from "./inputBinding";
type ActionButtonReceiveMessageData = {
label?: string;
icon?: string | [];
label?: { html: string; deps: HtmlDep[] };
icon?: { html: string; deps: HtmlDep[] };
disabled?: boolean;
};
@@ -39,45 +41,40 @@ class ActionButtonInputBinding extends InputBinding {
getState(el: HTMLElement): { value: number } {
return { value: this.getValue(el) };
}
receiveMessage(el: HTMLElement, data: ActionButtonReceiveMessageData): void {
const $el = $(el);
if (hasDefinedProperty(data, "label") || hasDefinedProperty(data, "icon")) {
// retrieve current label and icon
let label: string = $el.text();
let icon = "";
// to check (and store) the previous icon, we look for a $el child
// object that has an i tag, and some (any) class (this prevents
// italicized text - which has an i tag but, usually, no class -
// from being mistakenly selected)
if ($el.find("i[class]").length > 0) {
const iconHtml = $el.find("i[class]")[0];
if (iconHtml === $el.children()[0]) {
// another check for robustness
icon = $(iconHtml).prop("outerHTML");
}
async receiveMessage(
el: HTMLElement,
data: ActionButtonReceiveMessageData
): Promise<void> {
if (hasDefinedProperty(data, "icon")) {
let iconContainer = el.querySelector<HTMLElement>(
":scope > .action-icon"
);
// If no container exists yet, create one
if (!iconContainer) {
iconContainer = document.createElement("span");
iconContainer.className = "action-icon";
el.prepend(iconContainer);
}
await renderContent(iconContainer, data.icon);
}
// update the requested properties
if (hasDefinedProperty(data, "label")) {
label = data.label;
if (hasDefinedProperty(data, "label")) {
let labelContainer = el.querySelector<HTMLElement>(
":scope > .action-label"
);
if (!labelContainer) {
labelContainer = document.createElement("span");
labelContainer.className = "action-label";
el.appendChild(labelContainer);
}
if (hasDefinedProperty(data, "icon")) {
// `data.icon` can be an [] if user gave `character(0)`.
icon = Array.isArray(data.icon) ? "" : data.icon ?? "";
}
// produce new html
$el.html(icon + " " + label);
await renderContent(labelContainer, data.label);
}
if (hasDefinedProperty(data, "disabled")) {
if (data.disabled) {
$el.attr("disabled", "");
el.setAttribute("disabled", "");
} else {
$el.attr("disabled", null);
el.removeAttribute("disabled");
}
}
}

View File

@@ -1,11 +1,16 @@
import $ from "jquery";
import type { HtmlDep } from "../../shiny/render";
import { renderContent } from "../../shiny/render";
import { hasDefinedProperty } from "../../utils";
import { InputBinding } from "./inputBinding";
type CheckedHTMLElement = HTMLInputElement;
type CheckboxChecked = CheckedHTMLElement["checked"];
type CheckboxReceiveMessageData = { value?: CheckboxChecked; label?: string };
type CheckboxReceiveMessageData = {
value?: CheckboxChecked;
label?: { html: string; deps: HtmlDep[] };
};
class CheckboxInputBinding extends InputBinding {
find(scope: HTMLElement): JQuery<HTMLElement> {
@@ -32,10 +37,10 @@ class CheckboxInputBinding extends InputBinding {
value: el.checked,
};
}
receiveMessage(
async receiveMessage(
el: CheckedHTMLElement,
data: CheckboxReceiveMessageData
): void {
): Promise<void> {
if (hasDefinedProperty(data, "value")) {
el.checked = data.value;
}
@@ -43,7 +48,8 @@ class CheckboxInputBinding extends InputBinding {
// checkboxInput()'s label works different from other
// input labels...the label container should always exist
if (hasDefinedProperty(data, "label")) {
$(el).parent().find("span").text(data.label);
const labelSpan = $(el).parent().find("span");
await renderContent(labelSpan, data.label);
}
$(el).trigger("change");

View File

@@ -1,6 +1,22 @@
import type { EventPriority } from "../../inputPolicies/inputPolicy";
import type { RatePolicyModes } from "../../inputPolicies/inputRateDecorator";
import type { BindScope } from "../../shiny/bind";
type SubscribeEventPriority =
| EventPriority
| boolean
| { priority: EventPriority };
// Historically, the .subscribe()'s callback value only took a boolean. In this
// case:
// * false: send value immediately (i.e., priority = "immediate")
// * true: send value later (i.e., priority = "deferred")
// * The input rate policy is also consulted on whether to debounce or
// throttle
// In recent versions, the value can also be "event", meaning that the
// value should be sent immediately regardless of whether it has changed.
type InputSubscribeCallback = (value: SubscribeEventPriority) => void;
class InputBinding {
name!: string;
@@ -26,10 +42,7 @@ class InputBinding {
el; // unused var
}
// The callback method takes one argument, whose value is boolean. If true,
// allow deferred (debounce or throttle) sending depending on the value of
// getRatePolicy. If false, send value immediately. Default behavior is `false`
subscribe(el: HTMLElement, callback: (value: boolean) => void): void {
subscribe(el: HTMLElement, callback: InputSubscribeCallback): void {
// empty
el; // unused var
callback; // unused var
@@ -102,3 +115,4 @@ class InputBinding {
//// END NOTES FOR FUTURE DEV
export { InputBinding };
export type { InputSubscribeCallback, SubscribeEventPriority };

View File

@@ -1,6 +1,7 @@
import $ from "jquery";
import { Shiny } from "..";
import type { InputBinding, OutputBinding } from "../bindings";
import type { SubscribeEventPriority } from "../bindings/input/inputBinding";
import { OutputBindingAdapter } from "../bindings/outputAdapter";
import type { BindingRegistry } from "../bindings/registry";
import { ShinyClientMessageEvent } from "../components/errorConsole";
@@ -8,6 +9,7 @@ import type {
InputRateDecorator,
InputValidateDecorator,
} from "../inputPolicies";
import type { EventPriority } from "../inputPolicies/inputPolicy";
import { shinyAppBindOutput, shinyAppUnbindOutput } from "./initedMethods";
import { sendImageSizeFns } from "./sendImageSize";
@@ -27,7 +29,7 @@ function valueChangeCallback(
inputs: InputValidateDecorator,
binding: InputBinding,
el: HTMLElement,
allowDeferred: boolean
priority?: SubscribeEventPriority
) {
let id = binding.getId(el);
@@ -37,20 +39,32 @@ function valueChangeCallback(
if (type) id = id + ":" + type;
const opts: {
priority: "deferred" | "immediate";
binding: typeof binding;
el: typeof el;
} = {
priority: allowDeferred ? "deferred" : "immediate",
binding: binding,
el: el,
};
// Normalize the priority to a valid EventPriority
const normalizedPriority = normalizeEventPriority(priority);
inputs.setInput(id, value, opts);
inputs.setInput(id, value, { priority: normalizedPriority, binding, el });
}
}
// Narrow the type of the subscribe callback value to EventPriority
function normalizeEventPriority(
priority?: SubscribeEventPriority
): EventPriority {
if (priority === false || priority === undefined) {
return "immediate";
}
if (priority === true) {
return "deferred";
}
if (typeof priority === "object" && "priority" in priority) {
return priority.priority;
}
return priority;
}
/**
* Registry for input and output binding IDs. Used to check for duplicate IDs
* and to keep track of which IDs have already been added to the app. Use an
@@ -272,8 +286,8 @@ function bindInputs(
const thisBinding = binding;
const thisEl = el;
return function (allowDeferred: boolean) {
valueChangeCallback(inputs, thisBinding, thisEl, allowDeferred);
return function (priority?: SubscribeEventPriority) {
valueChangeCallback(inputs, thisBinding, thisEl, priority);
};
})();

View File

@@ -82,7 +82,7 @@ class ShinyClass {
addCustomMessageHandler: typeof addCustomMessageHandler;
// The following are added in the initialization, by initShiny()
createSocket?: () => WebSocket;
createSocket?: () => Promise<WebSocket>;
user?: string;
progressHandlers?: ShinyApp["progressHandlers"];
shinyapp?: ShinyApp;
@@ -669,7 +669,7 @@ class ShinyClass {
// We've collected all the initial values--start the server process!
inputsNoResend.reset(initialValues);
shinyapp.connect(initialValues);
await shinyapp.connect(initialValues);
$(document).one("shiny:connected", () => {
initDeferredIframes();
});

View File

@@ -92,7 +92,7 @@ function setFileInputBinding(fileInputBinding_: FileInputBinding): void {
fileInputBinding = fileInputBinding_;
}
function getShinyCreateWebsocket(): (() => WebSocket) | void {
function getShinyCreateWebsocket(): (() => Promise<WebSocket>) | void {
return validateShinyHasBeenSet().createSocket;
}

View File

@@ -159,11 +159,11 @@ class ShinyApp {
this._init();
}
connect(initialInput: InputValues): void {
async connect(initialInput: InputValues): Promise<void> {
if (this.$socket)
throw "Connect was already called on this application object";
this.$socket = this.createSocket();
this.$socket = await this.createSocket();
this.$initialInput = initialInput;
$.extend(this.$inputValues, initialInput);
@@ -176,7 +176,7 @@ class ShinyApp {
private scheduledReconnect: number | undefined = undefined;
reconnect(): void {
async reconnect(): Promise<void> {
// This function can be invoked directly even if there's a scheduled
// reconnect, so be sure to clear any such scheduled reconnects.
clearTimeout(this.scheduledReconnect);
@@ -184,15 +184,15 @@ class ShinyApp {
if (this.isConnected())
throw "Attempted to reconnect, but already connected.";
this.$socket = this.createSocket();
this.$socket = await this.createSocket();
this.$initialInput = $.extend({}, this.$inputValues);
this.$updateConditionals();
}
createSocket(): ShinyWebSocket {
const createSocketFunc: () => ShinyWebSocket =
async createSocket(): Promise<ShinyWebSocket> {
const createSocketFunc: () => Promise<ShinyWebSocket> =
getShinyCreateWebsocket() ||
(() => {
(async () => {
let protocol = "ws:";
if (window.location.protocol === "https:") protocol = "wss:";
@@ -211,6 +211,12 @@ class ShinyApp {
if (!/\/$/.test(defaultPath)) defaultPath += "/";
defaultPath += "websocket/";
const sessionId = await this.$getSessionId();
if (sessionId) {
defaultPath += "?session_id=" + sessionId;
}
// Fail gracefully if the server does not support this endpoint
const ws: ShinyWebSocket = new WebSocket(
protocol + "//" + window.location.host + defaultPath
);
@@ -220,7 +226,7 @@ class ShinyApp {
return ws;
});
const socket = createSocketFunc();
const socket = await createSocketFunc();
let hasOpened = false;
socket.onopen = () => {
@@ -277,6 +283,39 @@ class ShinyApp {
return socket;
}
async $getSessionId(): Promise<string | null> {
// If the session ID has been received through a websocket message,
// no need to create a new one.
if (this.config) {
return this.config.sessionId;
}
// Fetch this client's session ID from the server. When successful, the
// server will require a (valid) session ID when establishing a websocket
// connection.
try {
const response = await fetch("/new_shiny_session/");
if (response.ok) {
const data = await response.json();
return data.session_id;
}
// If the endpoint doesn't exist, then a session ID isn't required
// to establish a websocket connection.
if (response.status === 404) {
return null;
}
// If other problems happen, we probably want to know about it.
console.error("Error fetching session ID:", response);
return null;
} catch (error) {
console.error("Error fetching session ID:", error);
return null;
}
}
async startActionQueueLoop(): Promise<void> {
// eslint-disable-next-line no-constant-condition
while (true) {
@@ -314,8 +353,8 @@ class ShinyApp {
}
$scheduleReconnect(delay: Parameters<typeof setTimeout>[1]): void {
this.scheduledReconnect = window.setTimeout(() => {
this.reconnect();
this.scheduledReconnect = window.setTimeout(async () => {
await this.reconnect();
}, delay);
}

View File

@@ -1,7 +1,14 @@
import type { HtmlDep } from "../../shiny/render";
import { InputBinding } from "./inputBinding";
type ActionButtonReceiveMessageData = {
label?: string;
icon?: string | [];
label?: {
html: string;
deps: HtmlDep[];
};
icon?: {
html: string;
deps: HtmlDep[];
};
disabled?: boolean;
};
declare class ActionButtonInputBinding extends InputBinding {
@@ -13,7 +20,7 @@ declare class ActionButtonInputBinding extends InputBinding {
getState(el: HTMLElement): {
value: number;
};
receiveMessage(el: HTMLElement, data: ActionButtonReceiveMessageData): void;
receiveMessage(el: HTMLElement, data: ActionButtonReceiveMessageData): Promise<void>;
unsubscribe(el: HTMLElement): void;
}
export { ActionButtonInputBinding };

View File

@@ -1,9 +1,13 @@
import type { HtmlDep } from "../../shiny/render";
import { InputBinding } from "./inputBinding";
type CheckedHTMLElement = HTMLInputElement;
type CheckboxChecked = CheckedHTMLElement["checked"];
type CheckboxReceiveMessageData = {
value?: CheckboxChecked;
label?: string;
label?: {
html: string;
deps: HtmlDep[];
};
};
declare class CheckboxInputBinding extends InputBinding {
find(scope: HTMLElement): JQuery<HTMLElement>;
@@ -15,7 +19,7 @@ declare class CheckboxInputBinding extends InputBinding {
label: string;
value: CheckboxChecked;
};
receiveMessage(el: CheckedHTMLElement, data: CheckboxReceiveMessageData): void;
receiveMessage(el: CheckedHTMLElement, data: CheckboxReceiveMessageData): Promise<void>;
}
export { CheckboxInputBinding };
export type { CheckedHTMLElement, CheckboxReceiveMessageData };

View File

@@ -1,12 +1,17 @@
import type { EventPriority } from "../../inputPolicies/inputPolicy";
import type { RatePolicyModes } from "../../inputPolicies/inputRateDecorator";
import type { BindScope } from "../../shiny/bind";
type SubscribeEventPriority = EventPriority | boolean | {
priority: EventPriority;
};
type InputSubscribeCallback = (value: SubscribeEventPriority) => void;
declare class InputBinding {
name: string;
find(scope: BindScope): JQuery<HTMLElement>;
getId(el: HTMLElement): string;
getType(el: HTMLElement): string | null;
getValue(el: HTMLElement): any;
subscribe(el: HTMLElement, callback: (value: boolean) => void): void;
subscribe(el: HTMLElement, callback: InputSubscribeCallback): void;
unsubscribe(el: HTMLElement): void;
receiveMessage(el: HTMLElement, data: unknown): Promise<void> | void;
getState(el: HTMLElement): unknown;
@@ -18,3 +23,4 @@ declare class InputBinding {
dispose(el: HTMLElement): void;
}
export { InputBinding };
export type { InputSubscribeCallback, SubscribeEventPriority };

View File

@@ -35,7 +35,7 @@ declare class ShinyClass {
renderHtmlAsync: typeof renderHtmlAsync;
renderHtml: typeof renderHtml;
addCustomMessageHandler: typeof addCustomMessageHandler;
createSocket?: () => WebSocket;
createSocket?: () => Promise<WebSocket>;
user?: string;
progressHandlers?: ShinyApp["progressHandlers"];
shinyapp?: ShinyApp;

View File

@@ -19,5 +19,5 @@ declare function shinyAppUnbindOutput(id: string, binding: OutputBindingAdapter)
declare function getShinyOnCustomMessage(): Handler | null;
declare function getFileInputBinding(): FileInputBinding;
declare function setFileInputBinding(fileInputBinding_: FileInputBinding): void;
declare function getShinyCreateWebsocket(): (() => WebSocket) | void;
declare function getShinyCreateWebsocket(): (() => Promise<WebSocket>) | void;
export { setShinyObj, shinySetInputValue, shinyShinyApp, setShinyUser, shinyForgetLastInputValue, shinyBindAll, shinyUnbindAll, shinyInitializeInputs, shinyAppBindOutput, shinyAppUnbindOutput, getShinyOnCustomMessage, getFileInputBinding, setFileInputBinding, getShinyCreateWebsocket, };

View File

@@ -52,11 +52,12 @@ declare class ShinyApp {
$nextRequestId: number;
$allowReconnect: boolean | "force";
constructor();
connect(initialInput: InputValues): void;
connect(initialInput: InputValues): Promise<void>;
isConnected(): boolean;
private scheduledReconnect;
reconnect(): void;
createSocket(): ShinyWebSocket;
reconnect(): Promise<void>;
createSocket(): Promise<ShinyWebSocket>;
$getSessionId(): Promise<string | null>;
startActionQueueLoop(): Promise<void>;
sendInput(values: InputValues): void;
$notifyDisconnected(): void;

View File

@@ -0,0 +1,21 @@
# Action button allows icon customization
Code
actionButton("foo", "Click me")
Output
<button id="foo" type="button" class="btn btn-default action-button">
<span class="action-label">Click me</span>
</button>
---
Code
actionButton("foo", "Click me", icon = icon("star"))
Output
<button id="foo" type="button" class="btn btn-default action-button">
<span class="action-icon">
<i class="far fa-star" role="presentation" aria-label="star icon"></i>
</span>
<span class="action-label">Click me</span>
</button>

View File

@@ -55,3 +55,42 @@ test_that("Action link accepts class arguments", {
get_class(make_link("extra extra2")), sub("\"$", " extra extra2\"", act_class)
)
})
test_that("Action button allows icon customization", {
# No separator between icon and label
expect_snapshot(actionButton("foo", "Click me"))
# Should include separator between icon and label
expect_snapshot(
actionButton("foo", "Click me", icon = icon("star"))
)
# Warn on a non-HTML icon
expect_warning(
actionButton("foo", "Click me", icon = "not an icon"),
"non-HTML value was provided"
)
# Allows for arbitrary HTML as icon
btn <- expect_no_warning(
actionButton("foo", "Click me", icon = tags$svg())
)
btn2 <- expect_no_warning(
actionButton("foo", "Click me", icon = tagList(tags$svg()))
)
btn3 <- expect_no_warning(
actionButton("foo", "Click me", icon = list(tags$svg()))
)
btn4 <- expect_no_warning(
actionButton("foo", "Click me", icon = HTML("<svg></svg>"))
)
# Ignore newlines+indentation for comparison
as_character <- function(x) {
gsub("\\n\\s*", "", as.character(x))
}
expect_equal(as_character(btn), as_character(btn2))
expect_equal(as_character(btn2), as_character(btn3))
expect_equal(as_character(btn3), as_character(btn4))
})

View File

@@ -921,7 +921,7 @@ __metadata:
peerDependenciesMeta:
jquery-ui:
optional: true
checksum: 8718ebda1068894fc1267459b603f492045723ed1000fdbe798f2fab78fed8536b1906f56c53e9bd0ff9dce24aed176045618d0a1eddcf48f7d0313ad4ad67e9
checksum: ba260ba5804c16b1455ff79f9d00ce860e12ae36e29d7a5f702da6b384c9454497421b8e06fe683d10fac53e2dc6ec008da4fa129a153cbbfe5396e027eb4247
languageName: node
linkType: hard