Compare commits

..

3 Commits

Author SHA1 Message Date
Carson
5d563a00eb Port most everything to bslib 2025-04-25 10:19:04 -05:00
Carson
d4bf6aaab9 Restrict type='submit' behavior specifically to submitButton() 2025-04-25 10:06:12 -05:00
Carson
49c5d29003 First pass at adding textSubmitInput() 2025-04-25 10:06:12 -05:00
54 changed files with 1190 additions and 1205 deletions

View File

@@ -60,7 +60,7 @@ Authors@R: c(
comment = "showdown.js library"),
person("Ivan", "Sagalaev", role = c("ctb", "cph"),
comment = "highlight.js library"),
person(given = "R Core Team", role = c("ctb", "cph"),
person(family = "R Core Team", role = c("ctb", "cph"),
comment = "tar implementation from R")
)
Description: Makes it incredibly easy to build interactive web
@@ -107,7 +107,6 @@ Suggests:
reactlog (>= 1.0.0),
magrittr,
yaml,
mirai,
future,
dygraphs,
ragg,

15
NEWS.md
View File

@@ -4,13 +4,13 @@
* `textInput()`, `textAreaInput()`, `numericInput()` and `passwordInput()` all gain an `updateOn` option. `updateOn = "change"` is the default and previous behavior, where the input value updates immediately whenever the value changes. With `updateOn = "blur"`, the input value will update only when the text input loses focus or when the user presses Enter (or Cmd/Ctrl + Enter for `textAreaInput()`). (#4183)
* `textAreaInput()` gains a `autoresize` option, which automatically resizes the text area to fit its content. (#4210)
* `textAreaInput()` gains a `autoresize` option, which automatically resizes the text area to fit its content.
* The family of `update*Input()` functions can now render HTML content passed to the `label` argument (e.g., `updateInputText(label = tags$b("New label"))`). (#3996)
* The `callback` argument of Shiny.js' `InputBinding.subscribe()` method gains support for a value of `"event"`. This makes it possible for an input binding to use event priority when updating the value (i.e., send immediately and always resend, even if the value hasn't changed).
## Changes
* Shiny no longer suspends input changes when _any_ `<input type="submit">` or `<button type="submit">` is on the page. Instead, it now only suspends when a `submitButton()` is present. If you have reason for creating a submit button from custom HTML, add a CSS class of `shiny-submit-button` to the button. (#4209)
* Shiny no longer suspends input changes when _any_ `<input type="submit">` or `<button type="submit">` is on the page. Instead, it now only suspends when a `submitButton()` is present. If you have reason for creating a submit button from custom HTML, add a CSS class of `shiny-submit-button` to the button.
## Improvements
@@ -25,10 +25,6 @@
* Shiny's Typescript assets are now compiled to ES2021 instead of ES5. (#4066)
* `ExtendedTask` now catches synchronous values and errors and returns them via `$result()`. Previously, the extended task function was required to always return a promise. This change makes it easier to use `ExtendedTask` with a function that may return early or do some synchronous work before returning a promise. (#4225)
* `renderPlot()` was updated to accomodate changes in ggplot2 v4.0.0. (#4226)
## Bug fixes
* Fixed a bug with modals where calling `removeModal()` too quickly after `showModal()` would fail to remove the modal if the remove modal message was received while the modal was in the process of being revealed. (#4173)
@@ -37,10 +33,6 @@
* Updated the JavaScript used when inserting a tab to avoid rendering dynamic UI elements twice when adding the new tab via `insertTab()` or `bslib::nav_insert()`. (#4179)
* Fixed an issue with `ExtendedTask` where synchronous errors would cause an error that would stop the current session. (#4225)
* `shiny::shinyAppTemplate()` no longer errors without a call to `library(shiny)`. (#3870)
# shiny 1.10.0
## New features and improvements
@@ -200,6 +192,7 @@ In addition, various properties of the spinners and pulse can be customized with
* Fixed #3833: When `width` is provided to `textAreaInput()`, we now correctly set the width of the `<textarea>` element. (#3838)
# shiny 1.7.4.1
## Full changelog

View File

@@ -231,8 +231,8 @@ utils::globalVariables(".GenericCallEnv", add = TRUE)
#' promises, but rather objects provided by the
#' \href{https://rstudio.github.io/promises/}{\pkg{promises}} package, which
#' are similar to promises in JavaScript. (See [promises::promise()] for more
#' information.) You can also use [mirai::mirai()] or [future::future()]
#' objects to run code in a separate process or even on a remote machine.
#' information.) You can also use [future::future()] objects to run code in a
#' separate process or even on a remote machine.
#'
#' If the value returns a promise, then anything that consumes the cached
#' reactive must expect it to return a promise.

View File

@@ -75,18 +75,6 @@ getCallNames <- function(calls) {
})
}
# A stripped down version of getCallNames() that intentionally avoids deparsing expressions.
# Instead, it leaves expressions to be directly `rlang::hash()` (for de-duplication), which
# is much faster than deparsing then hashing.
getCallNamesForHash <- function(calls) {
lapply(calls, function(call) {
name <- call[[1L]]
if (is.function(name)) return("<Anonymous>")
if (typeof(name) == "promise") return("<Promise>")
name
})
}
getLocs <- function(calls) {
vapply(calls, function(call) {
srcref <- attr(call, "srcref", exact = TRUE)
@@ -156,7 +144,7 @@ getCallStackDigest <- function(callStack, warn = FALSE) {
)
}
rlang::hash(getCallNamesForHash(callStack))
rlang::hash(getCallNames(callStack))
}
saveCallStackDigest <- function(callStack) {

View File

@@ -41,15 +41,12 @@
#' is, a function that quickly returns a promise) and allows even that very
#' session to immediately unblock and carry on with other user interactions.
#'
#' @examplesIf rlang::is_interactive() && rlang::is_installed("mirai")
#' @examplesIf rlang::is_interactive() && rlang::is_installed("future")
#'
#' library(shiny)
#' library(bslib)
#' library(mirai)
#'
#' # Set background processes for running tasks
#' daemons(1)
#' # Reset when the app is stopped
#' onStop(function() daemons(0))
#' library(future)
#' plan(multisession)
#'
#' ui <- page_fluid(
#' titlePanel("Extended Task Demo"),
@@ -63,12 +60,13 @@
#'
#' server <- function(input, output) {
#' rand_task <- ExtendedTask$new(function() {
#' mirai(
#' future(
#' {
#' # Slow operation goes here
#' Sys.sleep(2)
#' sample(1:100, 1)
#' }
#' },
#' seed = TRUE
#' )
#' })
#'
@@ -102,12 +100,11 @@ ExtendedTask <- R6Class("ExtendedTask", portable = TRUE, cloneable = FALSE,
#' @param func The long-running operation to execute. This should be an
#' asynchronous function, meaning, it should use the
#' [\{promises\}](https://rstudio.github.io/promises/) package, most
#' likely in conjunction with the
#' [\{mirai\}](https://mirai.r-lib.org) or
#' likely in conjuction with the
#' [\{future\}](https://rstudio.github.io/promises/articles/promises_04_futures.html)
#' package. (In short, the return value of `func` should be a
#' [`mirai`][mirai::mirai()], [`Future`][future::future()], `promise`,
#' or something else that [promises::as.promise()] understands.)
#' [`Future`][future::future()] object, or a `promise`, or something else
#' that [promises::as.promise()] understands.)
#'
#' It's also important that this logic does not read from any
#' reactive inputs/sources, as inputs may change after the function is
@@ -133,15 +130,14 @@ ExtendedTask <- R6Class("ExtendedTask", portable = TRUE, cloneable = FALSE,
#' arguments.
invoke = function(...) {
args <- rlang::dots_list(..., .ignore_empty = "none")
call <- rlang::caller_call(n = 0)
if (
isolate(private$rv_status()) == "running" ||
private$invocation_queue$size() > 0
) {
private$invocation_queue$add(list(args = args, call = call))
private$invocation_queue$add(args)
} else {
private$do_invoke(args, call = call)
private$do_invoke(args)
}
invisible(NULL)
},
@@ -208,41 +204,44 @@ ExtendedTask <- R6Class("ExtendedTask", portable = TRUE, cloneable = FALSE,
rv_error = NULL,
invocation_queue = NULL,
do_invoke = function(args, call = NULL) {
do_invoke = function(args) {
private$rv_status("running")
private$rv_value(NULL)
private$rv_error(NULL)
p <- promises::promise_resolve(
maskReactiveContext(do.call(private$func, args))
)
p <- promises::then(
p,
onFulfilled = function(value, .visible) {
private$on_success(list(value = value, visible = .visible))
},
onRejected = function(error) {
private$on_error(error, call = call)
}
)
promises::finally(p, onFinally = function() {
if (private$invocation_queue$size() > 0) {
next_call <- private$invocation_queue$remove()
private$do_invoke(next_call$args, next_call$call)
}
p <- NULL
tryCatch({
maskReactiveContext({
# TODO: Bounce the do.call off of a promise_resolve(), so that the
# call to invoke() always returns immediately?
result <- do.call(private$func, args)
p <- promises::as.promise(result)
})
}, error = function(e) {
private$on_error(e)
})
promises::finally(
promises::then(p,
onFulfilled = function(value, .visible) {
private$on_success(list(value=value, visible=.visible))
},
onRejected = function(error) {
private$on_error(error)
}
),
onFinally = function() {
if (private$invocation_queue$size() > 0) {
private$do_invoke(private$invocation_queue$remove())
}
}
)
invisible(NULL)
},
on_error = function(err, call = NULL) {
cli::cli_warn(
"ERROR: An error occurred when invoking the ExtendedTask.",
parent = err,
call = call
)
on_error = function(err) {
private$rv_status("error")
private$rv_error(err)
},

View File

@@ -57,7 +57,7 @@ submitButton <- function(text = "Apply Changes", icon = NULL, width = NULL) {
div(
tags$button(
type="submit",
class="btn btn-primary shiny-submit-button",
class="btn btn-primary submit-button",
style = css(width = validateCssUnit(width)),
list(icon, text)
)

View File

@@ -66,7 +66,7 @@ textAreaInput <- function(
resize <- match.arg(resize, c("both", "none", "vertical", "horizontal"))
}
classes <- "form-control"
classes <- c("shiny-input-textarea", "form-control")
if (autoresize) {
classes <- c(classes, "textarea-autoresize")
if (is.null(rows)) {
@@ -75,7 +75,7 @@ textAreaInput <- function(
}
div(
class = "shiny-input-textarea form-group shiny-input-container",
class = "form-group shiny-input-container",
style = css(width = validateCssUnit(width)),
shinyInputLabel(inputId, label),
tags$textarea(

View File

@@ -266,8 +266,6 @@ drawPlot <- function(name, session, func, width, height, alt, pixelratio, res, .
# addition to ggplot, and there's a print method for that class, that we
# won't override that method. https://github.com/rstudio/shiny/issues/841
print.ggplot <- custom_print.ggplot
# For compatibility with ggplot2 >v4.0.0
`print.ggplot2::ggplot` <- custom_print.ggplot
# Use capture.output to squelch printing to the actual console; we
# are only interested in plot output

View File

@@ -37,11 +37,7 @@
updateTextInput <- function(session = getDefaultReactiveDomain(), inputId, label = NULL, value = NULL, placeholder = NULL) {
validate_session_object(session)
message <- dropNulls(list(
label = processDeps(label, session),
value = value,
placeholder = placeholder
))
message <- dropNulls(list(label=label, value=value, placeholder=placeholder))
session$sendInputMessage(inputId, message)
}
@@ -115,10 +111,7 @@ updateTextAreaInput <- updateTextInput
updateCheckboxInput <- function(session = getDefaultReactiveDomain(), inputId, label = NULL, value = NULL) {
validate_session_object(session)
message <- dropNulls(list(
label = processDeps(label, session),
value = value
))
message <- dropNulls(list(label=label, value=value))
session$sendInputMessage(inputId, message)
}
@@ -182,17 +175,13 @@ updateActionButton <- function(session = getDefaultReactiveDomain(), inputId, la
validate_session_object(session)
if (!is.null(icon)) icon <- as.character(validateIcon(icon))
message <- dropNulls(list(
label = processDeps(label, session),
icon = icon,
disabled = disabled
))
message <- dropNulls(list(label=label, icon=icon, disabled=disabled))
session$sendInputMessage(inputId, message)
}
#' @rdname updateActionButton
#' @export
updateActionLink <- function(session = getDefaultReactiveDomain(), inputId, label = NULL, icon = NULL) {
updateActionButton(session, inputId = inputId, label = processDeps(label, session), icon = icon)
updateActionButton(session, inputId=inputId, label=label, icon=icon)
}
@@ -236,12 +225,7 @@ updateDateInput <- function(session = getDefaultReactiveDomain(), inputId, label
min <- dateYMD(min, "min")
max <- dateYMD(max, "max")
message <- dropNulls(list(
label = processDeps(label, session),
value = value,
min = min,
max = max
))
message <- dropNulls(list(label=label, value=value, min=min, max=max))
session$sendInputMessage(inputId, message)
}
@@ -291,7 +275,7 @@ updateDateRangeInput <- function(session = getDefaultReactiveDomain(), inputId,
max <- dateYMD(max, "max")
message <- dropNulls(list(
label = processDeps(label, session),
label = label,
value = dropNulls(list(start = start, end = end)),
min = min,
max = max
@@ -390,16 +374,13 @@ updateNavlistPanel <- updateTabsetPanel
#' }
#' @export
updateNumericInput <- function(session = getDefaultReactiveDomain(), inputId, label = NULL, value = NULL,
min = NULL, max = NULL, step = NULL) {
min = NULL, max = NULL, step = NULL) {
validate_session_object(session)
message <- dropNulls(list(
label = processDeps(label, session),
value = formatNoSci(value),
min = formatNoSci(min),
max = formatNoSci(max),
step = formatNoSci(step)
label = label, value = formatNoSci(value),
min = formatNoSci(min), max = formatNoSci(max), step = formatNoSci(step)
))
session$sendInputMessage(inputId, message)
}
@@ -479,7 +460,7 @@ updateSliderInput <- function(session = getDefaultReactiveDomain(), inputId, lab
}
message <- dropNulls(list(
label = processDeps(label, session),
label = label,
value = formatNoSci(value),
min = formatNoSci(min),
max = formatNoSci(max),
@@ -510,11 +491,7 @@ updateInputOptions <- function(session, inputId, label = NULL, choices = NULL,
))
}
message <- dropNulls(list(
label = processDeps(label, session),
options = options,
value = selected
))
message <- dropNulls(list(label = label, options = options, value = selected))
session$sendInputMessage(inputId, message)
}
@@ -667,11 +644,7 @@ updateSelectInput <- function(session = getDefaultReactiveDomain(), inputId, lab
choices <- if (!is.null(choices)) choicesWithNames(choices)
if (!is.null(selected)) selected <- as.character(selected)
options <- if (!is.null(choices)) selectOptions(choices, selected, inputId, FALSE)
message <- dropNulls(list(
label = processDeps(label, session),
options = options,
value = selected
))
message <- dropNulls(list(label = label, options = options, value = selected))
session$sendInputMessage(inputId, message)
}

View File

@@ -5,7 +5,7 @@ test_that("Initial snapshot values are consistent", {
app$expect_values()
}){{
if (isTRUE(module)) {
shiny::HTML('
HTML('
test_that("Module values are consistent", {

View File

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

View File

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

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

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

@@ -384,8 +384,8 @@ html.autoreload-enabled #shiny-disconnected-overlay.reloading {
width: 100%;
}
/* Styling for textAreaInput(autoresize=TRUE) */
textarea.textarea-autoresize.form-control {
/* Styling for inputTextArea(autoresize=TRUE) */
.textarea-autoresize textarea.form-control {
padding: 5px 8px;
resize: none;
overflow-y: hidden;

View File

@@ -46,15 +46,12 @@ session to immediately unblock and carry on with other user interactions.
}
\examples{
\dontshow{if (rlang::is_interactive() && rlang::is_installed("mirai")) (if (getRversion() >= "3.4") withAutoprint else force)(\{ # examplesIf}
\dontshow{if (rlang::is_interactive() && rlang::is_installed("future")) (if (getRversion() >= "3.4") withAutoprint else force)(\{ # examplesIf}
library(shiny)
library(bslib)
library(mirai)
# Set background processes for running tasks
daemons(1)
# Reset when the app is stopped
onStop(function() daemons(0))
library(future)
plan(multisession)
ui <- page_fluid(
titlePanel("Extended Task Demo"),
@@ -68,12 +65,13 @@ ui <- page_fluid(
server <- function(input, output) {
rand_task <- ExtendedTask$new(function() {
mirai(
future(
{
# Slow operation goes here
Sys.sleep(2)
sample(1:100, 1)
}
},
seed = TRUE
)
})
@@ -123,12 +121,11 @@ server function.
\item{\code{func}}{The long-running operation to execute. This should be an
asynchronous function, meaning, it should use the
\href{https://rstudio.github.io/promises/}{\{promises\}} package, most
likely in conjunction with the
\href{https://mirai.r-lib.org}{\{mirai\}} or
likely in conjuction with the
\href{https://rstudio.github.io/promises/articles/promises_04_futures.html}{\{future\}}
package. (In short, the return value of \code{func} should be a
\code{\link[mirai:mirai]{mirai}}, \code{\link[future:future]{Future}}, \code{promise},
or something else that \code{\link[promises:is.promise]{promises::as.promise()}} understands.)
\code{\link[future:future]{Future}} object, or a \code{promise}, or something else
that \code{\link[promises:is.promise]{promises::as.promise()}} understands.)
It's also important that this logic does not read from any
reactive inputs/sources, as inputs may change after the function is

View File

@@ -234,8 +234,8 @@ With a cached reactive expression, the key and/or value expression can be
promises, but rather objects provided by the
\href{https://rstudio.github.io/promises/}{\pkg{promises}} package, which
are similar to promises in JavaScript. (See \code{\link[promises:promise]{promises::promise()}} for more
information.) You can also use \code{\link[mirai:mirai]{mirai::mirai()}} or \code{\link[future:future]{future::future()}}
objects to run code in a separate process or even on a remote machine.
information.) You can also use \code{\link[future:future]{future::future()}} objects to run code in a
separate process or even on a remote machine.
If the value returns a promise, then anything that consumes the cached
reactive must expect it to return a promise.

View File

@@ -61,7 +61,7 @@ Other contributors:
\item John Fraser (showdown.js library) [contributor, copyright holder]
\item John Gruber (showdown.js library) [contributor, copyright holder]
\item Ivan Sagalaev (highlight.js library) [contributor, copyright holder]
\item R Core Team (tar implementation from R) [contributor, copyright holder]
\item R Core Team (tar implementation from R) [contributor, copyright holder]
}
}

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

View File

@@ -0,0 +1,6 @@
.textarea-autoresize textarea.form-control {
padding: 5px 8px;
resize: none;
overflow-y: hidden;
height: auto;
}

View File

@@ -113,10 +113,10 @@ class CheckboxGroupInputBinding extends InputBinding {
options: options,
};
}
async receiveMessage(
receiveMessage(
el: CheckboxGroupHTMLElement,
data: CheckboxGroupReceiveMessageData
): Promise<void> {
): void {
const $el = $(el);
// This will replace all the options
@@ -132,7 +132,7 @@ class CheckboxGroupInputBinding extends InputBinding {
this.setValue(el, data.value);
}
await updateLabel(data.label, getLabelNode(el));
updateLabel(data.label, getLabelNode(el));
$(el).trigger("change");
}

View File

@@ -292,13 +292,10 @@ class DateInputBinding extends DateInputBindingBase {
startview: startview,
};
}
async receiveMessage(
el: HTMLElement,
data: DateReceiveMessageData
): Promise<void> {
receiveMessage(el: HTMLElement, data: DateReceiveMessageData): void {
const $input = $(el).find("input");
await updateLabel(data.label, this._getLabelNode(el));
updateLabel(data.label, this._getLabelNode(el));
if (hasDefinedProperty(data, "min")) this._setMin($input[0], data.min);

View File

@@ -106,16 +106,13 @@ class DateRangeInputBinding extends DateInputBindingBase {
startview: startview,
};
}
async receiveMessage(
el: HTMLElement,
data: DateRangeReceiveMessageData
): Promise<void> {
receiveMessage(el: HTMLElement, data: DateRangeReceiveMessageData): void {
const $el = $(el);
const $inputs = $el.find("input");
const $startinput = $inputs.eq(0);
const $endinput = $inputs.eq(1);
await updateLabel(data.label, getLabelNode(el));
updateLabel(data.label, getLabelNode(el));
if (hasDefinedProperty(data, "min")) {
this._setMin($startinput[0], data.min);

View File

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

View File

@@ -51,10 +51,7 @@ class NumberInputBinding extends TextInputBindingBase {
return "shiny.number";
el;
}
async receiveMessage(
el: NumberHTMLElement,
data: NumberReceiveMessageData
): Promise<void> {
receiveMessage(el: NumberHTMLElement, data: NumberReceiveMessageData): void {
// Setting values to `""` will remove the attribute value from the DOM element.
// The attr key will still remain, but there is not value... ex: `<input id="foo" type="number" min max/>`
if (hasDefinedProperty(data, "value")) el.value = data.value ?? "";
@@ -62,7 +59,7 @@ class NumberInputBinding extends TextInputBindingBase {
if (hasDefinedProperty(data, "max")) el.max = data.max ?? "";
if (hasDefinedProperty(data, "step")) el.step = data.step ?? "";
await updateLabel(data.label, getLabelNode(el));
updateLabel(data.label, getLabelNode(el));
$(el).trigger("change");
}

View File

@@ -103,10 +103,7 @@ class RadioInputBinding extends InputBinding {
options: options,
};
}
async receiveMessage(
el: RadioHTMLElement,
data: RadioReceiveMessageData
): Promise<void> {
receiveMessage(el: RadioHTMLElement, data: RadioReceiveMessageData): void {
const $el = $(el);
// This will replace all the options
@@ -125,7 +122,7 @@ class RadioInputBinding extends InputBinding {
this.setValue(el, data.value);
}
await updateLabel(data.label, getLabelNode(el));
updateLabel(data.label, getLabelNode(el));
$(el).trigger("change");
}

View File

@@ -102,10 +102,10 @@ class SelectInputBinding extends InputBinding {
options: options,
};
}
async receiveMessage(
receiveMessage(
el: SelectHTMLElement,
data: SelectInputReceiveMessageData
): Promise<void> {
): void {
const $el = $(el);
// This will replace all the options
@@ -205,7 +205,7 @@ class SelectInputBinding extends InputBinding {
this.setValue(el, data.value);
}
await updateLabel(data.label, getLabelNode(el));
updateLabel(data.label, getLabelNode(el));
$(el).trigger("change");
}

View File

@@ -179,10 +179,7 @@ class SliderInputBinding extends TextInputBindingBase {
unsubscribe(el: HTMLElement): void {
$(el).off(".sliderInputBinding");
}
async receiveMessage(
el: HTMLElement,
data: SliderReceiveMessageData
): Promise<void> {
receiveMessage(el: HTMLElement, data: SliderReceiveMessageData): void {
const $el = $(el);
const slider = $el.data("ionRangeSlider");
const msg: {
@@ -229,7 +226,7 @@ class SliderInputBinding extends TextInputBindingBase {
}
}
await updateLabel(data.label, getLabelNode(el));
updateLabel(data.label, getLabelNode(el));
// (maybe) update data elements
const domElements: Array<"data-type" | "time-format" | "timezone"> = [

View File

@@ -1,6 +1,7 @@
import $ from "jquery";
import { $escape, hasDefinedProperty, updateLabel } from "../../utils";
import type { EventPriority } from "../../inputPolicies/inputPolicy";
import { InputBinding } from "./inputBinding";
// interface TextHTMLElement extends NameValueHTMLElement {
@@ -49,7 +50,10 @@ class TextInputBindingBase extends InputBinding {
value;
}
subscribe(el: TextHTMLElement, callback: (x: boolean) => void): void {
subscribe(
el: TextHTMLElement,
callback: (x: EventPriority | boolean) => void
): void {
const $el = $(el);
const updateOn = $el.data("update-on") || "change";
@@ -126,13 +130,10 @@ class TextInputBinding extends TextInputBindingBase {
placeholder: el.placeholder,
};
}
async receiveMessage(
el: TextHTMLElement,
data: TextReceiveMessageData
): Promise<void> {
receiveMessage(el: TextHTMLElement, data: TextReceiveMessageData): void {
if (hasDefinedProperty(data, "value")) this.setValue(el, data.value);
await updateLabel(data.label, getLabelNode(el));
updateLabel(data.label, getLabelNode(el));
if (hasDefinedProperty(data, "placeholder"))
el.placeholder = data.placeholder;

View File

@@ -2,53 +2,96 @@ import $ from "jquery";
import { TextInputBinding } from "./text";
// When a textarea becomes visible, update the height
const intersectObserver = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
updateHeight(entry.target as HTMLInputElement);
}
});
});
class TextareaInputBinding extends TextInputBinding {
#inputHandler: EventListener | null = null;
export class TextareaInputBinding extends TextInputBinding {
find(scope: HTMLElement): JQuery<HTMLElement> {
// Inputs now also have the .shiny-input-textarea class
return $(scope).find("textarea");
}
}
initialize(el: HTMLInputElement): void {
super.initialize(el);
updateHeight(el);
/*************************************************************
* Code below this point is for textAreaInput(autoresize=TRUE)
************************************************************/
interface DOMEvent<T extends EventTarget> extends Event {
readonly target: T;
}
function onDelegatedEvent(
eventName: string,
selector: string,
callback: (target: HTMLTextAreaElement) => void
) {
document.addEventListener(eventName, (e) => {
const e2 = e as DOMEvent<HTMLTextAreaElement>;
if (e2.target.matches(selector)) {
callback(e2.target);
}
});
}
// Use a single intersectionObserver as they are slow to create / use.
let textAreaIntersectionObserver: IntersectionObserver | null = null;
function callUpdateHeightWhenTargetIsVisible(target: HTMLTextAreaElement) {
if (textAreaIntersectionObserver === null) {
// Create a single observer to watch for the textarea becoming visible
textAreaIntersectionObserver = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
// Quit if the entry is not visible
if (!entry.isIntersecting) {
return;
}
// If the entry is visible (even if it's just a single pixel)
// Stop observing the target
textAreaIntersectionObserver?.unobserve(entry.target);
// Update the height of the textarea
updateHeight(entry.target as HTMLTextAreaElement);
});
});
}
subscribe(el: HTMLInputElement, callback: (x: boolean) => void): void {
super.subscribe(el, callback);
textAreaIntersectionObserver.observe(target);
}
this.#inputHandler = (e) => updateHeight(e.target as HTMLInputElement);
el.addEventListener("input", this.#inputHandler);
intersectObserver.observe(el);
}
function updateHeight(target: HTMLTextAreaElement) {
if (target.scrollHeight > 0) {
// Automatically resize the textarea to fit its content.
target.style.height = "auto";
target.style.height = target.scrollHeight + "px";
} else {
// The textarea is not visible on the page, therefore it has a 0 scroll height.
unsubscribe(el: HTMLInputElement): void {
super.unsubscribe(el);
if (this.#inputHandler) el.removeEventListener("input", this.#inputHandler);
intersectObserver.unobserve(el);
// If we should autoresize the text area height, then we can wait for the textarea to
// become visible and call `updateHeight` again. Hopefully the scroll height is no
// longer 0
callUpdateHeightWhenTargetIsVisible(target);
}
}
function updateHeight(el: HTMLInputElement) {
if (!el.classList.contains("textarea-autoresize")) {
// Update on change
onDelegatedEvent(
"input",
"textarea.textarea-autoresize",
(target: HTMLTextAreaElement) => {
updateHeight(target);
}
);
// Update on load
function updateOnLoad() {
if (document.readyState === "loading") {
// Document still loading, wait 10ms to check again.
setTimeout(updateOnLoad, 10);
return;
}
if (el.scrollHeight == 0) {
return;
}
el.style.height = "auto";
el.style.height = el.scrollHeight + "px";
// document.readyState in ["interactive", "complete"];\
const textAreas = document.querySelectorAll(
"textarea.textarea-autoresize"
) as NodeListOf<HTMLTextAreaElement>;
textAreas.forEach(updateHeight);
}
export { TextareaInputBinding };
updateOnLoad();

View File

@@ -8,6 +8,7 @@ import type {
InputRateDecorator,
InputValidateDecorator,
} from "../inputPolicies";
import type { InputPolicyOpts } from "../inputPolicies/inputPolicy";
import { shinyAppBindOutput, shinyAppUnbindOutput } from "./initedMethods";
import { sendImageSizeFns } from "./sendImageSize";
@@ -27,7 +28,7 @@ function valueChangeCallback(
inputs: InputValidateDecorator,
binding: InputBinding,
el: HTMLElement,
allowDeferred: boolean
priority: InputPolicyOpts["priority"]
) {
let id = binding.getId(el);
@@ -37,17 +38,7 @@ function valueChangeCallback(
if (type) id = id + ":" + type;
const opts: {
priority: "deferred" | "immediate";
binding: typeof binding;
el: typeof el;
} = {
priority: allowDeferred ? "deferred" : "immediate",
binding: binding,
el: el,
};
inputs.setInput(id, value, opts);
inputs.setInput(id, value, { priority, binding, el });
}
}
@@ -272,8 +263,17 @@ function bindInputs(
const thisBinding = binding;
const thisEl = el;
return function (allowDeferred: boolean) {
valueChangeCallback(inputs, thisBinding, thisEl, allowDeferred);
// Historically speaking, this callback has only accepted a boolean value,
// but in recent versions it can also accept a input priority.
return function (priority: InputPolicyOpts["priority"] | boolean) {
const normalizedPriority =
typeof priority !== "boolean"
? priority
: priority
? "deferred"
: "immediate";
valueChangeCallback(inputs, thisBinding, thisEl, normalizedPriority);
};
})();

View File

@@ -177,11 +177,11 @@ class ShinyClass {
let target: InputPolicy;
if (document.querySelector(".shiny-submit-button")) {
if (document.querySelector(".submit-button")) {
// If there is a submit button on the page, use defer decorator
target = inputsDefer;
document.querySelectorAll(".shiny-submit-button").forEach(function (x) {
document.querySelectorAll(".submit-button").forEach(function (x) {
x.addEventListener("click", function (event) {
event.preventDefault();
inputsDefer.submit();

View File

@@ -1,6 +1,4 @@
import $ from "jquery";
import type { HtmlDep } from "../shiny/render";
import { renderContent } from "../shiny/render";
import { windowDevicePixelRatio } from "../window/pixelRatio";
import type { MapValuesUnion, MapWithResult } from "./extraTypes";
import { hasDefinedProperty, hasOwnProperty } from "./object";
@@ -353,27 +351,23 @@ const compareVersion = function (
else throw `Unknown operator: ${op}`;
};
async function updateLabel(
labelContent: string | { html: string; deps: HtmlDep[] } | undefined,
function updateLabel(
labelTxt: string | undefined,
labelNode: JQuery<HTMLElement>
): Promise<void> {
): void {
// Only update if label was specified in the update method
if (typeof labelContent === "undefined") return;
if (typeof labelTxt === "undefined") return;
if (labelNode.length !== 1) {
throw new Error("labelNode must be of length 1");
}
if (typeof labelContent === "string") {
labelContent = {
html: labelContent,
deps: [],
};
}
// Should the label be empty?
const emptyLabel = Array.isArray(labelTxt) && labelTxt.length === 0;
if (labelContent.html === "") {
if (emptyLabel) {
labelNode.addClass("shiny-label-null");
} else {
await renderContent(labelNode, labelContent);
labelNode.text(labelTxt);
labelNode.removeClass("shiny-label-null");
}
}

View File

@@ -20,7 +20,7 @@ declare class CheckboxGroupInputBinding extends InputBinding {
value: ReturnType<CheckboxGroupInputBinding["getValue"]>;
options: ValueLabelObject[];
};
receiveMessage(el: CheckboxGroupHTMLElement, data: CheckboxGroupReceiveMessageData): Promise<void>;
receiveMessage(el: CheckboxGroupHTMLElement, data: CheckboxGroupReceiveMessageData): void;
subscribe(el: CheckboxGroupHTMLElement, callback: (x: boolean) => void): void;
unsubscribe(el: CheckboxGroupHTMLElement): void;
}

View File

@@ -52,7 +52,7 @@ declare class DateInputBinding extends DateInputBindingBase {
format: string;
startview: DatepickerViewModes;
};
receiveMessage(el: HTMLElement, data: DateReceiveMessageData): Promise<void>;
receiveMessage(el: HTMLElement, data: DateReceiveMessageData): void;
}
export { DateInputBinding, DateInputBindingBase };
export type { DateReceiveMessageData };

View File

@@ -27,7 +27,7 @@ declare class DateRangeInputBinding extends DateInputBindingBase {
language: string;
startview: string;
};
receiveMessage(el: HTMLElement, data: DateRangeReceiveMessageData): Promise<void>;
receiveMessage(el: HTMLElement, data: DateRangeReceiveMessageData): void;
initialize(el: HTMLElement): void;
subscribe(el: HTMLElement, callback: (x: boolean) => void): void;
unsubscribe(el: HTMLElement): void;

View File

@@ -1,3 +1,4 @@
import type { EventPriority } from "../../inputPolicies/inputPolicy";
import type { RatePolicyModes } from "../../inputPolicies/inputRateDecorator";
import type { BindScope } from "../../shiny/bind";
declare class InputBinding {
@@ -6,7 +7,7 @@ declare class InputBinding {
getId(el: HTMLElement): string;
getType(el: HTMLElement): string | null;
getValue(el: HTMLElement): any;
subscribe(el: HTMLElement, callback: (value: boolean) => void): void;
subscribe(el: HTMLElement, callback: (value: EventPriority | boolean) => void): void;
unsubscribe(el: HTMLElement): void;
receiveMessage(el: HTMLElement, data: unknown): Promise<void> | void;
getState(el: HTMLElement): unknown;

View File

@@ -12,7 +12,7 @@ declare class NumberInputBinding extends TextInputBindingBase {
getValue(el: NumberHTMLElement): string[] | number | string | null | undefined;
setValue(el: NumberHTMLElement, value: number): void;
getType(el: NumberHTMLElement): string;
receiveMessage(el: NumberHTMLElement, data: NumberReceiveMessageData): Promise<void>;
receiveMessage(el: NumberHTMLElement, data: NumberReceiveMessageData): void;
getState(el: NumberHTMLElement): {
label: string;
value: ReturnType<NumberInputBinding["getValue"]>;

View File

@@ -18,7 +18,7 @@ declare class RadioInputBinding extends InputBinding {
value: ReturnType<RadioInputBinding["getValue"]>;
options: ValueLabelObject[];
};
receiveMessage(el: RadioHTMLElement, data: RadioReceiveMessageData): Promise<void>;
receiveMessage(el: RadioHTMLElement, data: RadioReceiveMessageData): void;
subscribe(el: RadioHTMLElement, callback: (x: boolean) => void): void;
unsubscribe(el: RadioHTMLElement): void;
}

View File

@@ -28,7 +28,7 @@ declare class SelectInputBinding extends InputBinding {
label: string;
}>;
};
receiveMessage(el: SelectHTMLElement, data: SelectInputReceiveMessageData): Promise<void>;
receiveMessage(el: SelectHTMLElement, data: SelectInputReceiveMessageData): void;
subscribe(el: SelectHTMLElement, callback: (x: boolean) => void): void;
unsubscribe(el: HTMLElement): void;
initialize(el: SelectHTMLElement): void;

View File

@@ -26,7 +26,7 @@ declare class SliderInputBinding extends TextInputBindingBase {
setValue(el: HTMLElement, value: number | string | [number | string, number | string]): void;
subscribe(el: HTMLElement, callback: (x: boolean) => void): void;
unsubscribe(el: HTMLElement): void;
receiveMessage(el: HTMLElement, data: SliderReceiveMessageData): Promise<void>;
receiveMessage(el: HTMLElement, data: SliderReceiveMessageData): void;
getRatePolicy(el: HTMLElement): {
policy: "debounce";
delay: 250;

View File

@@ -1,3 +1,4 @@
import type { EventPriority } from "../../inputPolicies/inputPolicy";
import { InputBinding } from "./inputBinding";
type TextHTMLElement = HTMLInputElement;
type TextReceiveMessageData = {
@@ -10,7 +11,7 @@ declare class TextInputBindingBase extends InputBinding {
getId(el: TextHTMLElement): string;
getValue(el: TextHTMLElement): unknown;
setValue(el: TextHTMLElement, value: unknown): void;
subscribe(el: TextHTMLElement, callback: (x: boolean) => void): void;
subscribe(el: TextHTMLElement, callback: (x: EventPriority | boolean) => void): void;
unsubscribe(el: TextHTMLElement): void;
receiveMessage(el: TextHTMLElement, data: unknown): void;
getState(el: TextHTMLElement): unknown;
@@ -27,7 +28,7 @@ declare class TextInputBinding extends TextInputBindingBase {
value: string;
placeholder: string;
};
receiveMessage(el: TextHTMLElement, data: TextReceiveMessageData): Promise<void>;
receiveMessage(el: TextHTMLElement, data: TextReceiveMessageData): void;
}
export { TextInputBinding, TextInputBindingBase };
export type { TextHTMLElement, TextReceiveMessageData };

View File

@@ -1,9 +1,4 @@
import { TextInputBinding } from "./text";
declare class TextareaInputBinding extends TextInputBinding {
#private;
export declare class TextareaInputBinding extends TextInputBinding {
find(scope: HTMLElement): JQuery<HTMLElement>;
initialize(el: HTMLInputElement): void;
subscribe(el: HTMLInputElement, callback: (x: boolean) => void): void;
unsubscribe(el: HTMLInputElement): void;
}
export { TextareaInputBinding };

View File

@@ -0,0 +1,18 @@
import type { EventPriority } from "../../inputPolicies/inputPolicy";
import { InputBinding } from "./inputBinding";
type TextHTMLElement = HTMLInputElement;
type TextSubmitReceiveMessageData = {
value?: string;
placeholder?: string;
submit?: boolean;
focus?: boolean;
};
declare class TextSubmitInputBinding extends InputBinding {
find(scope: HTMLElement): JQuery<HTMLElement>;
getValue(el: TextHTMLElement): string;
setValue(el: TextHTMLElement, value: string): void;
subscribe(el: TextHTMLElement, callback: (x: EventPriority | boolean) => void): void;
unsubscribe(el: HTMLElement): void;
receiveMessage(el: TextHTMLElement, data: TextSubmitReceiveMessageData): void;
}
export { TextSubmitInputBinding };

View File

@@ -1,4 +1,3 @@
import type { HtmlDep } from "../shiny/render";
import type { MapValuesUnion, MapWithResult } from "./extraTypes";
import { hasDefinedProperty, hasOwnProperty } from "./object";
declare function escapeHTML(str: string): string;
@@ -27,10 +26,7 @@ declare function isnan(x: unknown): boolean;
declare function _equal(x: unknown, y: unknown): boolean;
declare function equal(...args: unknown[]): boolean;
declare const compareVersion: (a: string, op: "<" | "<=" | "==" | ">" | ">=", b: string) => boolean;
declare function updateLabel(labelContent: string | {
html: string;
deps: HtmlDep[];
} | undefined, labelNode: JQuery<HTMLElement>): Promise<void>;
declare function updateLabel(labelTxt: string | undefined, labelNode: JQuery<HTMLElement>): void;
declare function getComputedLinkColor(el: HTMLElement): string;
declare function isBS3(): boolean;
declare function toLowerCase<T extends string>(str: T): Lowercase<T>;

View File

@@ -6,9 +6,7 @@ sortList <- function(x) {
}
# This will create print.ggplot in the current environment
# print.ggplot is for ggplot2 < 4.0.0
# print.ggplot2::ggplot is for ggplot2 >= 4.0.0
print.ggplot <- `print.ggplot2::ggplot` <- custom_print.ggplot
print.ggplot <- custom_print.ggplot
test_that("ggplot coordmap", {

View File

@@ -1307,7 +1307,7 @@ for (do_priming in c(TRUE, FALSE)) {
# dr should've fired, and we should have converged on the right answer.
expect_identical(dr_fired, 2)
isolate(expect_identical(rv$a, dr()))
# be sure tr() converged on the right answer; (We've already confirmed throttle-like behavior above)
expect_identical(tr_fired, 4)
isolate(expect_identical(rv$a, tr()))
})
}

View File

@@ -16,19 +16,18 @@ test_that("Radio buttons and checkboxes work with modules", {
resultA <- sessA$lastInputMessage
expect_equal(resultA$id, "test1")
expect_equal(as.character(resultA$message$label$html), "Label")
expect_equal(resultA$message$label, "Label")
expect_equal(resultA$message$value, "a")
expect_match(resultA$message$options, '"modA-test1"')
expect_no_match(resultA$message$options, '"test1"')
sessB <- createModuleSession("modB")
updateCheckboxGroupInput(sessB, "test2", label = icon("eye"), choices = LETTERS[1:5])
updateCheckboxGroupInput(sessB, "test2", label = "Label", choices = LETTERS[1:5])
resultB <- sessB$lastInputMessage
expect_equal(resultB$id, "test2")
expect_length(resultB$message$label, 2)
expect_s3_class(resultB$message$label$html, "html")
expect_equal(resultB$message$label, "Label")
expect_null(resultB$message$value)
expect_match(resultB$message$options, '"modB-test2"')
expect_no_match(resultB$message$options, '"test2"')