mirror of
https://github.com/rstudio/shiny.git
synced 2026-01-10 23:48:01 -05:00
Compare commits
95 Commits
set_app_st
...
chore/shin
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
83bee8447e | ||
|
|
41e20f79f8 | ||
|
|
081272f584 | ||
|
|
7f127ce0ae | ||
|
|
d3f8479bcd | ||
|
|
6760c31818 | ||
|
|
781ceaaa5c | ||
|
|
fff283648b | ||
|
|
f71f1256b8 | ||
|
|
f26b1335d8 | ||
|
|
370ba1f288 | ||
|
|
54988c17c8 | ||
|
|
65fe23fa02 | ||
|
|
b22b06e3d2 | ||
|
|
3677f4e1c6 | ||
|
|
d6eb0493b3 | ||
|
|
4e13cdb365 | ||
|
|
4e3710cdaa | ||
|
|
5feedaf4c8 | ||
|
|
ce29695e44 | ||
|
|
f0f06a2c34 | ||
|
|
860a3fef86 | ||
|
|
6afadade5d | ||
|
|
c1bda7fb7b | ||
|
|
509c165ee8 | ||
|
|
54e0ef7598 | ||
|
|
03f2d5f014 | ||
|
|
122c1e74cd | ||
|
|
d29f4cdf21 | ||
|
|
300fb217d1 | ||
|
|
33dc41c4bd | ||
|
|
4b6e257dfc | ||
|
|
1f23f37f89 | ||
|
|
59b1c46485 | ||
|
|
01705c1299 | ||
|
|
18955a2abf | ||
|
|
dbbe7f9679 | ||
|
|
61a51a869f | ||
|
|
298822fc44 | ||
|
|
283c71e772 | ||
|
|
b1297395a9 | ||
|
|
b850cd2509 | ||
|
|
56878ebbaa | ||
|
|
6a09fda08e | ||
|
|
77bc4e9ec7 | ||
|
|
a1b9fda809 | ||
|
|
97a12ec601 | ||
|
|
81bdde64c4 | ||
|
|
c4ef42337b | ||
|
|
ce78d0dcf1 | ||
|
|
7069064dd6 | ||
|
|
a0a83d5fe3 | ||
|
|
8fbc4ad4c1 | ||
|
|
5346a00373 | ||
|
|
2dc69aea37 | ||
|
|
be6f6716bf | ||
|
|
7f59f93692 | ||
|
|
798b336df6 | ||
|
|
bef6b4bfd9 | ||
|
|
80ab088e2d | ||
|
|
481a692b07 | ||
|
|
8ae936ba01 | ||
|
|
6dc377842f | ||
|
|
837307fe8c | ||
|
|
dfe359c1b6 | ||
|
|
b8923e9497 | ||
|
|
9ebcbf8a2d | ||
|
|
a6fc6bf8cb | ||
|
|
eddc3047d4 | ||
|
|
6db17d4f67 | ||
|
|
d21f9493fb | ||
|
|
e87f942e89 | ||
|
|
1eb9ed7345 | ||
|
|
9d923d079a | ||
|
|
b054e45402 | ||
|
|
8b1d30aefe | ||
|
|
ab87a0708d | ||
|
|
0b97ee1ecc | ||
|
|
68546c319e | ||
|
|
69188fef22 | ||
|
|
6be6dfbfeb | ||
|
|
6fc06281bd | ||
|
|
f724128d41 | ||
|
|
518ef0f9f8 | ||
|
|
f5b395485e | ||
|
|
31aca7aa70 | ||
|
|
b38a630224 | ||
|
|
1b7709411b | ||
|
|
2b48aa0d91 | ||
|
|
6fdf23752e | ||
|
|
8542f5d017 | ||
|
|
e7b830755a | ||
|
|
23c7b0683a | ||
|
|
5805895581 | ||
|
|
90539bff25 |
@@ -15,6 +15,11 @@ parser: '@typescript-eslint/parser'
|
||||
parserOptions:
|
||||
ecmaVersion: 2018
|
||||
sourceType: module
|
||||
project:
|
||||
- './tsconfig.json'
|
||||
ignorePatterns: # mirrors tsconfig.json's exclude
|
||||
- '**/__tests__'
|
||||
- '**/*.d.ts'
|
||||
plugins:
|
||||
- '@typescript-eslint'
|
||||
- prettier
|
||||
@@ -70,6 +75,10 @@ rules:
|
||||
|
||||
"@typescript-eslint/consistent-type-imports":
|
||||
- error
|
||||
|
||||
"@typescript-eslint/no-floating-promises":
|
||||
- error
|
||||
|
||||
"@typescript-eslint/naming-convention":
|
||||
- error
|
||||
|
||||
|
||||
@@ -7,3 +7,4 @@ plugins:
|
||||
spec: "@yarnpkg/plugin-interactive-tools"
|
||||
|
||||
yarnPath: .yarn/releases/yarn-3.2.3.cjs
|
||||
checksumBehavior: update
|
||||
|
||||
27
DESCRIPTION
27
DESCRIPTION
@@ -1,19 +1,19 @@
|
||||
Package: shiny
|
||||
Type: Package
|
||||
Title: Web Application Framework for R
|
||||
Version: 1.7.4.9002
|
||||
Version: 1.8.0.9000
|
||||
Authors@R: c(
|
||||
person("Winston", "Chang", role = c("aut", "cre"), email = "winston@rstudio.com", comment = c(ORCID = "0000-0002-1576-2126")),
|
||||
person("Joe", "Cheng", role = "aut", email = "joe@rstudio.com"),
|
||||
person("JJ", "Allaire", role = "aut", email = "jj@rstudio.com"),
|
||||
person("Carson", "Sievert", role = "aut", email = "carson@rstudio.com", comment = c(ORCID = "0000-0002-4958-2844")),
|
||||
person("Barret", "Schloerke", role = "aut", email = "barret@rstudio.com", comment = c(ORCID = "0000-0001-9986-114X")),
|
||||
person("Yihui", "Xie", role = "aut", email = "yihui@rstudio.com"),
|
||||
person("Jeff", "Allen", role = "aut", email = "jeff@rstudio.com"),
|
||||
person("Jonathan", "McPherson", role = "aut", email = "jonathan@rstudio.com"),
|
||||
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("Jeff", "Allen", role = "aut"),
|
||||
person("Jonathan", "McPherson", role = "aut", email = "jonathan@posit.co"),
|
||||
person("Alan", "Dipert", role = "aut"),
|
||||
person("Barbara", "Borges", role = "aut"),
|
||||
person(family = "RStudio", role = "cph"),
|
||||
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"),
|
||||
@@ -93,7 +93,6 @@ Imports:
|
||||
glue (>= 1.3.2),
|
||||
bslib (>= 0.3.0),
|
||||
cachem,
|
||||
ellipsis,
|
||||
lifecycle (>= 0.2.0)
|
||||
Suggests:
|
||||
datasets,
|
||||
@@ -111,7 +110,8 @@ Suggests:
|
||||
ragg,
|
||||
showtext,
|
||||
sass
|
||||
URL: https://shiny.rstudio.com/
|
||||
URL: https://shiny.posit.co/,
|
||||
https://github.com/rstudio/shiny
|
||||
BugReports: https://github.com/rstudio/shiny/issues
|
||||
Collate:
|
||||
'globals.R'
|
||||
@@ -131,6 +131,7 @@ Collate:
|
||||
'deprecated.R'
|
||||
'devmode.R'
|
||||
'diagnose.R'
|
||||
'extended-task.R'
|
||||
'fileupload.R'
|
||||
'graph.R'
|
||||
'reactives.R'
|
||||
@@ -202,7 +203,7 @@ Collate:
|
||||
'version_selectize.R'
|
||||
'version_strftime.R'
|
||||
'viewer.R'
|
||||
RoxygenNote: 7.2.3
|
||||
RoxygenNote: 7.3.1
|
||||
Encoding: UTF-8
|
||||
Roxygen: list(markdown = TRUE)
|
||||
RdMacros: lifecycle
|
||||
|
||||
@@ -19,6 +19,7 @@ S3method("[[",shinyoutput)
|
||||
S3method("[[<-",reactivevalues)
|
||||
S3method("[[<-",shinyoutput)
|
||||
S3method("names<-",reactivevalues)
|
||||
S3method(as.list,Map)
|
||||
S3method(as.list,reactivevalues)
|
||||
S3method(as.shiny.appobj,character)
|
||||
S3method(as.shiny.appobj,list)
|
||||
@@ -43,6 +44,7 @@ S3method(bindEvent,reactiveExpr)
|
||||
S3method(bindEvent,shiny.render.function)
|
||||
S3method(format,reactiveExpr)
|
||||
S3method(format,reactiveVal)
|
||||
S3method(length,Map)
|
||||
S3method(names,reactivevalues)
|
||||
S3method(print,reactive)
|
||||
S3method(print,reactivevalues)
|
||||
@@ -53,6 +55,7 @@ S3method(str,reactivevalues)
|
||||
export("conditionStackTrace<-")
|
||||
export(..stacktraceoff..)
|
||||
export(..stacktraceon..)
|
||||
export(ExtendedTask)
|
||||
export(HTML)
|
||||
export(MockShinySession)
|
||||
export(NS)
|
||||
@@ -332,8 +335,6 @@ import(httpuv)
|
||||
import(methods)
|
||||
import(mime)
|
||||
import(xtable)
|
||||
importFrom(ellipsis,check_dots_empty)
|
||||
importFrom(ellipsis,check_dots_unnamed)
|
||||
importFrom(fastmap,fastmap)
|
||||
importFrom(fastmap,is.key_missing)
|
||||
importFrom(fastmap,key_missing)
|
||||
@@ -391,6 +392,8 @@ importFrom(rlang,"fn_body<-")
|
||||
importFrom(rlang,"fn_fmls<-")
|
||||
importFrom(rlang,as_function)
|
||||
importFrom(rlang,as_quosure)
|
||||
importFrom(rlang,check_dots_empty)
|
||||
importFrom(rlang,check_dots_unnamed)
|
||||
importFrom(rlang,enexpr)
|
||||
importFrom(rlang,enquo)
|
||||
importFrom(rlang,enquo0)
|
||||
|
||||
83
NEWS.md
83
NEWS.md
@@ -1,21 +1,88 @@
|
||||
# shiny 1.7.4.9002
|
||||
# shiny (development version)
|
||||
|
||||
## Full changelog
|
||||
## Breaking changes
|
||||
|
||||
### Breaking changes
|
||||
* Both `conditionalPanel()` and `uiOutput()` are now styled with `display: contents` by default in Shiny apps that use Bootstrap 5. This means that the elements they contain are positioned as if they were direct children of the parent container holding the `conditionalPanel()` or `uiOutput()`. This is probably what most users intend when they use these functions, but it may break apps that applied styles directly to the container elements created by these two functions. In that case, you may include CSS rules to set `display: block` for the `.shiny-panel-conditional` or `.shiny-html-output` classes. (#3957, #3960)
|
||||
|
||||
### New features and improvements
|
||||
## New features and improvements
|
||||
|
||||
* Closed #789: Dynamic UI is now rendered asynchronously, thanks in part to the newly exported `Shiny.renderDependenciesAsync()`, `Shiny.renderHtmlAsync()`, and `Shiny.renderContentAsync()`. Importantly, this means `<script>` tags are now loaded asynchronously (the old way used `XMLHttpRequest`, which is synchronous). In addition, `Shiny` now manages a queue of async tasks (exposed via `Shiny.shinyapp.taskQueue`) so that order of execution is preserved. (#3666)
|
||||
* Added an console that shows some errors in the browser. Also provide better error messages for duplicate input and output bindings. (#3931)
|
||||
|
||||
* Added a new `ExtendedTask` abstraction, for long-running asynchronous tasks that you don't want to block the rest of the app, or even the rest of the session. Designed to be used with new `bslib::input_task_button()` and `bslib::bind_task_button()` functions that help give user feedback and prevent extra button clicks. (#3958)
|
||||
|
||||
* The `shiny.error` will now pass the error condition object to a handler function if the handler takes an `error` argument. (#3987)
|
||||
|
||||
## Bug fixes
|
||||
|
||||
* Notifications are now constrained to the width of the viewport for window widths smaller the default notification panel size. (#3949)
|
||||
|
||||
* Fixed #2392: `downloadButton()` now visibly returns its HTML tag so that it renders correctly in R Markdown and Quarto output. (Thanks to @fennovj, #2672)
|
||||
|
||||
# shiny 1.8.0
|
||||
|
||||
## Breaking changes
|
||||
|
||||
* Closed #3899: The JS function `Shiny.bindAll()` is now asynchronous. This change is driven by the recent push toward making dynamic UI rendering asynchronous, which is necessary for [shinylive](https://shinylive.io/r) (and should've happened when it was first introduced in Shiny v1.7.5). The vast majority of existing `Shiny.bindAll()` uses should continue to work as before, but some cases may break if downstream code relies on it being synchronous (i.e., blocking the main thread). In this case, consider placing any downstream code in a `.then()` callback (or `await` the result in a `async` function). (#3929)
|
||||
* Since `renderContent()` calls `bindAll()` (after it inserts content), it now returns a `Promise<void>` instead of `void`, which can be useful if downstream code needs to wait for the binding to complete.
|
||||
|
||||
## New features and improvements
|
||||
|
||||
* Updated `selectizeInput()`'s selectize.js dependency from v0.12.4 to v0.15.2. In addition to many bug fixes and improvements, this update also adds several new [plugin options](https://selectize.dev/docs/demos/plugins). (#3875)
|
||||
|
||||
* Shiny's CSS styling (for things like `showNotification()`, `withProgress()`, `inputPanel()`, etc.), has been updated with `{bslib}`'s upcoming CSS-only dark mode feature in mind. (#3882, #3914)
|
||||
|
||||
* Default styles for `showNotification()` were tweaked slightly to improve accessibility, sizing, and padding. (#3913)
|
||||
|
||||
* Shiny inputs and `{htmlwidgets}` are no longer treated as draggable inside of `absolutePanel()`/`fixedPanel()` with `draggable = TRUE`. As a result, interactions like zooming and panning now work as expected with widgets like `{plotly}` and `{leaflet}` when they appear in a draggable panel. (#3752, #3933)
|
||||
|
||||
* For `InputBinding`s, the `.receiveMessage()` method can now be asynchronous or synchronous (previously it could only be synchronous). (#3930)
|
||||
|
||||
## Bug fixes
|
||||
|
||||
* `fileInput()` no longer has unwanted round corners applied to the `buttonLabel`. (#3879)
|
||||
|
||||
* Fixed #3898: `wrapFunctionLabel()` no longer throws an error if the `name` is longer than 10000 bytes. (#3903)
|
||||
|
||||
# shiny 1.7.5.1
|
||||
|
||||
## Bug fixes
|
||||
|
||||
* On r-devel (R > 4.3.1), `isTruthy(NULL)` now returns `FALSE` (as it does with older versions of R). (#3906)
|
||||
|
||||
# shiny 1.7.5
|
||||
|
||||
## Possibly breaking changes
|
||||
|
||||
* For `reactiveValues()` objects, whenever the `$names()` or `$values()` methods are called, the keys are now returned in the order that they were inserted. (#3774)
|
||||
|
||||
* The value provided to `options(shiny.json.digits)` is now interpreted as number of _digits after the decimal_ instead of _significant digits_. To treat the value as significant digits, wrap it in `I()` (e.g., `options(shiny.json.digits = I(4))`). This new default behavior not only helps with reducing digits in testing snapshots, but is also more consistent with `{jsonlite}`'s default behavior. (#3819)
|
||||
|
||||
## New features and improvements
|
||||
|
||||
* Closed #789: Dynamic UI is now rendered asynchronously, thanks in part to the newly exported `Shiny.renderDependenciesAsync()`, `Shiny.renderHtmlAsync()`, and `Shiny.renderContentAsync()`. Importantly, this means `<script>` tags are now loaded asynchronously (the old way used `XMLHttpRequest`, which is synchronous). In addition, `Shiny` now manages a queue of async tasks (exposed via `Shiny.shinyapp.taskQueue`) so that order of execution is preserved. (#3666)
|
||||
|
||||
* Fixes #3840: `updateSliderInput()` now warns when attempting to set invalid `min`, `max`, or `value` values. Sending an invalid update message to an input no longer causes other update messages to fail. (#3843)
|
||||
|
||||
* `sliderInput()` now has a larger target area for clicking or tapping on the slider handle or range. (#3859)
|
||||
|
||||
* Closed #2956: Component authors can now prevent Shiny from creating an input binding on specific elements by adding the `data-shiny-no-bind-input` attribute to the element. The attribute may have any or no value; its presence will prevent binding. This feature is primarily useful for input component authors who want to use standard HTML input elements without causing Shiny to create an input binding for them. Additionally, Shiny now adds custom classes to its inputs. For example, `checkboxInput()` now has a `shiny-input-checkbox` class. These custom classes may be utilized in future updates to Shiny's input binding logic. (#3861)
|
||||
|
||||
* `Map` objects are now initialized at load time instead of build time. This avoids potential problems that could arise from storing `fastmap` objects into the built Shiny package. (#3775)
|
||||
|
||||
### Bug fixes
|
||||
## Bug fixes
|
||||
|
||||
* Fixed #3771: Sometimes the error `ion.rangeSlider.min.js: i.stopPropagation is not a function` would appear in the JavaScript console. (#3772)
|
||||
|
||||
* Fixed #3833: When `width` is provided to `textAreaInput()`, we now correctly set the width of the `<textarea>` element. (#3838)
|
||||
|
||||
|
||||
# shiny 1.7.4.1
|
||||
|
||||
## Full changelog
|
||||
|
||||
* Closed #3849: In R-devel, a warning was raised when Shiny was loaded because `as.numeric_version()` was called with a number instead of a string. (#3850)
|
||||
|
||||
|
||||
# shiny 1.7.4
|
||||
|
||||
## Full changelog
|
||||
@@ -165,7 +232,7 @@ This release focuses on improvements in three main areas:
|
||||
|
||||
1. Better theming (and Bootstrap 4) support:
|
||||
* The `theme` argument of `fluidPage()`, `navbarPage()`, and `bootstrapPage()` all now understand `bslib::bs_theme()` objects, which can be used to opt-into Bootstrap 4, use any Bootswatch theme, and/or implement custom themes without writing any CSS.
|
||||
* The `session` object now includes `$setCurrentTheme()` and `$getCurrentTheme()` methods to dynamically update (or obtain) the page's `theme` after initial load, which is useful for things such as [adding a dark mode switch to an app](https://rstudio.github.io/bslib/articles/bslib.html#dynamic) or some other "real-time" theming tool like `bslib::bs_themer()`.
|
||||
* The `session` object now includes `$setCurrentTheme()` and `$getCurrentTheme()` methods to dynamically update (or obtain) the page's `theme` after initial load, which is useful for things such as [adding a dark mode switch to an app](https://rstudio.github.io/bslib/articles/theming.html#dynamic) or some other "real-time" theming tool like `bslib::bs_themer()`.
|
||||
* For more details, see [`{bslib}`'s website](https://rstudio.github.io/bslib/)
|
||||
|
||||
2. Caching of `reactive()` and `render*()` (e.g. `renderText()`, `renderTable()`, etc) expressions.
|
||||
@@ -558,7 +625,7 @@ This is a significant release for Shiny, with a major new feature that was nearl
|
||||
|
||||
* Fixed #1600: URL-encoded bookmarking did not work with sliders that had dates or date-times. (#1961)
|
||||
|
||||
* Fixed #1962: [File dragging and dropping](https://www.rstudio.com/blog/shiny-1-0-4/) broke in the presence of jQuery version 3.0 as introduced by the [rhandsontable](https://jrowen.github.io/rhandsontable/) [htmlwidget](https://www.htmlwidgets.org/). (#2005)
|
||||
* Fixed #1962: [File dragging and dropping](https://posit.co/blog/shiny-1-0-4/) broke in the presence of jQuery version 3.0 as introduced by the [rhandsontable](https://jrowen.github.io/rhandsontable/) [htmlwidget](https://www.htmlwidgets.org/). (#2005)
|
||||
|
||||
* Improved the error handling inside the `addResourcePath()` function, to give end users more informative error messages when the `directoryPath` argument cannot be normalized. This is especially useful for `runtime: shiny_prerendered` Rmd documents, like `learnr` tutorials. (#1968)
|
||||
|
||||
|
||||
@@ -159,8 +159,8 @@ utils::globalVariables(".GenericCallEnv", add = TRUE)
|
||||
#' ```
|
||||
#'
|
||||
#' To use different settings for a session-scoped cache, you can set
|
||||
#' `self$cache` at the top of your server function. By default, it will create
|
||||
#' a 200 MB memory cache for each session, but you can replace it with
|
||||
#' `session$cache` at the top of your server function. By default, it will
|
||||
#' create a 200 MB memory cache for each session, but you can replace it with
|
||||
#' something different. To use the session-scoped cache, you must also call
|
||||
#' `bindCache()` with `cache="session"`. This will create a 100 MB cache for
|
||||
#' the session:
|
||||
@@ -453,7 +453,7 @@ utils::globalVariables(".GenericCallEnv", add = TRUE)
|
||||
#' bindEvent(input$go)
|
||||
#' # The cached, eventified reactive takes a reactive dependency on
|
||||
#' # input$go, but doesn't use it for the cache key. It uses input$x and
|
||||
#' # input$y for the cache key, but doesn't take a reactive depdency on
|
||||
#' # input$y for the cache key, but doesn't take a reactive dependency on
|
||||
#' # them, because the reactive dependency is superseded by addEvent().
|
||||
#'
|
||||
#' output$txt <- renderText(r())
|
||||
|
||||
@@ -374,8 +374,7 @@ collapseSizes <- function(padding) {
|
||||
#' @param inverse `TRUE` to use a dark background and light text for the
|
||||
#' navigation bar
|
||||
#' @param collapsible `TRUE` to automatically collapse the navigation
|
||||
#' elements into a menu when the width of the browser is less than 940 pixels
|
||||
#' (useful for viewing on smaller touchscreen device)
|
||||
#' elements into an expandable menu on mobile devices or narrow window widths.
|
||||
#' @param fluid `TRUE` to use a fluid layout. `FALSE` to use a fixed
|
||||
#' layout.
|
||||
#' @param windowTitle the browser window title (as a character string). The
|
||||
@@ -533,7 +532,12 @@ wellPanel <- function(...) {
|
||||
#' }
|
||||
#' @export
|
||||
conditionalPanel <- function(condition, ..., ns = NS(NULL)) {
|
||||
div(`data-display-if`=condition, `data-ns-prefix`=ns(""), ...)
|
||||
div(
|
||||
class = "shiny-panel-conditional",
|
||||
`data-display-if` = condition,
|
||||
`data-ns-prefix` = ns(""),
|
||||
...
|
||||
)
|
||||
}
|
||||
|
||||
#' Create a help text element
|
||||
@@ -1234,13 +1238,13 @@ downloadButton <- function(outputId,
|
||||
class=NULL,
|
||||
...,
|
||||
icon = shiny::icon("download")) {
|
||||
aTag <- tags$a(id=outputId,
|
||||
class=paste('btn btn-default shiny-download-link', class),
|
||||
href='',
|
||||
target='_blank',
|
||||
download=NA,
|
||||
validateIcon(icon),
|
||||
label, ...)
|
||||
tags$a(id=outputId,
|
||||
class=paste('btn btn-default shiny-download-link', class),
|
||||
href='',
|
||||
target='_blank',
|
||||
download=NA,
|
||||
validateIcon(icon),
|
||||
label, ...)
|
||||
}
|
||||
|
||||
#' @rdname downloadButton
|
||||
|
||||
206
R/extended-task.R
Normal file
206
R/extended-task.R
Normal file
@@ -0,0 +1,206 @@
|
||||
#' Task or computation that proceeds in the background
|
||||
#'
|
||||
#' @description In normal Shiny reactive code, whenever an observer, calc, or
|
||||
#' output is busy computing, it blocks the current session from receiving any
|
||||
#' inputs or attempting to proceed with any other computation related to that
|
||||
#' session.
|
||||
#'
|
||||
#' The `ExtendedTask` class allows you to have an expensive operation that is
|
||||
#' started by a reactive effect, and whose (eventual) results can be accessed
|
||||
#' by a regular observer, calc, or output; but during the course of the
|
||||
#' operation, the current session is completely unblocked, allowing the user
|
||||
#' to continue using the rest of the app while the operation proceeds in the
|
||||
#' background.
|
||||
#'
|
||||
#' Note that each `ExtendedTask` object does not represent a _single
|
||||
#' invocation_ of its long-running function. Rather, it's an object that is
|
||||
#' used to invoke the function with different arguments, keeps track of
|
||||
#' whether an invocation is in progress, and provides ways to get at the
|
||||
#' current status or results of the operation. A single `ExtendedTask` object
|
||||
#' does not permit overlapping invocations: if the `invoke()` method is called
|
||||
#' before the previous `invoke()` is completed, the new invocation will not
|
||||
#' begin until the previous invocation has completed.
|
||||
#'
|
||||
#' @section `ExtendedTask` versus asynchronous reactives:
|
||||
#'
|
||||
#' Shiny has long supported [using
|
||||
#' \{promises\}](https://rstudio.github.io/promises/articles/promises_06_shiny.html)
|
||||
#' to write asynchronous observers, calcs, or outputs. You may be wondering
|
||||
#' what the differences are between those techniques and this class.
|
||||
#'
|
||||
#' Asynchronous observers, calcs, and outputs are not--and have never
|
||||
#' been--designed to let a user start a long-running operation, while keeping
|
||||
#' that very same (browser) session responsive to other interactions. Instead,
|
||||
#' they unblock other sessions, so you can take a long-running operation that
|
||||
#' would normally bring the entire R process to a halt and limit the blocking
|
||||
#' to just the session that started the operation. (For more details, see the
|
||||
#' section on ["The Flush
|
||||
#' Cycle"](https://rstudio.github.io/promises/articles/promises_06_shiny.html#the-flush-cycle).)
|
||||
#'
|
||||
#' `ExtendedTask`, on the other hand, invokes an asynchronous function (that
|
||||
#' is, a function that quickly returns a promise) and allows even that very
|
||||
#' session to immediately unblock and carry on with other user interactions.
|
||||
#'
|
||||
#' @export
|
||||
ExtendedTask <- R6Class("ExtendedTask", portable = TRUE,
|
||||
public = list(
|
||||
#' @description
|
||||
#' Creates a new `ExtendedTask` object. `ExtendedTask` should generally be
|
||||
#' created either at the top of a server function, or at the top of a module
|
||||
#' server function.
|
||||
#'
|
||||
#' @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 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
|
||||
#' [`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
|
||||
#' invoked; instead, if the function needs to access reactive inputs, it
|
||||
#' should take parameters and the caller of the `invoke()` method should
|
||||
#' read reactive inputs and pass them as arguments.
|
||||
initialize = function(func) {
|
||||
private$func <- func
|
||||
private$rv_status <- reactiveVal("initial")
|
||||
private$rv_value <- reactiveVal(NULL)
|
||||
private$rv_error <- reactiveVal(NULL)
|
||||
private$invocation_queue <- fastmap::fastqueue()
|
||||
},
|
||||
#' @description
|
||||
#' Starts executing the long-running operation. If this `ExtendedTask` is
|
||||
#' already running (meaning, a previous call to `invoke()` is not yet
|
||||
#' complete) then enqueues this invocation until after the current
|
||||
#' invocation, and any already-enqueued invocation, completes.
|
||||
#'
|
||||
#' @param ... Parameters to use for this invocation of the underlying
|
||||
#' function. If reactive inputs are needed by the underlying function,
|
||||
#' they should be read by the caller of `invoke` and passed in as
|
||||
#' arguments.
|
||||
invoke = function(...) {
|
||||
args <- rlang::dots_list(..., .ignore_empty = "none")
|
||||
|
||||
if (
|
||||
isolate(private$rv_status()) == "running" ||
|
||||
private$invocation_queue$size() > 0
|
||||
) {
|
||||
private$invocation_queue$add(args)
|
||||
} else {
|
||||
private$do_invoke(args)
|
||||
}
|
||||
invisible(NULL)
|
||||
},
|
||||
#' @description
|
||||
#' This is a reactive read that invalidates the caller when the task's
|
||||
#' status changes.
|
||||
#'
|
||||
#' Returns one of the following values:
|
||||
#'
|
||||
#' * `"initial"`: This `ExtendedTask` has not yet been invoked
|
||||
#' * `"running"`: An invocation is currently running
|
||||
#' * `"success"`: An invocation completed successfully, and a value can be
|
||||
#' retrieved via the `result()` method
|
||||
#' * `"error"`: An invocation completed with an error, which will be
|
||||
#' re-thrown if you call the `result()` method
|
||||
status = function() {
|
||||
private$rv_status()
|
||||
},
|
||||
#' @description
|
||||
#' Attempts to read the results of the most recent invocation. This is a
|
||||
#' reactive read that invalidates as the task's status changes.
|
||||
#'
|
||||
#' The actual behavior differs greatly depending on the current status of
|
||||
#' the task:
|
||||
#'
|
||||
#' * `"initial"`: Throws a silent error (like [`req(FALSE)`][req()]). If
|
||||
#' this happens during output rendering, the output will be blanked out.
|
||||
#' * `"running"`: Throws a special silent error that, if it happens during
|
||||
#' output rendering, makes the output appear "in progress" until further
|
||||
#' notice.
|
||||
#' * `"success"`: Returns the return value of the most recent invocation.
|
||||
#' * `"error"`: Throws whatever error was thrown by the most recent
|
||||
#' invocation.
|
||||
#'
|
||||
#' This method is intended to be called fairly naively by any output or
|
||||
#' reactive expression that cares about the output--you just have to be
|
||||
#' aware that if the result isn't ready for whatever reason, processing will
|
||||
#' stop in much the same way as `req(FALSE)` does, but when the result is
|
||||
#' ready you'll get invalidated, and when you run again the result should be
|
||||
#' there.
|
||||
#'
|
||||
#' Note that the `result()` method is generally not meant to be used with
|
||||
#' [observeEvent()], [eventReactive()], [bindEvent()], or [isolate()] as the
|
||||
#' invalidation will be ignored.
|
||||
result = function() {
|
||||
switch (private$rv_status(),
|
||||
running = req(FALSE, cancelOutput="progress"),
|
||||
success = if (private$rv_value()$visible) {
|
||||
private$rv_value()$value
|
||||
} else {
|
||||
invisible(private$rv_value()$value)
|
||||
},
|
||||
error = stop(private$rv_error()),
|
||||
# default case (initial, cancelled)
|
||||
req(FALSE)
|
||||
)
|
||||
}
|
||||
),
|
||||
private = list(
|
||||
func = NULL,
|
||||
# reactive value with "initial"|"running"|"success"|"error"
|
||||
rv_status = NULL,
|
||||
rv_value = NULL,
|
||||
rv_error = NULL,
|
||||
invocation_queue = NULL,
|
||||
|
||||
do_invoke = function(args) {
|
||||
private$rv_status("running")
|
||||
private$rv_value(NULL)
|
||||
private$rv_error(NULL)
|
||||
|
||||
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) {
|
||||
private$rv_status("error")
|
||||
private$rv_error(err)
|
||||
},
|
||||
|
||||
on_success = function(value) {
|
||||
private$rv_status("success")
|
||||
private$rv_value(value)
|
||||
}
|
||||
)
|
||||
)
|
||||
@@ -16,12 +16,6 @@
|
||||
s3_register("knitr::knit_print", "reactive")
|
||||
s3_register("knitr::knit_print", "shiny.appobj")
|
||||
s3_register("knitr::knit_print", "shiny.render.function")
|
||||
|
||||
# Shiny 1.4.0 bumps jQuery 1.x to 3.x, which caused a problem
|
||||
# with static-rendering of htmlwidgets, and htmlwidgets 1.5
|
||||
# includes a fix for this problem
|
||||
# https://github.com/rstudio/shiny/issues/2630
|
||||
register_upgrade_message("htmlwidgets", 1.5)
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ NULL
|
||||
#' depending on the values in the query string / hash (e.g. instead of basing
|
||||
#' the conditional on an input or a calculated reactive, you can base it on the
|
||||
#' query string). However, note that, if you're changing the query string / hash
|
||||
#' programatically from within the server code, you must use
|
||||
#' programmatically from within the server code, you must use
|
||||
#' `updateQueryString(_yourNewQueryString_, mode = "push")`. The default
|
||||
#' `mode` for `updateQueryString` is `"replace"`, which doesn't
|
||||
#' raise any events, so any observers or reactives that depend on it will
|
||||
|
||||
@@ -7,6 +7,8 @@
|
||||
#' @param label The contents of the button or link--usually a text label, but
|
||||
#' you could also use any other HTML, like an image.
|
||||
#' @param icon An optional [icon()] to appear on the button.
|
||||
#' @param disabled If `TRUE`, the button will not be clickable. Use
|
||||
#' [updateActionButton()] to dynamically enable/disable the button.
|
||||
#' @param ... Named attributes to be applied to the button or link.
|
||||
#'
|
||||
#' @family input elements
|
||||
@@ -49,7 +51,8 @@
|
||||
#' * Event handlers (e.g., [observeEvent()], [eventReactive()]) won't execute on initial load.
|
||||
#' * Input validation (e.g., [req()], [need()]) will fail on initial load.
|
||||
#' @export
|
||||
actionButton <- function(inputId, label, icon = NULL, width = NULL, ...) {
|
||||
actionButton <- function(inputId, label, icon = NULL, width = NULL,
|
||||
disabled = FALSE, ...) {
|
||||
|
||||
value <- restoreInput(id = inputId, default = NULL)
|
||||
|
||||
@@ -58,6 +61,7 @@ actionButton <- function(inputId, label, icon = NULL, width = NULL, ...) {
|
||||
type="button",
|
||||
class="btn btn-default action-button",
|
||||
`data-val` = value,
|
||||
disabled = if (isTRUE(disabled)) NA else NULL,
|
||||
list(validateIcon(icon), label),
|
||||
...
|
||||
)
|
||||
|
||||
@@ -31,7 +31,7 @@ checkboxInput <- function(inputId, label, value = FALSE, width = NULL) {
|
||||
|
||||
value <- restoreInput(id = inputId, default = value)
|
||||
|
||||
inputTag <- tags$input(id = inputId, type="checkbox")
|
||||
inputTag <- tags$input(id = inputId, type="checkbox", class = "shiny-input-checkbox")
|
||||
if (!is.null(value) && value)
|
||||
inputTag$attribs$checked <- "checked"
|
||||
|
||||
|
||||
@@ -2,8 +2,13 @@
|
||||
#'
|
||||
#' Create a file upload control that can be used to upload one or more files.
|
||||
#'
|
||||
#' Whenever a file upload completes, the corresponding input variable is set
|
||||
#' to a dataframe. See the `Server value` section.
|
||||
#' Whenever a file upload completes, the corresponding input variable is set to
|
||||
#' a dataframe. See the `Server value` section.
|
||||
#'
|
||||
#' Each time files are uploaded, they are written to a new random subdirectory
|
||||
#' inside of R's process-level temporary directory. The Shiny user session keeps
|
||||
#' track of all uploads in the session, and when the session ends, Shiny deletes
|
||||
#' all of the subdirectories where files where uploaded to.
|
||||
#'
|
||||
#' @family input elements
|
||||
#'
|
||||
@@ -11,21 +16,21 @@
|
||||
#' @param multiple Whether the user should be allowed to select and upload
|
||||
#' multiple files at once. **Does not work on older browsers, including
|
||||
#' Internet Explorer 9 and earlier.**
|
||||
#' @param accept A character vector of "unique file type specifiers" which
|
||||
#' gives the browser a hint as to the type of file the server expects.
|
||||
#' Many browsers use this prevent the user from selecting an invalid file.
|
||||
#' @param accept A character vector of "unique file type specifiers" which gives
|
||||
#' the browser a hint as to the type of file the server expects. Many browsers
|
||||
#' use this prevent the user from selecting an invalid file.
|
||||
#'
|
||||
#' A unique file type specifier can be:
|
||||
#' * A case insensitive extension like `.csv` or `.rds`.
|
||||
#' * A valid MIME type, like `text/plain` or `application/pdf`
|
||||
#' * One of `audio/*`, `video/*`, or `image/*` meaning any audio, video,
|
||||
#' or image type, respectively.
|
||||
#' or image type, respectively.
|
||||
#' @param buttonLabel The label used on the button. Can be text or an HTML tag
|
||||
#' object.
|
||||
#' @param placeholder The text to show before a file has been uploaded.
|
||||
#' @param capture What source to use for capturing image, audio or video data.
|
||||
#' This attribute facilitates user access to a device's media capture
|
||||
#' mechanism, such as a camera, or microphone, from within a file upload
|
||||
#' This attribute facilitates user access to a device's media capture
|
||||
#' mechanism, such as a camera, or microphone, from within a file upload
|
||||
#' control.
|
||||
#'
|
||||
#' A value of `user` indicates that the user-facing camera and/or microphone
|
||||
@@ -67,7 +72,9 @@
|
||||
#' }
|
||||
#'
|
||||
#' @section Server value:
|
||||
#' A `data.frame` that contains one row for each selected file, and following columns:
|
||||
#'
|
||||
#' A `data.frame` that contains one row for each selected file, and following
|
||||
#' columns:
|
||||
#' \describe{
|
||||
#' \item{`name`}{The filename provided by the web browser. This is
|
||||
#' **not** the path to read to get at the actual data that was uploaded
|
||||
@@ -101,6 +108,7 @@ fileInput <- function(inputId, label, multiple = FALSE, accept = NULL,
|
||||
|
||||
inputTag <- tags$input(
|
||||
id = inputId,
|
||||
class = "shiny-input-file",
|
||||
name = inputId,
|
||||
type = "file",
|
||||
# Don't use "display: none;" style, which causes keyboard accessibility issue; instead use the following workaround: https://css-tricks.com/places-its-tempting-to-use-display-none-but-dont/
|
||||
|
||||
@@ -35,7 +35,7 @@ numericInput <- function(inputId, label, value, min = NA, max = NA, step = NA,
|
||||
value <- restoreInput(id = inputId, default = value)
|
||||
|
||||
# build input tag
|
||||
inputTag <- tags$input(id = inputId, type = "number", class="form-control",
|
||||
inputTag <- tags$input(id = inputId, type = "number", class="shiny-input-number form-control",
|
||||
value = formatNoSci(value))
|
||||
if (!is.na(min))
|
||||
inputTag$attribs$min = min
|
||||
|
||||
@@ -35,7 +35,7 @@ passwordInput <- function(inputId, label, value = "", width = NULL,
|
||||
div(class = "form-group shiny-input-container",
|
||||
style = css(width = validateCssUnit(width)),
|
||||
shinyInputLabel(inputId, label),
|
||||
tags$input(id = inputId, type="password", class="form-control", value=value,
|
||||
tags$input(id = inputId, type="password", class="shiny-input-password form-control", value=value,
|
||||
placeholder = placeholder)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
#' from a list of values.
|
||||
#'
|
||||
#' By default, `selectInput()` and `selectizeInput()` use the JavaScript library
|
||||
#' \pkg{selectize.js} (<https://github.com/selectize/selectize.js>) instead of
|
||||
#' \pkg{selectize.js} (<https://selectize.dev/>) instead of
|
||||
#' the basic select input element. To use the standard HTML select input
|
||||
#' element, use `selectInput()` with `selectize=FALSE`.
|
||||
#'
|
||||
@@ -106,6 +106,7 @@ selectInput <- function(inputId, label, choices, selected = NULL,
|
||||
# create select tag and add options
|
||||
selectTag <- tags$select(
|
||||
id = inputId,
|
||||
class = "shiny-input-select",
|
||||
class = if (!selectize) "form-control",
|
||||
size = size,
|
||||
selectOptions(choices, selected, inputId, selectize)
|
||||
@@ -172,7 +173,7 @@ needOptgroup <- function(choices) {
|
||||
|
||||
#' @rdname selectInput
|
||||
#' @param ... Arguments passed to `selectInput()`.
|
||||
#' @param options A list of options. See the documentation of \pkg{selectize.js}
|
||||
#' @param options A list of options. See the documentation of \pkg{selectize.js}(<https://selectize.dev/docs/usage>)
|
||||
#' for possible options (character option values inside [base::I()] will
|
||||
#' be treated as literal JavaScript code; see [renderDataTable()]
|
||||
#' for details).
|
||||
@@ -252,9 +253,8 @@ selectizeDependencyFunc <- function(theme) {
|
||||
# name, the JS/CSS would be loaded/included twice, which leads to
|
||||
# strange issues, especially since we now include a 3rd party
|
||||
# accessibility plugin https://github.com/rstudio/shiny/pull/3153
|
||||
script <- file.path(
|
||||
selectizeDir, c("js/selectize.min.js", "accessibility/js/selectize-plugin-a11y.min.js")
|
||||
)
|
||||
script <- file.path(selectizeDir, selectizeScripts())
|
||||
|
||||
bslib::bs_dependency(
|
||||
input = sass::sass_file(stylesheet),
|
||||
theme = theme,
|
||||
@@ -272,10 +272,18 @@ selectizeStaticDependency <- function(version) {
|
||||
src = "www/shared/selectize",
|
||||
package = "shiny",
|
||||
stylesheet = "css/selectize.bootstrap3.css",
|
||||
script = c(
|
||||
"js/selectize.min.js",
|
||||
"accessibility/js/selectize-plugin-a11y.min.js"
|
||||
)
|
||||
script = selectizeScripts()
|
||||
)
|
||||
}
|
||||
|
||||
selectizeScripts <- function() {
|
||||
isMinified <- isTRUE(get_devmode_option("shiny.minified", TRUE))
|
||||
paste0(
|
||||
c(
|
||||
"js/selectize",
|
||||
"accessibility/js/selectize-plugin-a11y"
|
||||
),
|
||||
if (isMinified) ".min.js" else ".js"
|
||||
)
|
||||
}
|
||||
|
||||
@@ -287,7 +295,7 @@ selectizeStaticDependency <- function(version) {
|
||||
#'
|
||||
#' By default, `varSelectInput()` and `selectizeInput()` use the
|
||||
#' JavaScript library \pkg{selectize.js}
|
||||
#' (<https://github.com/selectize/selectize.js>) to instead of the basic
|
||||
#' (<https://selectize.dev/>) to instead of the basic
|
||||
#' select input element. To use the standard HTML select input element, use
|
||||
#' `selectInput()` with `selectize=FALSE`.
|
||||
#'
|
||||
@@ -383,7 +391,7 @@ varSelectInput <- function(
|
||||
|
||||
#' @rdname varSelectInput
|
||||
#' @param ... Arguments passed to `varSelectInput()`.
|
||||
#' @param options A list of options. See the documentation of \pkg{selectize.js}
|
||||
#' @param options A list of options. See the documentation of \pkg{selectize.js}(<https://selectize.dev/docs/usage>)
|
||||
#' for possible options (character option values inside [base::I()] will
|
||||
#' be treated as literal JavaScript code; see [renderDataTable()]
|
||||
#' for details).
|
||||
|
||||
@@ -42,7 +42,7 @@ textInput <- function(inputId, label, value = "", width = NULL,
|
||||
div(class = "form-group shiny-input-container",
|
||||
style = css(width = validateCssUnit(width)),
|
||||
shinyInputLabel(inputId, label),
|
||||
tags$input(id = inputId, type="text", class="form-control", value=value,
|
||||
tags$input(id = inputId, type="text", class="shiny-input-text form-control", value=value,
|
||||
placeholder = placeholder)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -52,7 +52,7 @@ textAreaInput <- function(inputId, label, value = "", width = NULL, height = NUL
|
||||
|
||||
style <- css(
|
||||
# The width is specified on the parent div.
|
||||
width = if (!is.null(width)) "width: 100%;",
|
||||
width = if (!is.null(width)) "100%",
|
||||
height = validateCssUnit(height),
|
||||
resize = resize
|
||||
)
|
||||
@@ -62,7 +62,7 @@ textAreaInput <- function(inputId, label, value = "", width = NULL, height = NUL
|
||||
style = if (!is.null(width)) paste0("width: ", validateCssUnit(width), ";"),
|
||||
tags$textarea(
|
||||
id = inputId,
|
||||
class = "form-control",
|
||||
class = "shiny-input-textarea form-control",
|
||||
placeholder = placeholder,
|
||||
style = style,
|
||||
rows = rows,
|
||||
|
||||
20
R/jqueryui.R
20
R/jqueryui.R
@@ -76,16 +76,20 @@ absolutePanel <- function(...,
|
||||
|
||||
style <- paste(paste(names(cssProps), cssProps, sep = ':', collapse = ';'), ';', sep='')
|
||||
divTag <- tags$div(style=style, ...)
|
||||
if (isTRUE(draggable)) {
|
||||
divTag <- tagAppendAttributes(divTag, class='draggable')
|
||||
return(tagList(
|
||||
divTag,
|
||||
jqueryuiDependency(),
|
||||
tags$script('$(".draggable").draggable();')
|
||||
))
|
||||
} else {
|
||||
|
||||
if (identical(draggable, FALSE)) {
|
||||
return(divTag)
|
||||
}
|
||||
|
||||
# Add Shiny inputs and htmlwidgets to 'non-draggable' elements
|
||||
# Cf. https://api.jqueryui.com/draggable/#option-cancel
|
||||
dragOpts <- '{cancel: ".shiny-input-container,.html-widget,input,textarea,button,select,option"}'
|
||||
dragJS <- sprintf('$(".draggable").draggable(%s);', dragOpts)
|
||||
tagList(
|
||||
tagAppendAttributes(divTag, class='draggable'),
|
||||
jqueryuiDependency(),
|
||||
tags$script(HTML(dragJS))
|
||||
)
|
||||
}
|
||||
|
||||
#' @rdname absolutePanel
|
||||
|
||||
11
R/map.R
11
R/map.R
@@ -48,9 +48,12 @@ Map <- R6Class(
|
||||
)
|
||||
)
|
||||
|
||||
as.list.Map <- function(map) {
|
||||
map$values()
|
||||
#' @export
|
||||
as.list.Map <- function(x, ...) {
|
||||
x$values()
|
||||
}
|
||||
length.Map <- function(map) {
|
||||
map$size()
|
||||
|
||||
#' @export
|
||||
length.Map <- function(x) {
|
||||
x$size()
|
||||
}
|
||||
|
||||
14
R/react.R
14
R/react.R
@@ -219,10 +219,10 @@ getDummyContext <- function() {
|
||||
|
||||
wrapForContext <- function(func, ctx) {
|
||||
force(func)
|
||||
force(ctx)
|
||||
force(ctx) # may be NULL (in the case of maskReactiveContext())
|
||||
|
||||
function(...) {
|
||||
ctx$run(function() {
|
||||
.getReactiveEnvironment()$runWith(ctx, function() {
|
||||
captureStackTraces(
|
||||
func(...)
|
||||
)
|
||||
@@ -234,12 +234,18 @@ reactivePromiseDomain <- function() {
|
||||
promises::new_promise_domain(
|
||||
wrapOnFulfilled = function(onFulfilled) {
|
||||
force(onFulfilled)
|
||||
ctx <- getCurrentContext()
|
||||
|
||||
# ctx will be NULL if we're in a maskReactiveContext()
|
||||
ctx <- if (hasCurrentContext()) getCurrentContext() else NULL
|
||||
|
||||
wrapForContext(onFulfilled, ctx)
|
||||
},
|
||||
wrapOnRejected = function(onRejected) {
|
||||
force(onRejected)
|
||||
ctx <- getCurrentContext()
|
||||
|
||||
# ctx will be NULL if we're in a maskReactiveContext()
|
||||
ctx <- if (hasCurrentContext()) getCurrentContext() else NULL
|
||||
|
||||
wrapForContext(onRejected, ctx)
|
||||
}
|
||||
)
|
||||
|
||||
@@ -2187,8 +2187,8 @@ maskReactiveContext <- function(expr) {
|
||||
#' @param autoDestroy If `TRUE` (the default), the observer will be
|
||||
#' automatically destroyed when its domain (if any) ends.
|
||||
#' @param ignoreNULL Whether the action should be triggered (or value
|
||||
#' calculated, in the case of `eventReactive`) when the input is
|
||||
#' `NULL`. See Details.
|
||||
#' calculated, in the case of `eventReactive`) when the input event expression
|
||||
#' is `NULL`. See Details.
|
||||
#' @param ignoreInit If `TRUE`, then, when this `observeEvent` is
|
||||
#' first created/initialized, ignore the `handlerExpr` (the second
|
||||
#' argument), whether it is otherwise supposed to run or not. The default is
|
||||
|
||||
@@ -80,7 +80,10 @@ getShinyOption <- function(name, default = NULL) {
|
||||
#' [shinyDeprecated()] for more information.}
|
||||
#' \item{shiny.error (defaults to `NULL`)}{This can be a function which is called when an error
|
||||
#' occurs. For example, `options(shiny.error=recover)` will result a
|
||||
#' the debugger prompt when an error occurs.}
|
||||
#' the debugger prompt when an error occurs. If the function takes an `error`
|
||||
#' argument, it will be called with the error object. For example,
|
||||
#' `options(shiny.error = function(error) { })` will receive the error
|
||||
#' condition object when an error occurs.}
|
||||
#' \item{shiny.fullstacktrace (defaults to `FALSE`)}{Controls whether "pretty" (`FALSE`) or full
|
||||
#' stack traces (`TRUE`) are dumped to the console when errors occur during Shiny app execution.
|
||||
#' Pretty stack traces attempt to only show user-supplied code, but this pruning can't always
|
||||
@@ -90,8 +93,9 @@ getShinyOption <- function(name, default = NULL) {
|
||||
#' \item{shiny.jquery.version (defaults to `3`)}{The major version of jQuery to use.
|
||||
#' Currently only values of `3` or `1` are supported. If `1`, then jQuery 1.12.4 is used. If `3`,
|
||||
#' then jQuery `r version_jquery` is used.}
|
||||
#' \item{shiny.json.digits (defaults to `16`)}{The number of digits to use when converting
|
||||
#' numbers to JSON format to send to the client web browser.}
|
||||
#' \item{shiny.json.digits (defaults to `I(16)`)}{Max number of digits to use when converting
|
||||
#' numbers to JSON format to send to the client web browser. Use [I()] to specify significant digits.
|
||||
#' Use `NA` for max precision.}
|
||||
#' \item{shiny.launch.browser (defaults to `interactive()`)}{A boolean which controls the default behavior
|
||||
#' when an app is run. See [runApp()] for more information.}
|
||||
#' \item{shiny.mathjax.url (defaults to `"https://mathjax.rstudio.com/latest/MathJax.js"`)}{
|
||||
@@ -112,7 +116,7 @@ getShinyOption <- function(name, default = NULL) {
|
||||
#' production.}
|
||||
#' \item{shiny.sanitize.errors (defaults to `FALSE`)}{If `TRUE`, then normal errors (i.e.
|
||||
#' errors not wrapped in `safeError`) won't show up in the app; a simple
|
||||
#' generic error message is printed instead (the error and strack trace printed
|
||||
#' generic error message is printed instead (the error and stack trace printed
|
||||
#' to the console remain unchanged). If you want to sanitize errors in general, but you DO want a
|
||||
#' particular error `e` to get displayed to the user, then set this option
|
||||
#' to `TRUE` and use `stop(safeError(e))` for errors you want the
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
# See also R/reexports.R
|
||||
|
||||
## usethis namespace: start
|
||||
## usethis namespace: end
|
||||
#' @importFrom lifecycle deprecated is_present
|
||||
#' @importFrom grDevices dev.set dev.cur
|
||||
#' @importFrom fastmap fastmap
|
||||
@@ -18,13 +17,13 @@
|
||||
#' is_false list2
|
||||
#' missing_arg is_missing maybe_missing
|
||||
#' quo_is_missing fn_fmls<- fn_body fn_body<-
|
||||
#' @importFrom ellipsis
|
||||
#' check_dots_empty check_dots_unnamed
|
||||
#' @import htmltools
|
||||
#' @import httpuv
|
||||
#' @import xtable
|
||||
#' @import R6
|
||||
#' @import mime
|
||||
## usethis namespace: end
|
||||
NULL
|
||||
|
||||
# It's necessary to Depend on methods so Rscript doesn't fail. It's necessary
|
||||
@@ -34,3 +33,11 @@ NULL
|
||||
# since we call require(shiny) as part of loading the app.
|
||||
#' @import methods
|
||||
NULL
|
||||
|
||||
|
||||
# For usethis::use_release_issue()
|
||||
release_bullets <- function() {
|
||||
c(
|
||||
"Update static imports: `staticimports::import()`"
|
||||
)
|
||||
}
|
||||
|
||||
62
R/shiny.R
62
R/shiny.R
@@ -16,8 +16,7 @@ NULL
|
||||
#'
|
||||
#' @name shiny-package
|
||||
#' @aliases shiny
|
||||
#' @docType package
|
||||
NULL
|
||||
"_PACKAGE"
|
||||
|
||||
createUniqueId <- function(bytes, prefix = "", suffix = "") {
|
||||
withPrivateSeed({
|
||||
@@ -33,8 +32,12 @@ createUniqueId <- function(bytes, prefix = "", suffix = "") {
|
||||
}
|
||||
|
||||
toJSON <- function(x, ..., dataframe = "columns", null = "null", na = "null",
|
||||
auto_unbox = TRUE, digits = getOption("shiny.json.digits", 16),
|
||||
use_signif = TRUE, force = TRUE, POSIXt = "ISO8601", UTC = TRUE,
|
||||
auto_unbox = TRUE,
|
||||
# Shiny has had a legacy value of 16 significant digits
|
||||
# We can use `I(16)` mixed with the default behavior in jsonlite's `use_signif=`
|
||||
# https://github.com/jeroen/jsonlite/commit/728efa9
|
||||
digits = getOption("shiny.json.digits", I(16)), use_signif = is(digits, "AsIs"),
|
||||
force = TRUE, POSIXt = "ISO8601", UTC = TRUE,
|
||||
rownames = FALSE, keep_vec_names = TRUE, strict_atomic = TRUE) {
|
||||
|
||||
if (strict_atomic) {
|
||||
@@ -211,7 +214,7 @@ workerId <- local({
|
||||
#' Sends a custom message to the web page. `type` must be a
|
||||
#' single-element character vector giving the type of message, while
|
||||
#' `message` can be any jsonlite-encodable value. Custom messages
|
||||
#' have no meaning to Shiny itself; they are used soley to convey information
|
||||
#' have no meaning to Shiny itself; they are used solely to convey information
|
||||
#' to custom JavaScript logic in the browser. You can do this by adding
|
||||
#' JavaScript code to the browser that calls
|
||||
#' \code{Shiny.addCustomMessageHandler(type, function(message){...})}
|
||||
@@ -1145,6 +1148,8 @@ ShinySession <- R6Class(
|
||||
structure(list(), class = "try-error", condition = cond)
|
||||
} else if (inherits(cond, "shiny.output.cancel")) {
|
||||
structure(list(), class = "cancel-output")
|
||||
} else if (inherits(cond, "shiny.output.progress")) {
|
||||
structure(list(), class = "progress-output")
|
||||
} else if (cnd_inherits(cond, "shiny.silent.error")) {
|
||||
# The error condition might have been chained by
|
||||
# foreign code, e.g. dplyr. Find the original error.
|
||||
@@ -1173,6 +1178,33 @@ ShinySession <- R6Class(
|
||||
# client knows that progress is over.
|
||||
self$requestFlush()
|
||||
|
||||
if (inherits(value, "progress-output")) {
|
||||
# This is the case where an output needs to compute for longer
|
||||
# than this reactive flush. We put the output into progress mode
|
||||
# (i.e. adding .recalculating) with a special flag that means
|
||||
# the progress indication should not be cleared until this
|
||||
# specific output receives a new value or error.
|
||||
self$showProgress(name, persistent=TRUE)
|
||||
|
||||
# It's conceivable that this output already ran successfully
|
||||
# within this reactive flush, in which case we could either show
|
||||
# the new output while simultaneously making it .recalculating;
|
||||
# or we squelch the new output and make whatever output is in
|
||||
# the client .recalculating. I (jcheng) decided on the latter as
|
||||
# it seems more in keeping with what we do with these kinds of
|
||||
# intermediate output values/errors in general, i.e. ignore them
|
||||
# and wait until we have a final answer. (Also kind of feels
|
||||
# like a bug in the app code if you routinely have outputs that
|
||||
# are executing successfully, only to be invalidated again
|
||||
# within the same reactive flush--use priority to fix that.)
|
||||
private$invalidatedOutputErrors$remove(name)
|
||||
private$invalidatedOutputValues$remove(name)
|
||||
|
||||
# It's important that we return so that the existing output in
|
||||
# the client remains untouched.
|
||||
return()
|
||||
}
|
||||
|
||||
private$sendMessage(recalculating = list(
|
||||
name = name, status = 'recalculated'
|
||||
))
|
||||
@@ -1305,23 +1337,29 @@ ShinySession <- R6Class(
|
||||
private$startCycle()
|
||||
}
|
||||
},
|
||||
showProgress = function(id) {
|
||||
showProgress = function(id, persistent=FALSE) {
|
||||
'Send a message to the client that recalculation of the output identified
|
||||
by \\code{id} is in progress. There is currently no mechanism for
|
||||
explicitly turning off progress for an output component; instead, all
|
||||
progress is implicitly turned off when flushOutput is next called.'
|
||||
progress is implicitly turned off when flushOutput is next called.
|
||||
|
||||
You can use persistent=TRUE if the progress for this output component
|
||||
should stay on beyond the flushOutput (or any subsequent flushOutputs); in
|
||||
that case, progress is only turned off (and the persistent flag cleared)
|
||||
when the output component receives a value or error, or, if
|
||||
showProgress(id, persistent=FALSE) is called and a subsequent flushOutput
|
||||
occurs.'
|
||||
|
||||
# If app is already closed, be sure not to show progress, otherwise we
|
||||
# will get an error because of the closed websocket
|
||||
if (self$closed)
|
||||
return()
|
||||
|
||||
if (id %in% private$progressKeys)
|
||||
return()
|
||||
if (!id %in% private$progressKeys) {
|
||||
private$progressKeys <- c(private$progressKeys, id)
|
||||
}
|
||||
|
||||
private$progressKeys <- c(private$progressKeys, id)
|
||||
|
||||
self$sendProgress('binding', list(id = id))
|
||||
self$sendProgress('binding', list(id = id, persistent = persistent))
|
||||
},
|
||||
sendProgress = function(type, message) {
|
||||
private$sendMessage(
|
||||
|
||||
22
R/shinyui.R
22
R/shinyui.R
@@ -69,6 +69,21 @@ renderPage <- function(ui, showcase=0, testMode=FALSE) {
|
||||
)
|
||||
}
|
||||
|
||||
if (in_devmode()) {
|
||||
# If we're in dev mode, add a simple script to the head that injects a
|
||||
# global variable for the client to use to detect dev mode.
|
||||
shiny_deps[[length(shiny_deps) + 1]] <-
|
||||
htmlDependency(
|
||||
"shiny-devmode",
|
||||
get_package_version("shiny"),
|
||||
src = "www/shared",
|
||||
package = "shiny",
|
||||
head="<script>window.__SHINY_DEV_MODE__ = true;</script>",
|
||||
all_files = FALSE
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
html <- renderDocument(ui, shiny_deps, processDep = createWebDependency)
|
||||
enc2utf8(paste(collapse = "\n", html))
|
||||
}
|
||||
@@ -133,8 +148,11 @@ shinyDependencyCSS <- function(theme) {
|
||||
))
|
||||
}
|
||||
|
||||
bs_version <- bslib::theme_version(theme)
|
||||
bootstrap_scss <- paste0("shiny.bootstrap", bs_version, ".scss")
|
||||
|
||||
scss_home <- system_file("www/shared/shiny_scss", package = "shiny")
|
||||
scss_files <- file.path(scss_home, c("bootstrap.scss", "shiny.scss"))
|
||||
scss_files <- file.path(scss_home, c(bootstrap_scss, "shiny.scss"))
|
||||
scss_files <- lapply(scss_files, sass::sass_file)
|
||||
|
||||
bslib::bs_dependency(
|
||||
@@ -156,7 +174,7 @@ shinyDependencyCSS <- function(theme) {
|
||||
#' This function is kept for backwards compatibility with older applications. It
|
||||
#' returns the value that is passed to it.
|
||||
#'
|
||||
#' @param ui A user interace definition
|
||||
#' @param ui A user interface definition
|
||||
#' @return The user interface definition, without modifications or side effects.
|
||||
#' @keywords internal
|
||||
#' @export
|
||||
|
||||
@@ -42,36 +42,20 @@ get_package_version <- function(pkg) {
|
||||
|
||||
is_installed <- function(pkg, version = NULL) {
|
||||
installed <- isNamespaceLoaded(pkg) || nzchar(system_file_cached(package = pkg))
|
||||
|
||||
if (is.null(version)) {
|
||||
return(installed)
|
||||
}
|
||||
installed && isTRUE(get_package_version(pkg) >= version)
|
||||
}
|
||||
|
||||
register_upgrade_message <- function(pkg, version, error = FALSE) {
|
||||
if (!is.character(version) && !inherits(version, "numeric_version")) {
|
||||
# Avoid https://bugs.r-project.org/show_bug.cgi?id=18548
|
||||
alert <- if (identical(Sys.getenv("TESTTHAT"), "true")) stop else warning
|
||||
alert("`version` must be a character string or a `package_version` or `numeric_version` object.")
|
||||
|
||||
msg <- sprintf(
|
||||
"This version of '%s' is designed to work with '%s' >= %s.
|
||||
Please upgrade via install.packages('%s').",
|
||||
environmentName(environment(register_upgrade_message)),
|
||||
pkg, version, pkg
|
||||
)
|
||||
|
||||
cond <- if (error) stop else packageStartupMessage
|
||||
|
||||
if (pkg %in% loadedNamespaces() && !is_installed(pkg, version)) {
|
||||
cond(msg)
|
||||
version <- numeric_version(sprintf("%0.9g", version))
|
||||
}
|
||||
|
||||
# Always register hook in case pkg is loaded at some
|
||||
# point the future (or, potentially, but less commonly,
|
||||
# unloaded & reloaded)
|
||||
setHook(
|
||||
packageEvent(pkg, "onLoad"),
|
||||
function(...) {
|
||||
if (!is_installed(pkg, version)) cond(msg)
|
||||
}
|
||||
)
|
||||
installed && isTRUE(get_package_version(pkg) >= version)
|
||||
}
|
||||
|
||||
# Simplified version rlang:::s3_register() that just uses
|
||||
@@ -190,11 +174,9 @@ system_file <- function(..., package = "base") {
|
||||
normalizePath(files, winslash = "/")
|
||||
}
|
||||
|
||||
# A wrapper for `system.file()`, which caches the results, because
|
||||
# `system.file()` can be slow. Note that because of caching, if
|
||||
# `system_file_cached()` is called on a package that isn't installed, then the
|
||||
# package is installed, and then `system_file_cached()` is called again, it will
|
||||
# still return "".
|
||||
# A wrapper for `system.file()`, which caches the package path because
|
||||
# `system.file()` can be slow. If a package is not installed, the result won't
|
||||
# be cached.
|
||||
system_file_cached <- local({
|
||||
pkg_dir_cache <- character()
|
||||
|
||||
@@ -206,7 +188,9 @@ system_file_cached <- local({
|
||||
not_cached <- is.na(match(package, names(pkg_dir_cache)))
|
||||
if (not_cached) {
|
||||
pkg_dir <- system.file(package = package)
|
||||
pkg_dir_cache[[package]] <<- pkg_dir
|
||||
if (nzchar(pkg_dir)) {
|
||||
pkg_dir_cache[[package]] <<- pkg_dir
|
||||
}
|
||||
} else {
|
||||
pkg_dir <- pkg_dir_cache[[package]]
|
||||
}
|
||||
|
||||
@@ -119,6 +119,8 @@ updateCheckboxInput <- function(session = getDefaultReactiveDomain(), inputId, l
|
||||
#' Change the label or icon of an action button on the client
|
||||
#'
|
||||
#' @template update-input
|
||||
#' @param disabled If `TRUE`, the button will not be clickable; if `FALSE`, it
|
||||
#' will be.
|
||||
#' @inheritParams actionButton
|
||||
#'
|
||||
#' @seealso [actionButton()]
|
||||
@@ -148,13 +150,13 @@ updateCheckboxInput <- function(session = getDefaultReactiveDomain(), inputId, l
|
||||
#' label = "New label",
|
||||
#' icon = icon("calendar"))
|
||||
#'
|
||||
#' # Leaves goButton2's label unchaged and
|
||||
#' # Leaves goButton2's label unchanged and
|
||||
#' # removes its icon
|
||||
#' updateActionButton(session, "goButton2",
|
||||
#' icon = character(0))
|
||||
#'
|
||||
#' # Leaves goButton3's icon, if it exists,
|
||||
#' # unchaged and changes its label
|
||||
#' # unchanged and changes its label
|
||||
#' updateActionButton(session, "goButton3",
|
||||
#' label = "New label 3")
|
||||
#'
|
||||
@@ -169,16 +171,18 @@ updateCheckboxInput <- function(session = getDefaultReactiveDomain(), inputId, l
|
||||
#' }
|
||||
#' @rdname updateActionButton
|
||||
#' @export
|
||||
updateActionButton <- function(session = getDefaultReactiveDomain(), inputId, label = NULL, icon = NULL) {
|
||||
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=label, icon=icon))
|
||||
message <- dropNulls(list(label=label, icon=icon, disabled=disabled))
|
||||
session$sendInputMessage(inputId, message)
|
||||
}
|
||||
#' @rdname updateActionButton
|
||||
#' @export
|
||||
updateActionLink <- updateActionButton
|
||||
updateActionLink <- function(session = getDefaultReactiveDomain(), inputId, label = NULL, icon = NULL) {
|
||||
updateActionButton(session, inputId=inputId, label=label, icon=icon)
|
||||
}
|
||||
|
||||
|
||||
#' Change the value of a date input on the client
|
||||
@@ -423,6 +427,23 @@ updateSliderInput <- function(session = getDefaultReactiveDomain(), inputId, lab
|
||||
{
|
||||
validate_session_object(session)
|
||||
|
||||
if (!is.null(value)) {
|
||||
if (!is.null(min) && !is.null(max)) {
|
||||
# Validate value/min/max together if all three are provided
|
||||
tryCatch(
|
||||
validate_slider_value(min, max, value, "updateSliderInput"),
|
||||
error = function(err) warning(conditionMessage(err), call. = FALSE)
|
||||
)
|
||||
} else if (length(value) < 1 || length(value) > 2 || any(is.na(value))) {
|
||||
# Otherwise ensure basic assumptions about value are met
|
||||
warning(
|
||||
"In updateSliderInput(): value must be a single value or a length-2 ",
|
||||
"vector and cannot contain NA values.",
|
||||
call. = FALSE
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
# If no min/max/value is provided, we won't know the
|
||||
# type, and this will return an empty string
|
||||
dataType <- getSliderType(min, max, value)
|
||||
|
||||
35
R/utils.R
35
R/utils.R
@@ -4,7 +4,7 @@ NULL
|
||||
|
||||
# @staticimports pkg:staticimports
|
||||
# is_installed get_package_version system_file
|
||||
# s3_register register_upgrade_message
|
||||
# s3_register
|
||||
# any_named any_unnamed
|
||||
|
||||
#' Make a random number generator repeatable
|
||||
@@ -488,7 +488,13 @@ shinyCallingHandlers <- function(expr) {
|
||||
return()
|
||||
|
||||
handle <- getOption('shiny.error')
|
||||
if (is.function(handle)) handle()
|
||||
if (is.function(handle)) {
|
||||
if ("error" %in% names(formals(handle))) {
|
||||
handle(error = e)
|
||||
} else {
|
||||
handle()
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
@@ -1093,7 +1099,7 @@ need <- function(expr, message = paste(label, "must be provided"), label) {
|
||||
#'
|
||||
#' You can use `req(FALSE)` (i.e. no condition) if you've already performed
|
||||
#' all the checks you needed to by that point and just want to stop the reactive
|
||||
#' chain now. There is no advantange to this, except perhaps ease of readibility
|
||||
#' chain now. There is no advantage to this, except perhaps ease of readability
|
||||
#' if you have a complicated condition to check for (or perhaps if you'd like to
|
||||
#' divide your condition into nested `if` statements).
|
||||
#'
|
||||
@@ -1115,7 +1121,10 @@ need <- function(expr, message = paste(label, "must be provided"), label) {
|
||||
#' @param ... Values to check for truthiness.
|
||||
#' @param cancelOutput If `TRUE` and an output is being evaluated, stop
|
||||
#' processing as usual but instead of clearing the output, leave it in
|
||||
#' whatever state it happens to be in.
|
||||
#' whatever state it happens to be in. If `"progress"`, do the same as `TRUE`,
|
||||
#' but also keep the output in recalculating state; this is intended for cases
|
||||
#' when an in-progress calculation will not be completed in this reactive
|
||||
#' flush cycle, but is still expected to provide a result in the future.
|
||||
#' @return The first value that was passed in.
|
||||
#' @export
|
||||
#' @examples
|
||||
@@ -1147,6 +1156,8 @@ req <- function(..., cancelOutput = FALSE) {
|
||||
if (!isTruthy(item)) {
|
||||
if (isTRUE(cancelOutput)) {
|
||||
cancelOutput()
|
||||
} else if (identical(cancelOutput, "progress")) {
|
||||
reactiveStop(class = "shiny.output.progress")
|
||||
} else {
|
||||
reactiveStop(class = "validation")
|
||||
}
|
||||
@@ -1240,14 +1251,12 @@ dotloop <- function(fun_, ...) {
|
||||
#' @param x An expression whose truthiness value we want to determine
|
||||
#' @export
|
||||
isTruthy <- function(x) {
|
||||
if (inherits(x, 'try-error'))
|
||||
return(FALSE)
|
||||
|
||||
if (!is.atomic(x))
|
||||
return(TRUE)
|
||||
|
||||
if (is.null(x))
|
||||
return(FALSE)
|
||||
if (inherits(x, 'try-error'))
|
||||
return(FALSE)
|
||||
if (!is.atomic(x))
|
||||
return(TRUE)
|
||||
if (length(x) == 0)
|
||||
return(FALSE)
|
||||
if (all(is.na(x)))
|
||||
@@ -1431,6 +1440,12 @@ wrapFunctionLabel <- function(func, name, ..stacktraceon = FALSE, dots = TRUE) {
|
||||
if (name == "name" || name == "func" || name == "relabelWrapper") {
|
||||
stop("Invalid name for wrapFunctionLabel: ", name)
|
||||
}
|
||||
if (nchar(name, "bytes") > 10000) {
|
||||
# Max variable length in R is 10000 bytes. Truncate to a shorter number of
|
||||
# chars because some characters could be multi-byte.
|
||||
name <- substr(name, 1, 5000)
|
||||
}
|
||||
|
||||
assign(name, func, environment())
|
||||
registerDebugHook(name, environment(), name)
|
||||
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
# Generated by tools/updateSelectize.R; do not edit by hand
|
||||
version_selectize <- "0.12.4"
|
||||
version_selectize <- "0.15.2"
|
||||
|
||||
@@ -16,7 +16,7 @@ Easily build rich and productive interactive web apps in R — no HTML/CSS/J
|
||||
* A prebuilt set of highly sophisticated, customizable, and easy-to-use widgets (e.g., plots, tables, sliders, dropdowns, date pickers, and more).
|
||||
* An attractive default look based on [Bootstrap](https://getbootstrap.com/) which can also be easily customized with the [bslib](https://github.com/rstudio/bslib) package or avoided entirely with more direct R bindings to HTML/CSS/JavaScript.
|
||||
* Seamless integration with [R Markdown](https://shiny.rstudio.com/articles/interactive-docs.html), making it easy to embed numerous applications natively within a larger dynamic document.
|
||||
* Tools for improving and monitoring performance, including native support for [async programming](https://www.rstudio.com/blog/shiny-1-1-0/), [caching](https://talks.cpsievert.me/20201117), [load testing](https://rstudio.github.io/shinyloadtest/), and more.
|
||||
* Tools for improving and monitoring performance, including native support for [async programming](https://posit.co/blog/shiny-1-1-0/), [caching](https://talks.cpsievert.me/20201117), [load testing](https://rstudio.github.io/shinyloadtest/), and more.
|
||||
* [Modules](https://shiny.rstudio.com/articles/modules.html): a framework for reducing code duplication and complexity.
|
||||
* An ability to [bookmark application state](https://shiny.rstudio.com/articles/bookmarking-state.html) and/or [generate code to reproduce output(s)](https://github.com/rstudio/shinymeta).
|
||||
* A rich ecosystem of extension packages for more [custom widgets](http://www.htmlwidgets.org/), [input validation](https://github.com/rstudio/shinyvalidate), [unit testing](https://github.com/rstudio/shinytest), and more.
|
||||
@@ -45,6 +45,10 @@ For more examples and inspiration, check out the [Shiny User Gallery](https://sh
|
||||
|
||||
For help with learning fundamental Shiny programming concepts, check out the [Mastering Shiny](https://mastering-shiny.org/) book and the [Shiny Tutorial](https://shiny.rstudio.com/tutorial/). The former is currently more up-to-date with modern Shiny features, whereas the latter takes a deeper, more visual, dive into fundamental concepts.
|
||||
|
||||
## Join the conversation
|
||||
|
||||
If you want to chat about Shiny, meet other developers, or help us decide what to work on next, [join us on Discord](https://discord.com/invite/yMGCamUMnS).
|
||||
|
||||
## Getting Help
|
||||
|
||||
To ask a question about Shiny, please use the [RStudio Community website](https://community.rstudio.com/new-topic?category=shiny&tags=shiny).
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
@charset "UTF-8";
|
||||
/* 'shiny' skin for Ion.RangeSlider, largely based on the 'big' skin, but with smaller dimensions, grayscale grid text, and without gradients
|
||||
© Posit, PBC, 2023
|
||||
© RStudio, Inc, 2014
|
||||
© Denis Ineshin, 2014 https://github.com/IonDen
|
||||
© guybowden, 2014 https://github.com/guybowden
|
||||
@@ -174,6 +175,18 @@
|
||||
background-color: #ededed;
|
||||
border: 1px solid #cccccc;
|
||||
border-radius: 8px;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.irs--shiny .irs-line::before {
|
||||
content: "";
|
||||
display: block;
|
||||
position: relative;
|
||||
cursor: s-resize;
|
||||
width: 100%;
|
||||
height: 22px;
|
||||
top: -9px;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.irs--shiny .irs-bar {
|
||||
@@ -182,12 +195,24 @@
|
||||
border-top: 1px solid #428bca;
|
||||
border-bottom: 1px solid #428bca;
|
||||
background: #428bca;
|
||||
cursor: s-resize;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.irs--shiny .irs-bar--single {
|
||||
border-radius: 8px 0 0 8px;
|
||||
}
|
||||
|
||||
.irs--shiny .irs-bar::before {
|
||||
content: "";
|
||||
display: block;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 22px;
|
||||
top: -9px;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.irs--shiny .irs-shadow {
|
||||
top: 38px;
|
||||
height: 2px;
|
||||
@@ -207,6 +232,7 @@
|
||||
background-color: #dedede;
|
||||
box-shadow: 1px 1px 3px rgba(255, 255, 255, 0.3);
|
||||
border-radius: 22px;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.irs--shiny .irs-handle.state_hover, .irs--shiny .irs-handle:hover {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
/* 'shiny' skin for Ion.RangeSlider, largely based on the 'big' skin, but with smaller dimensions, grayscale grid text, and without gradients
|
||||
© Posit, PBC, 2023
|
||||
© RStudio, Inc, 2014
|
||||
© Denis Ineshin, 2014 https://github.com/IonDen
|
||||
© guybowden, 2014 https://github.com/guybowden
|
||||
@@ -79,6 +80,19 @@ $font-family: $font-family-base !default;
|
||||
background-color: $line_bg_color;
|
||||
border: $line_border;
|
||||
@include border-radius-shim($line_height);
|
||||
|
||||
// Increase the touch target area of the slider line
|
||||
overflow: visible;
|
||||
&::before {
|
||||
content: "";
|
||||
display: block;
|
||||
position: relative;
|
||||
cursor: s-resize; // downward arrow
|
||||
width: 100%;
|
||||
height: $handle_height;
|
||||
top: -(($handle_height - $line_height / 2) / 2);
|
||||
z-index: 1; // raise touch area above grid
|
||||
}
|
||||
}
|
||||
|
||||
.#{$name}-bar {
|
||||
@@ -87,10 +101,23 @@ $font-family: $font-family-base !default;
|
||||
border-top: 1px solid $bar_color;
|
||||
border-bottom: 1px solid $bar_color;
|
||||
background: $bar_color;
|
||||
cursor: s-resize; // downard arrow, overwritten when a range
|
||||
z-index: 2;
|
||||
|
||||
&--single {
|
||||
@include border-radius-shim($line_height 0 0 $line_height);
|
||||
}
|
||||
|
||||
// Increase the touch target area of the slider bar
|
||||
&::before {
|
||||
content: "";
|
||||
display: block;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: $handle_height;
|
||||
top: -(($handle_height - $line_height / 2) / 2);
|
||||
z-index: 2; // raise touch area above line touch target
|
||||
}
|
||||
}
|
||||
|
||||
// sliderInput() doesn't currently support from_min/from_max, so this isn't relevant
|
||||
@@ -114,6 +141,7 @@ $font-family: $font-family-base !default;
|
||||
background-color: $handle_color;
|
||||
box-shadow: $handle_box_shadow;
|
||||
border-radius: $handle_width;
|
||||
z-index: 2;
|
||||
|
||||
&.state_hover,
|
||||
&:hover {
|
||||
|
||||
File diff suppressed because one or more lines are too long
3486
inst/www/shared/selectize/js/selectize.js
Normal file
3486
inst/www/shared/selectize/js/selectize.js
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
16
inst/www/shared/selectize/scss/plugins/auto_position.scss
Normal file
16
inst/www/shared/selectize/scss/plugins/auto_position.scss
Normal file
@@ -0,0 +1,16 @@
|
||||
.#{selectize}-dropdown.plugin-auto_position.#{$selectize}-position-top {
|
||||
border-top: 1px solid $select-color-border;
|
||||
border-bottom: 0 none;
|
||||
border-radius: 3px 3px 0 0;
|
||||
box-shadow: 0 -6px 12px rgba(var(--bs-body-color-rgb, 0,0,0), .18);
|
||||
}
|
||||
|
||||
.#{selectize}-control.plugin-auto_position .#{selectize}-input.#{$selectize}-position-top.dropdown-active {
|
||||
border-radius: 0 0 3px 3px;
|
||||
border-top: 0 none;
|
||||
|
||||
&::before {
|
||||
top: 0;
|
||||
bottom: unset;
|
||||
}
|
||||
}
|
||||
29
inst/www/shared/selectize/scss/plugins/clear_button.scss
Normal file
29
inst/www/shared/selectize/scss/plugins/clear_button.scss
Normal file
@@ -0,0 +1,29 @@
|
||||
.#{$selectize}-control.plugin-clear_button {
|
||||
.clear {
|
||||
text-decoration: none;
|
||||
display: flex;
|
||||
position: absolute;
|
||||
height: 100%;
|
||||
width: 25px;
|
||||
top: 0;
|
||||
right: calc(#{$select-padding-x} - #{$select-padding-item-x});
|
||||
color: var(--bs-body-color, black);
|
||||
opacity: 0.4;
|
||||
font-weight: bold;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
z-index: 1;
|
||||
font-size: 21px;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.clear:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
&.single .clear {
|
||||
right: calc(#{$select-padding-x} - #{$select-padding-item-x} + 1.5rem);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,16 +1,19 @@
|
||||
.selectize-control.plugin-drag_drop {
|
||||
&.multi > .selectize-input > div.ui-sortable-placeholder {
|
||||
visibility: visible !important;
|
||||
background: #f2f2f2 !important;
|
||||
background: rgba(0,0,0,0.06) !important;
|
||||
border: 0 none !important;
|
||||
@include selectize-box-shadow(inset 0 0 12px 4px #fff);
|
||||
.#{$selectize}-control.plugin-drag_drop {
|
||||
&.multi > .#{$selectize}-input.dragging {
|
||||
overflow: visible;
|
||||
}
|
||||
&.multi > .#{$selectize}-input > div.ui-sortable-placeholder {
|
||||
visibility: visible !important;
|
||||
background: #f2f2f2 !important;
|
||||
background: rgba(0, 0, 0, 0.06) !important;
|
||||
border: 0 none !important;
|
||||
box-shadow: inset 0 0 12px 4px #fff;
|
||||
}
|
||||
.ui-sortable-placeholder::after {
|
||||
content: '!';
|
||||
visibility: hidden;
|
||||
content: "!";
|
||||
visibility: hidden;
|
||||
}
|
||||
.ui-sortable-helper {
|
||||
@include selectize-box-shadow(0 2px 5px rgba(0,0,0,0.2));
|
||||
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,20 +1,24 @@
|
||||
.selectize-dropdown-header {
|
||||
position: relative;
|
||||
padding: $selectize-padding-dropdown-item-y $selectize-padding-dropdown-item-x;
|
||||
border-bottom: 1px solid $selectize-color-border;
|
||||
background: mix($selectize-color-dropdown, $selectize-color-border, 85%);
|
||||
@include selectize-border-radius($selectize-border-radius $selectize-border-radius 0 0);
|
||||
.#{$selectize}-dropdown.plugin-dropdown_header {
|
||||
.#{$selectize}-dropdown-header {
|
||||
position: relative;
|
||||
padding: ($select-padding-dropdown-item-y * 2)
|
||||
$select-padding-dropdown-item-x;
|
||||
border-bottom: 1px solid $select-color-border;
|
||||
background: RGBA($select-color-dropdown, 0.15);
|
||||
border-radius: $select-border-radius $select-border-radius 0 0;
|
||||
}
|
||||
.#{$selectize}-dropdown-header-close {
|
||||
position: absolute;
|
||||
right: $select-padding-dropdown-item-x;
|
||||
top: 50%;
|
||||
color: $select-color-text;
|
||||
opacity: 0.4;
|
||||
margin-top: -12px;
|
||||
line-height: 20px;
|
||||
font-size: 20px !important;
|
||||
text-decoration: none;
|
||||
}
|
||||
.#{$selectize}-dropdown-header-close:hover {
|
||||
color: RGB($select-color-rgb);
|
||||
}
|
||||
}
|
||||
.selectize-dropdown-header-close {
|
||||
position: absolute;
|
||||
right: $selectize-padding-dropdown-item-x;
|
||||
top: 50%;
|
||||
color: $selectize-color-text;
|
||||
opacity: 0.4;
|
||||
margin-top: -12px;
|
||||
line-height: 20px;
|
||||
font-size: 20px !important;
|
||||
}
|
||||
.selectize-dropdown-header-close:hover {
|
||||
color: darken($selectize-color-text, 25%);
|
||||
}
|
||||
@@ -1,17 +1,22 @@
|
||||
.selectize-dropdown.plugin-optgroup_columns {
|
||||
.#{$selectize}-dropdown.plugin-optgroup_columns {
|
||||
.#{$selectize}-dropdown-content {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.optgroup {
|
||||
border-right: 1px solid #f2f2f2;
|
||||
border-top: 0 none;
|
||||
float: left;
|
||||
@include selectize-box-sizing(border-box);
|
||||
border-right: 1px solid #f2f2f2;
|
||||
border-top: 0 none;
|
||||
flex-grow: 1;
|
||||
flex-basis: 0;
|
||||
min-width: 0;
|
||||
}
|
||||
.optgroup:last-child {
|
||||
border-right: 0 none;
|
||||
border-right: 0 none;
|
||||
}
|
||||
.optgroup:before {
|
||||
display: none;
|
||||
display: none;
|
||||
}
|
||||
.optgroup-header {
|
||||
border-top: 0 none;
|
||||
border-top: 0 none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,43 +1,38 @@
|
||||
.selectize-control.plugin-remove_button {
|
||||
[data-value] {
|
||||
position: relative;
|
||||
padding-right: 24px !important;
|
||||
.#{$selectize}-control.plugin-remove_button {
|
||||
.item {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
padding-right: 0 !important;
|
||||
}
|
||||
[data-value] .remove {
|
||||
z-index: 1; /* fixes ie bug (see #392) */
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
width: 17px;
|
||||
text-align: center;
|
||||
font-weight: bold;
|
||||
font-size: 12px;
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
vertical-align: middle;
|
||||
display: inline-block;
|
||||
padding: $selectize-padding-item-y 0 0 0;
|
||||
border-left: 1px solid $selectize-color-item-border;
|
||||
@include selectize-border-radius(0 2px 2px 0);
|
||||
@include selectize-box-sizing(border-box);
|
||||
|
||||
.item .remove {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
vertical-align: middle;
|
||||
display: inline-block;
|
||||
padding: $select-padding-item-y $select-padding-item-x;
|
||||
border-left: 1px solid $select-color-item-border;
|
||||
border-radius: 0 2px 2px 0;
|
||||
box-sizing: border-box;
|
||||
margin-left: $select-padding-item-x;
|
||||
}
|
||||
[data-value] .remove:hover {
|
||||
background: rgba(0,0,0,0.05);
|
||||
|
||||
.item .remove:hover {
|
||||
background: rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
[data-value].active .remove {
|
||||
border-left-color: $selectize-color-item-active-border;
|
||||
|
||||
.item.active .remove {
|
||||
border-left-color: $select-color-item-active-border;
|
||||
}
|
||||
.disabled [data-value] .remove:hover {
|
||||
background: none;
|
||||
|
||||
.disabled .item .remove:hover {
|
||||
background: none;
|
||||
}
|
||||
.disabled [data-value] .remove {
|
||||
border-left-color: lighten(desaturate($selectize-color-item-border, 100%), $selectize-lighten-disabled-item-border);
|
||||
}
|
||||
.remove-single {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
font-size: 23px;
|
||||
|
||||
.disabled .item .remove {
|
||||
border-left-color: lighten(
|
||||
desaturate($select-color-item-border, 100%),
|
||||
$select-lighten-disabled-item-border
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,158 +1,136 @@
|
||||
/**
|
||||
* selectize.bootstrap3.css (v0.12.3) - Bootstrap 3 Theme
|
||||
* Copyright (c) 2013–2015 Brian Reavis & contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this
|
||||
* file except in compliance with the License. You may obtain a copy of the License at:
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under
|
||||
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
|
||||
* ANY KIND, either express or implied. See the License for the specific language
|
||||
* governing permissions and limitations under the License.
|
||||
*
|
||||
* @author Brian Reavis <brian@thirdroute.com>
|
||||
*/
|
||||
|
||||
$selectize-font-family: inherit !default;
|
||||
$selectize-font-size: inherit !default;
|
||||
$selectize-line-height: $line-height-computed !default;
|
||||
$select-font-family: inherit;
|
||||
$select-font-size: inherit;
|
||||
$select-line-height: $line-height-computed;
|
||||
|
||||
$selectize-color-text: $input-color !default;
|
||||
$selectize-color-highlight: rgba(255,237,40,0.4) !default;
|
||||
$selectize-color-input: $input-bg !default;
|
||||
$selectize-color-input-full: $input-bg !default;
|
||||
$selectize-color-input-error: $state-danger-text !default;
|
||||
$selectize-color-input-error-focus: darken($selectize-color-input-error, 10%) !default;
|
||||
$selectize-color-disabled: $input-bg !default;
|
||||
$selectize-color-item: mix($selectize-color-input, $selectize-color-text, 90%) !default;
|
||||
$selectize-color-item-border: rgba(black, 0) !default;
|
||||
$selectize-color-item-active: $component-active-bg !default;
|
||||
$selectize-color-item-active-text: color-contrast($selectize-color-item-active) !default;
|
||||
$selectize-color-item-active-border: rgba(black, 0) !default;
|
||||
$selectize-color-optgroup: $dropdown-bg !default;
|
||||
$selectize-color-optgroup-text: $dropdown-header-color !default;
|
||||
$selectize-color-optgroup-border: $dropdown-divider-bg !default;
|
||||
$selectize-color-dropdown: $dropdown-bg !default;
|
||||
$selectize-color-dropdown-text: $dropdown-link-color !default;
|
||||
$selectize-color-dropdown-border-top: mix($input-border, $input-bg, 0.8) !default;
|
||||
$selectize-color-dropdown-item-active: $dropdown-link-hover-bg !default;
|
||||
$selectize-color-dropdown-item-active-text: $dropdown-link-hover-color !default;
|
||||
$selectize-color-dropdown-item-create-active-text: $dropdown-link-hover-color !default;
|
||||
$selectize-opacity-disabled: 0.5 !default;
|
||||
$selectize-shadow-input: none !default;
|
||||
$selectize-shadow-input-focus: inset 0 1px 2px rgba(black, 0.15) !default;
|
||||
$selectize-shadow-input-error: inset 0 1px 1px rgba(black, .075) !default;
|
||||
$selectize-shadow-input-error-focus: inset 0 1px 1px rgba(black, .075), 0 0 6px lighten($selectize-color-input-error, 20%) !default;
|
||||
$selectize-border: 1px solid $input-border !default;
|
||||
$selectize-border-radius: $input-border-radius !default;
|
||||
$select-color-text: $text-color;
|
||||
$select-color-highlight: rgba(255, 237, 40, 0.4);
|
||||
$select-color-input: $input-bg;
|
||||
$select-color-input-full: $input-bg;
|
||||
$select-color-input-error: $state-danger-text;
|
||||
$select-color-input-error-focus: darken($select-color-input-error, 10%);
|
||||
$select-color-disabled: $input-bg;
|
||||
$select-color-item: RGBA($select-color-text, 0.1);
|
||||
$select-color-item-border: rgba(0, 0, 0, 0);
|
||||
$select-color-item-active: $component-active-bg;
|
||||
$select-color-item-active-text: color-contrast($select-color-item-active);
|
||||
$select-color-item-active-border: rgba(0, 0, 0, 0);
|
||||
$select-color-optgroup: $dropdown-bg;
|
||||
$select-color-optgroup-text: $dropdown-header-color;
|
||||
$select-color-optgroup-border: $dropdown-divider-bg;
|
||||
$select-color-dropdown: $dropdown-bg;
|
||||
$select-color-dropdown-border-top: mix($input-border, $input-bg, 80%);
|
||||
$select-color-dropdown-item-active: $dropdown-link-hover-bg;
|
||||
$select-color-dropdown-item-active-text: $dropdown-link-hover-color;
|
||||
$select-color-dropdown-item-create-active-text: $dropdown-link-hover-color;
|
||||
$select-opacity-disabled: 0.5;
|
||||
$select-shadow-input: none;
|
||||
$select-shadow-input-focus: inset 0 1px 2px rgba(0, 0, 0, 0.15);
|
||||
$select-shadow-input-error: inset 0 1px 1px rgba(0, 0, 0, 0.075);
|
||||
$select-shadow-input-error-focus: inset 0 1px 1px rgba(0, 0, 0, 0.075),
|
||||
0 0 6px lighten($select-color-input-error, 20%);
|
||||
$select-border: 1px solid $input-border;
|
||||
$select-border-radius: $input-border-radius;
|
||||
|
||||
$selectize-width-item-border: 0 !default;
|
||||
$selectize-padding-x: $padding-base-horizontal !default;
|
||||
$selectize-padding-y: $padding-base-vertical !default;
|
||||
$selectize-padding-dropdown-item-x: $padding-base-horizontal !default;
|
||||
$selectize-padding-dropdown-item-y: 3px !default;
|
||||
$selectize-padding-item-x: 3px !default;
|
||||
$selectize-padding-item-y: 1px !default;
|
||||
$selectize-margin-item-x: 3px !default;
|
||||
$selectize-margin-item-y: 3px !default;
|
||||
$selectize-caret-margin: 0 !default;
|
||||
$select-width-item-border: 0px;
|
||||
$select-padding-x: $padding-base-horizontal;
|
||||
$select-padding-y: $padding-base-vertical;
|
||||
$select-padding-dropdown-item-x: $padding-base-horizontal;
|
||||
$select-padding-dropdown-item-y: 3px;
|
||||
$select-padding-item-x: 5px;
|
||||
$select-padding-item-y: 1px;
|
||||
$select-margin-item-x: 3px;
|
||||
$select-margin-item-y: 3px;
|
||||
|
||||
$selectize-arrow-size: 5px !default;
|
||||
$selectize-arrow-color: $selectize-color-text !default;
|
||||
$selectize-arrow-offset: $selectize-padding-x + 5px !default;
|
||||
$select-arrow-size: 5px;
|
||||
$select-arrow-color: $select-color-text;
|
||||
$select-arrow-offset: $select-padding-x + 5px;
|
||||
|
||||
@import "selectize";
|
||||
|
||||
//Import Plugins
|
||||
@import "plugins/drag_drop";
|
||||
@import "plugins/dropdown_header";
|
||||
@import "plugins/optgroup_columns";
|
||||
@import "plugins/remove_button";
|
||||
|
||||
.selectize-dropdown, .selectize-dropdown.form-control {
|
||||
height: auto;
|
||||
padding: 0;
|
||||
margin: 2px 0 0 0;
|
||||
z-index: $zindex-dropdown;
|
||||
background: $selectize-color-dropdown;
|
||||
color: $selectize-color-dropdown-text;
|
||||
border: 1px solid $dropdown-fallback-border;
|
||||
border: 1px solid $dropdown-border;
|
||||
@include selectize-border-radius ($border-radius-base);
|
||||
@include selectize-box-shadow (0 6px 12px rgba(black, .175));
|
||||
.#{$selectize}-dropdown,
|
||||
.#{$selectize}-dropdown.form-control {
|
||||
height: auto;
|
||||
padding: 0;
|
||||
margin: 2px 0 0 0;
|
||||
z-index: $zindex-dropdown;
|
||||
background: $select-color-dropdown;
|
||||
border: 1px solid $dropdown-fallback-border;
|
||||
border: 1px solid $dropdown-border;
|
||||
border-radius: $border-radius-base;
|
||||
box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175);
|
||||
}
|
||||
|
||||
.selectize-dropdown {
|
||||
.optgroup-header {
|
||||
font-size: $font-size-small;
|
||||
line-height: $line-height-base;
|
||||
}
|
||||
.optgroup:first-child:before {
|
||||
display: none;
|
||||
}
|
||||
.optgroup:before {
|
||||
content: ' ';
|
||||
display: block;
|
||||
@include nav-divider;
|
||||
margin-left: $selectize-padding-dropdown-item-x * -1;
|
||||
margin-right: $selectize-padding-dropdown-item-x * -1;
|
||||
}
|
||||
.#{$selectize}-dropdown {
|
||||
.optgroup-header {
|
||||
font-size: $font-size-small;
|
||||
line-height: $line-height-base;
|
||||
}
|
||||
.optgroup:first-child:before {
|
||||
display: none;
|
||||
}
|
||||
.optgroup:before {
|
||||
content: " ";
|
||||
display: block;
|
||||
@include nav-divider();
|
||||
margin-left: $select-padding-dropdown-item-x * -1;
|
||||
margin-right: $select-padding-dropdown-item-x * -1;
|
||||
}
|
||||
}
|
||||
|
||||
.selectize-dropdown-content {
|
||||
padding: 5px 0;
|
||||
.#{$selectize}-dropdown-content {
|
||||
padding: 5px 0;
|
||||
}
|
||||
|
||||
.selectize-dropdown-header {
|
||||
padding: $selectize-padding-dropdown-item-y * 2 $selectize-padding-dropdown-item-x;
|
||||
.#{$selectize}-dropdown-emptyoptionlabel {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.selectize-input {
|
||||
min-height: $input-height-base;
|
||||
.#{$selectize}-input {
|
||||
min-height: $input-height-base;
|
||||
|
||||
&.dropdown-active {
|
||||
@include selectize-border-radius ($selectize-border-radius);
|
||||
&.dropdown-active {
|
||||
border-radius: $select-border-radius;
|
||||
}
|
||||
&.dropdown-active::before {
|
||||
display: none;
|
||||
}
|
||||
&.focus {
|
||||
$color: $input-border-focus;
|
||||
$color-rgba: rgba(red($color), green($color), blue($color), 0.6);
|
||||
border-color: $color;
|
||||
outline: 0;
|
||||
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px $color-rgba;
|
||||
}
|
||||
}
|
||||
|
||||
.has-error .#{$selectize}-input {
|
||||
border-color: $select-color-input-error;
|
||||
box-shadow: $select-shadow-input-error;
|
||||
|
||||
&:focus {
|
||||
border-color: $select-color-input-error-focus;
|
||||
box-shadow: $select-shadow-input-error-focus;
|
||||
}
|
||||
}
|
||||
|
||||
.#{$selectize}-control {
|
||||
&.multi {
|
||||
.#{$selectize}-input.has-items {
|
||||
padding-left: $select-padding-x - $select-padding-item-x;
|
||||
padding-right: $select-padding-x - $select-padding-item-x;
|
||||
}
|
||||
&.dropdown-active::before {
|
||||
display: none;
|
||||
}
|
||||
&.focus {
|
||||
$color: $input-border-focus;
|
||||
$color-rgba: rgba(red($color), green($color), blue($color), .6);
|
||||
border-color: $color;
|
||||
outline: 0;
|
||||
@include selectize-box-shadow ("inset 0 1px 1px rgba(black, .075), 0 0 8px #{$color-rgba}");
|
||||
.#{$selectize}-input > div {
|
||||
border-radius: $select-border-radius - 1px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.has-error .selectize-input {
|
||||
border-color: $selectize-color-input-error;
|
||||
@include selectize-box-shadow ($selectize-shadow-input-error);
|
||||
|
||||
&:focus {
|
||||
border-color: $selectize-color-input-error-focus;
|
||||
@include selectize-box-shadow ($selectize-shadow-input-error-focus);
|
||||
}
|
||||
}
|
||||
|
||||
.selectize-control {
|
||||
&.multi {
|
||||
.selectize-input.has-items {
|
||||
padding-left: $selectize-padding-x - $selectize-padding-item-x;
|
||||
padding-right: $selectize-padding-x - $selectize-padding-item-x;
|
||||
}
|
||||
.selectize-input > div {
|
||||
@include selectize-border-radius ($selectize-border-radius - 1px);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.form-control.selectize-control {
|
||||
padding: 0;
|
||||
height: auto;
|
||||
border: none;
|
||||
background: none;
|
||||
@include selectize-box-shadow (none);
|
||||
@include selectize-border-radius (0);
|
||||
.form-control.#{$selectize}-control {
|
||||
padding: 0;
|
||||
height: auto;
|
||||
border: none;
|
||||
background: none;
|
||||
box-shadow: none;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
@@ -1,118 +1,112 @@
|
||||
/**
|
||||
* Selectize bootstrap 4
|
||||
*/
|
||||
|
||||
//Import Boostrap 4 functions and variables
|
||||
|
||||
$enable-shadows: true !default;
|
||||
$selectize-font-family: inherit !default;
|
||||
$selectize-font-size: inherit !default;
|
||||
$selectize-line-height: $input-btn-line-height !default; //formerly line-height-computed
|
||||
$select-font-family: inherit !default;
|
||||
$select-font-size: inherit !default;
|
||||
$select-line-height: $input-btn-line-height !default; //formerly line-height-computed
|
||||
|
||||
$selectize-color-text: $input-color !default;
|
||||
$selectize-color-highlight: rgba(255,237,40,0.4) !default;
|
||||
$selectize-color-input: $input-bg !default;
|
||||
$selectize-color-input-full: $input-bg !default;
|
||||
$selectize-color-input-error: $danger !default;
|
||||
$selectize-color-input-error-focus: darken($selectize-color-input-error, 10%) !default;
|
||||
$selectize-color-disabled: $input-bg !default;
|
||||
$selectize-color-item: mix($selectize-color-input, $selectize-color-text, 90%) !default;
|
||||
$selectize-color-item-border: $input-border-color !default;
|
||||
$selectize-color-item-active: $component-active-bg !default;
|
||||
$selectize-color-item-active-text: color-contrast($selectize-color-item-active) !default;
|
||||
$selectize-color-item-active-border: rgba(0,0,0,0) !default;
|
||||
$selectize-color-optgroup: $dropdown-bg !default;
|
||||
$selectize-color-optgroup-text: $dropdown-header-color !default;
|
||||
$selectize-color-optgroup-border: $dropdown-divider-bg !default;
|
||||
$selectize-color-dropdown: $dropdown-bg !default;
|
||||
$selectize-color-dropdown-text: $dropdown-link-color !default;
|
||||
$selectize-color-dropdown-border-top: mix($input-border-color, $input-bg, 0.8) !default;
|
||||
$selectize-color-dropdown-item-active: $dropdown-link-hover-bg !default;
|
||||
$selectize-color-dropdown-item-active-text: $dropdown-link-hover-color !default;
|
||||
$selectize-color-dropdown-item-create-active-text: $dropdown-link-hover-color !default;
|
||||
$selectize-opacity-disabled: 0.5 !default;
|
||||
$selectize-shadow-input: none !default;
|
||||
$selectize-shadow-input-focus: inset 0 1px 2px rgba(0,0,0,0.15) !default;
|
||||
$selectize-shadow-input-error: inset 0 1px 1px rgba(0, 0, 0, .075) !default;
|
||||
$selectize-shadow-input-error-focus: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 6px lighten($selectize-color-input-error, 20%) !default;
|
||||
$selectize-border: 1px solid $input-border-color !default;
|
||||
$selectize-border-radius: $input-border-radius !default;
|
||||
$select-color-text: gray("800") !default; //$gray-800
|
||||
$select-color-highlight: rgba(255, 237, 40, 0.4) !default;
|
||||
$select-color-input: $input-bg !default;
|
||||
$select-color-input-full: $input-bg !default;
|
||||
$select-color-input-error: theme-color("danger") !default;
|
||||
$select-color-input-error-focus: darken(
|
||||
$select-color-input-error,
|
||||
10%
|
||||
) !default;
|
||||
$select-color-disabled: $input-bg !default;
|
||||
$select-color-item: RGBA($select-color-text, 0.1) !default;
|
||||
$select-color-item-border: $border-color !default;
|
||||
$select-color-item-active: $component-active-bg !default;
|
||||
$select-color-item-active-text: color-contrast($select-color-item-active) !default;
|
||||
$select-color-item-active-border: rgba(0, 0, 0, 0) !default;
|
||||
$select-color-optgroup: $dropdown-bg !default;
|
||||
$select-color-optgroup-text: $dropdown-header-color !default;
|
||||
$select-color-optgroup-border: $dropdown-divider-bg !default;
|
||||
$select-color-dropdown: $dropdown-bg !default;
|
||||
$select-color-dropdown-border-top: mix(
|
||||
$input-border-color,
|
||||
$input-bg,
|
||||
80%
|
||||
) !default;
|
||||
$select-color-dropdown-item-active: $dropdown-link-hover-bg !default;
|
||||
$select-color-dropdown-item-active-text: $dropdown-link-hover-color !default;
|
||||
$select-color-dropdown-item-create-active-text: $dropdown-link-hover-color !default;
|
||||
$select-opacity-disabled: 0.5 !default;
|
||||
$select-shadow-input: none !default;
|
||||
$select-shadow-input-focus: inset 0 1px 2px rgba(0, 0, 0, 0.15) !default;
|
||||
$select-shadow-input-error: inset 0 1px 1px rgba(0, 0, 0, 0.075) !default;
|
||||
$select-shadow-input-error-focus: inset 0 1px 1px rgba(0, 0, 0, 0.075),
|
||||
0 0 6px lighten($select-color-input-error, 20%) !default;
|
||||
$select-border: 1px solid $input-border-color !default;
|
||||
$select-border-radius: $input-border-radius !default;
|
||||
|
||||
$selectize-width-item-border: 0px !default;
|
||||
$selectize-padding-x: $input-btn-padding-x !default;
|
||||
$selectize-padding-y: $input-btn-padding-y !default;
|
||||
$selectize-padding-dropdown-item-x: $input-btn-padding-x !default;
|
||||
$selectize-padding-dropdown-item-y: 3px !default;
|
||||
$selectize-padding-item-x: 3px !default;
|
||||
$selectize-padding-item-y: 1px !default;
|
||||
$selectize-margin-item-x: 3px !default;
|
||||
$selectize-margin-item-y: 3px !default;
|
||||
$selectize-caret-margin: 0 !default;
|
||||
|
||||
$selectize-arrow-size: 5px !default;
|
||||
$selectize-arrow-color: $selectize-color-text !default;
|
||||
$selectize-arrow-offset: calc(#{$selectize-padding-x} + 5px) !default;
|
||||
$select-width-item-border: 0px !default;
|
||||
$select-padding-x: $input-btn-padding-x !default;
|
||||
$select-padding-y: $input-btn-padding-y !default;
|
||||
$select-padding-dropdown-item-x: $input-btn-padding-x !default;
|
||||
$select-padding-dropdown-item-y: 3px !default;
|
||||
$select-padding-item-x: 5px !default;
|
||||
$select-padding-item-y: 1px !default;
|
||||
$select-margin-item-x: 3px !default;
|
||||
$select-margin-item-y: 3px !default;
|
||||
|
||||
$select-arrow-size: 5px !default;
|
||||
$select-arrow-color: $select-color-text !default;
|
||||
$select-arrow-offset: calc(#{$select-padding-x} + 5px) !default;
|
||||
|
||||
@import "selectize";
|
||||
|
||||
//Import Plugins
|
||||
@import "plugins/drag_drop";
|
||||
@import "plugins/dropdown_header";
|
||||
@import "plugins/optgroup_columns";
|
||||
@import "plugins/remove_button";
|
||||
|
||||
.selectize-dropdown, .selectize-dropdown.form-control {
|
||||
.#{$selectize}-dropdown,
|
||||
.#{$selectize}-dropdown.form-control {
|
||||
height: auto;
|
||||
padding: 0;
|
||||
margin: 2px 0 0 0;
|
||||
z-index: $zindex-dropdown;
|
||||
background: $selectize-color-dropdown;
|
||||
color: $selectize-color-dropdown-text;
|
||||
background: $select-color-dropdown;
|
||||
border: 1px solid $dropdown-border-color; //$dropdown-fallback-border
|
||||
@include selectize-border-radius($border-radius);
|
||||
@include selectize-box-shadow(0 6px 12px rgba(0,0,0,.175));
|
||||
border-radius: $border-radius;
|
||||
box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175);
|
||||
}
|
||||
|
||||
.selectize-dropdown {
|
||||
.#{$selectize}-dropdown {
|
||||
.optgroup-header {
|
||||
font-size: $font-size-sm;
|
||||
line-height: $line-height-base;
|
||||
font-size: $font-size-sm;
|
||||
line-height: $line-height-base;
|
||||
}
|
||||
.optgroup:first-child:before {
|
||||
display: none;
|
||||
display: none;
|
||||
}
|
||||
.optgroup:before {
|
||||
content: ' ';
|
||||
display: block;
|
||||
height: 0;
|
||||
margin: $dropdown-divider-margin-y 0;
|
||||
overflow: hidden;
|
||||
border-top: 1px solid $dropdown-divider-bg;
|
||||
margin-left: $selectize-padding-dropdown-item-x * -1;
|
||||
margin-right: $selectize-padding-dropdown-item-x * -1;
|
||||
content: " ";
|
||||
display: block;
|
||||
height: 0;
|
||||
margin: $dropdown-divider-margin-y 0;
|
||||
overflow: hidden;
|
||||
border-top: 1px solid $dropdown-divider-bg;
|
||||
margin-left: $select-padding-dropdown-item-x * -1;
|
||||
margin-right: $select-padding-dropdown-item-x * -1;
|
||||
}
|
||||
|
||||
.create {
|
||||
padding-left: $selectize-padding-dropdown-item-x;
|
||||
padding-left: $select-padding-dropdown-item-x;
|
||||
}
|
||||
}
|
||||
|
||||
.selectize-dropdown-content {
|
||||
.#{$selectize}-dropdown-content {
|
||||
padding: 5px 0;
|
||||
}
|
||||
|
||||
.selectize-dropdown-header {
|
||||
padding: $selectize-padding-dropdown-item-y * 2 $selectize-padding-dropdown-item-x;
|
||||
.#{$selectize}-dropdown-emptyoptionlabel {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.selectize-input {
|
||||
.#{$selectize}-input {
|
||||
min-height: $input-height;
|
||||
@include box-shadow($input-box-shadow);
|
||||
@include transition($input-transition);
|
||||
|
||||
&.dropdown-active {
|
||||
@include selectize-border-radius($selectize-border-radius);
|
||||
border-radius: $select-border-radius;
|
||||
}
|
||||
&.dropdown-active::before {
|
||||
display: none;
|
||||
@@ -128,22 +122,21 @@ $selectize-arrow-offset: calc(#{$selectize-padding-x} + 5px) !default;
|
||||
}
|
||||
}
|
||||
|
||||
.is-invalid .selectize-input {
|
||||
border-color: $selectize-color-input-error;
|
||||
@include selectize-box-shadow($selectize-shadow-input-error);
|
||||
.is-invalid .#{$selectize}-input {
|
||||
border-color: $select-color-input-error;
|
||||
box-shadow: $select-shadow-input-error;
|
||||
|
||||
&:focus {
|
||||
border-color: $selectize-color-input-error-focus;
|
||||
// @include selectize-box-shadow(none)
|
||||
@include selectize-box-shadow($selectize-shadow-input-error-focus);
|
||||
border-color: $select-color-input-error-focus;
|
||||
box-shadow: $select-shadow-input-error-focus;
|
||||
}
|
||||
}
|
||||
|
||||
.selectize-control {
|
||||
&.form-control-sm{
|
||||
.selectize-input.has-items{
|
||||
.#{$selectize}-control {
|
||||
&.form-control-sm {
|
||||
.#{$selectize}-input.has-items {
|
||||
min-height: $input-height-sm !important;
|
||||
height: $input-height-sm !important;
|
||||
height: $input-height-sm;
|
||||
padding: $input-padding-y-sm $input-padding-x-sm !important;
|
||||
font-size: $input-font-size-sm;
|
||||
line-height: $input-line-height-sm;
|
||||
@@ -151,26 +144,53 @@ $selectize-arrow-offset: calc(#{$selectize-padding-x} + 5px) !default;
|
||||
}
|
||||
|
||||
&.multi {
|
||||
.selectize-input.has-items {
|
||||
padding-left: calc(#{$selectize-padding-x} - #{$selectize-padding-item-x});
|
||||
padding-right: calc(#{$selectize-padding-x} - #{$selectize-padding-item-x});
|
||||
}
|
||||
.selectize-input > div {
|
||||
@include selectize-border-radius(calc(#{$selectize-border-radius} - 1px));
|
||||
}
|
||||
.#{$selectize}-input.has-items {
|
||||
height: auto;
|
||||
padding-left: calc(#{$select-padding-x} - #{$select-padding-item-x});
|
||||
padding-right: calc(#{$select-padding-x} - #{$select-padding-item-x});
|
||||
}
|
||||
.#{$selectize}-input > div {
|
||||
border-radius: calc(#{$select-border-radius} - 1px);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.form-control.selectize-control {
|
||||
.form-control.#{$selectize}-control {
|
||||
padding: 0;
|
||||
height: auto;
|
||||
border: none;
|
||||
background: none;
|
||||
@include selectize-box-shadow(none);
|
||||
@include selectize-border-radius(0);
|
||||
box-shadow: none;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.input-group .selectize-input {
|
||||
overflow: unset;
|
||||
@include selectize-border-radius(0 $selectize-border-radius $selectize-border-radius 0);
|
||||
.input-group .#{$selectize}-control:not(:last-child) {
|
||||
.#{$selectize}-input{
|
||||
overflow: unset;
|
||||
border-top-right-radius: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.input-group .#{$selectize}-control:not(:first-child) {
|
||||
.#{$selectize}-input{
|
||||
overflow: unset;
|
||||
border-top-left-radius: 0;
|
||||
border-bottom-left-radius: 0;
|
||||
}
|
||||
}
|
||||
|
||||
// .input-group .#{$selectize}-input {
|
||||
// overflow: unset;
|
||||
// border-radius: 0 $select-border-radius $select-border-radius 0;
|
||||
// }
|
||||
|
||||
.#{selectize}-dropdown.plugin-auto_position.#{$selectize}-position-top {
|
||||
border-top: $select-border!important;
|
||||
border-bottom: $select-border!important;
|
||||
border-radius: $select-border-radius!important;
|
||||
}
|
||||
.#{selectize}-control.plugin-auto_position .#{selectize}-input.#{$selectize}-position-top.dropdown-active {
|
||||
border-radius: $select-border-radius!important;
|
||||
border-top: $select-border!important;
|
||||
}
|
||||
|
||||
@@ -1,3 +1,200 @@
|
||||
$input-line-height-sm: $form-select-line-height !default;
|
||||
@import 'selectize.bootstrap4';
|
||||
.selectize-control{padding:0;}
|
||||
|
||||
$enable-shadows: true !default;
|
||||
$select-font-family: if($input-btn-font-family, $input-btn-font-family, inherit) !default;
|
||||
$select-font-size: $input-btn-font-size !default;
|
||||
$select-line-height: $input-btn-line-height !default; //formerly line-height-computed
|
||||
|
||||
$select-color-rgb: var(--bs-emphasis-color-rgb, 0, 0, 0) !default;
|
||||
$select-color-text: RGBA($select-color-rgb, 0.83) !default;
|
||||
$select-color-highlight: rgba(255, 237, 40, 0.4) !default;
|
||||
$select-color-input: $input-bg !default;
|
||||
$select-color-input-full: $input-bg !default;
|
||||
$select-color-input-error: $danger !default;
|
||||
$select-color-input-error-focus: darken(
|
||||
$select-color-input-error,
|
||||
10%
|
||||
) !default;
|
||||
$select-color-disabled: $input-bg !default;
|
||||
$select-color-item: RGBA($select-color-rgb, 0.05) !default;
|
||||
$select-color-item-border: $border-color !default;
|
||||
$select-color-item-active: $component-active-bg !default;
|
||||
$select-color-item-active-text: color-contrast($select-color-item-active) !default;
|
||||
$select-color-item-active-border: rgba(0, 0, 0, 0) !default;
|
||||
$select-color-optgroup: $dropdown-bg !default;
|
||||
$select-color-optgroup-text: $dropdown-header-color !default;
|
||||
$select-color-optgroup-border: $dropdown-divider-bg !default;
|
||||
$select-color-dropdown: $dropdown-bg !default;
|
||||
$select-color-dropdown-border-top: rgba($input-border-color, 0.8) !default;
|
||||
$select-color-dropdown-item-active: $dropdown-link-hover-bg !default;
|
||||
$select-color-dropdown-item-active-text: $dropdown-link-hover-color !default;
|
||||
$select-color-dropdown-item-create-active-text: $dropdown-link-hover-color !default;
|
||||
$select-opacity-disabled: 0.5 !default;
|
||||
$select-shadow-input: none !default;
|
||||
$select-shadow-input-focus: inset 0 1px 2px rgba(0, 0, 0, 0.15) !default;
|
||||
$select-shadow-input-error: inset 0 1px 1px rgba(0, 0, 0, 0.075) !default;
|
||||
$select-shadow-input-error-focus: inset 0 1px 1px rgba(0, 0, 0, 0.075),
|
||||
0 0 6px lighten($select-color-input-error, 20%) !default;
|
||||
$select-border: 1px solid $input-border-color !default;
|
||||
$select-border-radius: $input-border-radius !default;
|
||||
|
||||
$select-width-item-border: 0px !default;
|
||||
$select-padding-x: $input-btn-padding-x !default;
|
||||
$select-padding-y: $input-btn-padding-y !default;
|
||||
$select-padding-dropdown-item-x: $input-btn-padding-x !default;
|
||||
$select-padding-dropdown-item-y: 3px !default;
|
||||
$select-padding-item-x: 5px !default;
|
||||
$select-padding-item-y: 1px !default;
|
||||
$select-margin-item-x: 3px !default;
|
||||
$select-margin-item-y: 3px !default;
|
||||
|
||||
$select-arrow-size: 5px !default;
|
||||
$select-arrow-color: $select-color-text !default;
|
||||
$select-arrow-offset: calc(#{$select-padding-x} + 5px) !default;
|
||||
|
||||
@import "selectize";
|
||||
|
||||
.#{$selectize}-dropdown,
|
||||
.#{$selectize}-dropdown.form-control {
|
||||
height: auto;
|
||||
padding: 0;
|
||||
margin: 2px 0 0 0;
|
||||
z-index: $zindex-dropdown;
|
||||
background: $select-color-dropdown;
|
||||
border: 1px solid $dropdown-border-color; //$dropdown-fallback-border
|
||||
border-radius: $border-radius;
|
||||
box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175);
|
||||
}
|
||||
|
||||
.#{$selectize}-dropdown {
|
||||
.optgroup-header {
|
||||
font-size: $font-size-sm;
|
||||
line-height: $line-height-base;
|
||||
}
|
||||
.optgroup:first-child:before {
|
||||
display: none;
|
||||
}
|
||||
.optgroup:before {
|
||||
content: " ";
|
||||
display: block;
|
||||
height: 0;
|
||||
margin: $dropdown-divider-margin-y 0;
|
||||
overflow: hidden;
|
||||
border-top: 1px solid $dropdown-divider-bg;
|
||||
margin-left: $select-padding-dropdown-item-x * -1;
|
||||
margin-right: $select-padding-dropdown-item-x * -1;
|
||||
}
|
||||
|
||||
.create {
|
||||
padding-left: $select-padding-dropdown-item-x;
|
||||
}
|
||||
}
|
||||
|
||||
.#{$selectize}-dropdown-content {
|
||||
padding: 5px 0;
|
||||
}
|
||||
|
||||
.#{$selectize}-dropdown-emptyoptionlabel {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.#{$selectize}-input {
|
||||
min-height: $input-height;
|
||||
@include box-shadow($input-box-shadow);
|
||||
@include transition($input-transition);
|
||||
|
||||
&.dropdown-active {
|
||||
border-radius: $select-border-radius;
|
||||
}
|
||||
&.dropdown-active::before {
|
||||
display: none;
|
||||
}
|
||||
&.focus {
|
||||
border-color: $input-focus-border-color;
|
||||
outline: 0;
|
||||
@if $enable-shadows {
|
||||
box-shadow: $input-box-shadow, $input-focus-box-shadow;
|
||||
} @else {
|
||||
box-shadow: $input-focus-box-shadow;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.is-invalid .#{$selectize}-input {
|
||||
border-color: $select-color-input-error;
|
||||
box-shadow: $select-shadow-input-error;
|
||||
|
||||
&:focus {
|
||||
border-color: $select-color-input-error-focus;
|
||||
box-shadow: $select-shadow-input-error-focus;
|
||||
}
|
||||
}
|
||||
|
||||
.#{$selectize}-control {
|
||||
&.form-control-sm {
|
||||
.#{$selectize}-input {
|
||||
min-height: $input-height-sm !important;
|
||||
height: $input-height-sm;
|
||||
padding: $input-padding-y-sm $input-padding-x-sm !important;
|
||||
font-size: $input-font-size-sm;
|
||||
line-height: $line-height-sm;
|
||||
}
|
||||
}
|
||||
|
||||
&.multi {
|
||||
.#{$selectize}-input {
|
||||
height: auto;
|
||||
padding-left: calc(#{$select-padding-x} - #{$select-padding-item-x});
|
||||
padding-right: calc(#{$select-padding-x} - #{$select-padding-item-x});
|
||||
}
|
||||
.#{$selectize}-input > div {
|
||||
border-radius: calc(#{$select-border-radius} - 1px);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.form-select.#{$selectize}-control,
|
||||
.form-control.#{$selectize}-control {
|
||||
padding: 0;
|
||||
height: auto;
|
||||
border: none;
|
||||
background: none;
|
||||
box-shadow: none;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.input-group>.input-group-append>.btn, .input-group>.form-control:not(:first-child) {
|
||||
border-top-left-radius: 0;
|
||||
border-bottom-left-radius: 0;
|
||||
}
|
||||
|
||||
.input-group>.input-group-prepend>.btn {
|
||||
border-top-right-radius: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
}
|
||||
|
||||
|
||||
.input-group .#{$selectize}-control:not(:last-child) {
|
||||
.#{$selectize}-input{
|
||||
overflow: unset;
|
||||
border-top-right-radius: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.input-group .#{$selectize}-control:not(:first-child) {
|
||||
.#{$selectize}-input{
|
||||
overflow: unset;
|
||||
border-top-left-radius: 0;
|
||||
border-bottom-left-radius: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.#{selectize}-dropdown.plugin-auto_position.#{$selectize}-position-top {
|
||||
border-top: $select-border!important;
|
||||
border-bottom: $select-border!important;
|
||||
border-radius: $select-border-radius!important;
|
||||
}
|
||||
.#{selectize}-control.plugin-auto_position .#{selectize}-input.#{$selectize}-position-top.dropdown-active {
|
||||
border-radius: $select-border-radius!important;
|
||||
border-top: $select-border!important;
|
||||
}
|
||||
|
||||
@@ -1,84 +1,71 @@
|
||||
/**
|
||||
* selectize.default.css (v$$version) - Default Theme
|
||||
* Copyright (c) 2013–2015 Brian Reavis & contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this
|
||||
* file except in compliance with the License. You may obtain a copy of the License at:
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under
|
||||
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
|
||||
* ANY KIND, either express or implied. See the License for the specific language
|
||||
* governing permissions and limitations under the License.
|
||||
*
|
||||
* $author Brian Reavis <brian$thirdroute.com>
|
||||
*/
|
||||
$select-color-item: #1da7ee;
|
||||
$select-color-item-text: #fff;
|
||||
$select-color-item-active-text: #fff;
|
||||
$select-color-item-border: #0073bb;
|
||||
$select-color-item-active: #92c836;
|
||||
$select-color-item-active-border: #00578d;
|
||||
$select-width-item-border: 1px;
|
||||
|
||||
@import "selectize";
|
||||
@import "selectize.scss";
|
||||
|
||||
$selectize-color-item: #1da7ee;
|
||||
$selectize-color-item-text: #fff;
|
||||
$selectize-color-item-active-text: #fff;
|
||||
$selectize-color-item-border: #0073bb;
|
||||
$selectize-color-item-active: #92c836;
|
||||
$selectize-color-item-active-border: #00578d;
|
||||
$selectize-width-item-border: 1px;
|
||||
$selectize-caret-margin: 0 1px;
|
||||
|
||||
.selectize-control {
|
||||
.#{$selectize}-control {
|
||||
&.multi {
|
||||
.selectize-input {
|
||||
&.has-items {
|
||||
$padding-x: $selectize-padding-x - 3px;
|
||||
padding-left: $padding-x;
|
||||
padding-right: $padding-x;
|
||||
}
|
||||
&.disabled [data-value] {
|
||||
color: #999;
|
||||
text-shadow: none;
|
||||
background: none;
|
||||
@include selectize-box-shadow(none);
|
||||
.#{$selectize}-input {
|
||||
&.has-items {
|
||||
$padding-x: $select-padding-x - 3px;
|
||||
padding-left: $padding-x;
|
||||
padding-right: $padding-x;
|
||||
}
|
||||
&.disabled [data-value] {
|
||||
color: #999;
|
||||
text-shadow: none;
|
||||
background: none;
|
||||
box-shadow: none;
|
||||
|
||||
&, .remove {
|
||||
border-color: #e6e6e6;
|
||||
}
|
||||
.remove {
|
||||
background: none;
|
||||
}
|
||||
}
|
||||
[data-value] {
|
||||
text-shadow: 0 1px 0 rgba(0,51,83,0.3);
|
||||
@include selectize-border-radius(3px);
|
||||
@include selectize-vertical-gradient(#1da7ee, #178ee9);
|
||||
@include selectize-box-shadow(#{"0 1px 0 rgba(0,0,0,0.2),inset 0 1px rgba(255,255,255,0.03)"});
|
||||
&.active {
|
||||
@include selectize-vertical-gradient(#008fd8, #0075cf);
|
||||
}
|
||||
}
|
||||
}
|
||||
&,
|
||||
.remove {
|
||||
border-color: #e6e6e6;
|
||||
}
|
||||
.remove {
|
||||
background: none;
|
||||
}
|
||||
}
|
||||
[data-value] {
|
||||
text-shadow: 0 1px 0 rgba(0, 51, 83, 0.3);
|
||||
border-radius: 3px;
|
||||
@include selectize-vertical-gradient(#1da7ee, #178ee9);
|
||||
box-shadow: 0 1px 0 rgba(0, 0, 0, 0.2),
|
||||
inset 0 1px rgba(255, 255, 255, 0.03);
|
||||
&.active {
|
||||
@include selectize-vertical-gradient(#008fd8, #0075cf);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
&.single {
|
||||
.selectize-input {
|
||||
@include selectize-box-shadow(#{"0 1px 0 rgba(0,0,0,0.05), inset 0 1px 0 rgba(255,255,255,0.8)"});
|
||||
@include selectize-vertical-gradient(#fefefe, #f2f2f2);
|
||||
}
|
||||
.#{$selectize}-input {
|
||||
box-shadow: 0 1px 0 rgba(0, 0, 0, 0.05),
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.8);
|
||||
@include selectize-vertical-gradient(#fefefe, #f2f2f2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.selectize-control.single .selectize-input, .selectize-dropdown.single {
|
||||
.#{$selectize}-control.single .#{$selectize}-input,
|
||||
.#{$selectize}-dropdown.single {
|
||||
border-color: #b8b8b8;
|
||||
}
|
||||
|
||||
.selectize-dropdown {
|
||||
.#{$selectize}-dropdown {
|
||||
.optgroup-header {
|
||||
padding-top: $selectize-padding-dropdown-item-y + 2px;
|
||||
font-weight: bold;
|
||||
font-size: 0.85em;
|
||||
padding-top: $select-padding-dropdown-item-y + 2px;
|
||||
font-weight: bold;
|
||||
font-size: 0.85em;
|
||||
}
|
||||
.optgroup {
|
||||
border-top: 1px solid $selectize-color-dropdown-border-top;
|
||||
&:first-child {
|
||||
border-top: 0 none;
|
||||
}
|
||||
border-top: 1px solid $select-color-dropdown-border-top;
|
||||
&:first-child {
|
||||
border-top: 0 none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,300 +1,364 @@
|
||||
@use "sass:math";
|
||||
|
||||
// base styles
|
||||
$selectize: "selectize" !default;
|
||||
$select-font-family: inherit !default;
|
||||
$select-font-smoothing: inherit !default;
|
||||
$select-font-size: 13px !default;
|
||||
$select-line-height: 18px !default;
|
||||
|
||||
$selectize-font-family: inherit !default;
|
||||
$selectize-font-smoothing: inherit !default;
|
||||
$selectize-font-size: 13px !default;
|
||||
$selectize-line-height: 18px !default;
|
||||
$select-color-rgb: 0,0,0 !default;
|
||||
$select-color-text: RGBA($select-color-rgb, 0.83) !default;
|
||||
$select-color-border: #d0d0d0 !default;
|
||||
$select-color-highlight: rgba(125, 168, 208, 0.2) !default;
|
||||
$select-color-input: #fff !default;
|
||||
$select-color-input-full: $select-color-input !default;
|
||||
$select-color-disabled: #fafafa !default;
|
||||
$select-color-item: #f2f2f2 !default;
|
||||
$select-color-item-text: $select-color-text !default;
|
||||
$select-color-item-border: #d0d0d0 !default;
|
||||
$select-color-item-active: #e8e8e8 !default;
|
||||
$select-color-item-active-text: $select-color-text !default;
|
||||
$select-color-item-active-border: #cacaca !default;
|
||||
$select-color-dropdown: #fff !default;
|
||||
$select-color-dropdown-border: $select-color-border !default;
|
||||
$select-color-dropdown-border-top: #f0f0f0 !default;
|
||||
$select-color-dropdown-item-active: #f5fafd !default;
|
||||
$select-color-dropdown-item-active-text: #495c68 !default;
|
||||
$select-color-dropdown-item-create-text: RGBA($select-color-rgb, 0.5) !default;
|
||||
$select-color-dropdown-item-create-active-text: $select-color-dropdown-item-active-text !default;
|
||||
$select-color-optgroup: $select-color-dropdown !default;
|
||||
$select-color-optgroup-text: $select-color-text !default;
|
||||
$select-lighten-disabled-item: 30% !default;
|
||||
$select-lighten-disabled-item-text: 30% !default;
|
||||
$select-lighten-disabled-item-border: 30% !default;
|
||||
$select-opacity-disabled: 0.5 !default;
|
||||
|
||||
$selectize-color-text: #303030 !default;
|
||||
$selectize-color-border: #d0d0d0 !default;
|
||||
$selectize-color-highlight: rgba(125,168,208,0.2) !default;
|
||||
$selectize-color-input: #fff !default;
|
||||
$selectize-color-input-full: $selectize-color-input !default;
|
||||
$selectize-color-disabled: #fafafa !default;
|
||||
$selectize-color-item: #f2f2f2 !default;
|
||||
$selectize-color-item-text: $selectize-color-text !default;
|
||||
$selectize-color-item-border: #d0d0d0 !default;
|
||||
$selectize-color-item-active: #e8e8e8 !default;
|
||||
$selectize-color-item-active-text: $selectize-color-text !default;
|
||||
$selectize-color-item-active-border: #cacaca !default;
|
||||
$selectize-color-dropdown: #fff !default;
|
||||
$selectize-color-dropdown-border: $selectize-color-border !default;
|
||||
$selectize-color-dropdown-border-top: #f0f0f0 !default;
|
||||
$selectize-color-dropdown-item-active: #f5fafd !default;
|
||||
$selectize-color-dropdown-item-active-text: #495c68 !default;
|
||||
$selectize-color-dropdown-item-create-text: rgba(red($selectize-color-text), green($selectize-color-text), blue($selectize-color-text), 0.5) !default;
|
||||
$selectize-color-dropdown-item-create-active-text: $selectize-color-dropdown-item-active-text !default;
|
||||
$selectize-color-optgroup: $selectize-color-dropdown !default;
|
||||
$selectize-color-optgroup-text: $selectize-color-text !default;
|
||||
$selectize-lighten-disabled-item: 30% !default;
|
||||
$selectize-lighten-disabled-item-text: 30% !default;
|
||||
$selectize-lighten-disabled-item-border: 30% !default;
|
||||
$selectize-opacity-disabled: 0.5 !default;
|
||||
$select-shadow-input: inset 0 1px 1px rgba(0, 0, 0, 0.1) !default;
|
||||
$select-shadow-input-focus: inset 0 1px 2px rgba(0, 0, 0, 0.15) !default;
|
||||
$select-border: 1px solid $select-color-border !default;
|
||||
$select-dropdown-border: 1px solid $select-color-dropdown-border !default;
|
||||
$select-border-radius: 3px !default;
|
||||
|
||||
$selectize-shadow-input: inset 0 1px 1px rgba(0,0,0,0.1) !default;
|
||||
$selectize-shadow-input-focus: inset 0 1px 2px rgba(0,0,0,0.15) !default;
|
||||
$selectize-border: 1px solid $selectize-color-border !default;
|
||||
$selectize-dropdown-border: 1px solid $selectize-color-dropdown-border !default;
|
||||
$selectize-border-radius: 3px !default;
|
||||
$select-width-item-border: 0px !default;
|
||||
$select-max-height-dropdown: 200px !default;
|
||||
|
||||
$selectize-width-item-border: 0 !default;
|
||||
$selectize-max-height-dropdown: 200px !default;
|
||||
$select-padding-x: 8px !default;
|
||||
$select-padding-y: 8px !default;
|
||||
$select-padding-item-x: 6px !default;
|
||||
$select-padding-item-y: 2px !default;
|
||||
$select-padding-dropdown-item-x: $select-padding-x !default;
|
||||
$select-padding-dropdown-item-y: 5px !default;
|
||||
$select-margin-item-x: 3px !default;
|
||||
$select-margin-item-y: 3px !default;
|
||||
|
||||
$selectize-padding-x: 8px !default;
|
||||
$selectize-padding-y: 8px !default;
|
||||
$selectize-padding-item-x: 6px !default;
|
||||
$selectize-padding-item-y: 2px !default;
|
||||
$selectize-padding-dropdown-item-x: $selectize-padding-x !default;
|
||||
$selectize-padding-dropdown-item-y: 5px !default;
|
||||
$selectize-margin-item-x: 3px !default;
|
||||
$selectize-margin-item-y: 3px !default;
|
||||
$select-arrow-size: 5px !default;
|
||||
$select-arrow-color: #808080 !default;
|
||||
$select-arrow-offset: 15px !default;
|
||||
|
||||
$selectize-arrow-size: 5px !default;
|
||||
$selectize-arrow-color: #808080 !default;
|
||||
$selectize-arrow-offset: 15px !default;
|
||||
$select-caret-margin: 0 0px !default;
|
||||
$select-caret-margin-rtl: 0 4px 0 -2px !default;
|
||||
|
||||
$selectize-caret-margin: 0 2px 0 0 !default;
|
||||
$selectize-caret-margin-rtl: 0 4px 0 -2px !default;
|
||||
$select-spinner-size: 30px;
|
||||
$select-spinner-border-size: 5px;
|
||||
$select-spinner-border-color: $select-color-border;
|
||||
|
||||
@mixin selectize-border-radius($radii){
|
||||
@if mixin-exists("border-radius") {
|
||||
@include border-radius($radii)
|
||||
} @else {
|
||||
border-radius: $radii;
|
||||
}
|
||||
}
|
||||
@mixin selectize-unselectable(){
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
@mixin selectize-box-shadow($shadow){
|
||||
-webkit-box-shadow: $shadow;
|
||||
box-shadow: $shadow;
|
||||
}
|
||||
@mixin selectize-box-sizing($type: border-box){
|
||||
-webkit-box-sizing: $type;
|
||||
-moz-box-sizing: $type;
|
||||
box-sizing: $type;
|
||||
}
|
||||
@mixin selectize-vertical-gradient($color-top, $color-bottom){
|
||||
@mixin selectize-vertical-gradient($color-top, $color-bottom) {
|
||||
background-color: mix($color-top, $color-bottom, 60%);
|
||||
background-image: -moz-linear-gradient(top, $color-top, $color-bottom); // FF 3.6+
|
||||
background-image: -webkit-gradient(linear, 0 0, 0 100%, from($color-top), to($color-bottom)); // Safari 4+, Chrome 2+
|
||||
background-image: -webkit-linear-gradient(top, $color-top, $color-bottom); // Safari 5.1+, Chrome 10+
|
||||
background-image: -o-linear-gradient(top, $color-top, $color-bottom); // Opera 11.10
|
||||
background-image: linear-gradient(to bottom, $color-top, $color-bottom); // Standard, IE10
|
||||
background-image: linear-gradient(to bottom, $color-top, $color-bottom);
|
||||
background-repeat: repeat-x;
|
||||
//filter: e(%("progid:DXImageTransform.Microsoft@include gradient(startColorstr='%d', endColorstr='%d', GradientType=0)",argb($color-top),argb($color-bottom))); // IE9 and down
|
||||
}
|
||||
|
||||
.selectize-control {
|
||||
@import "plugins/drag_drop.scss";
|
||||
@import "plugins/dropdown_header.scss";
|
||||
@import "plugins/optgroup_columns.scss";
|
||||
@import "plugins/remove_button.scss";
|
||||
@import "plugins/clear_button.scss";
|
||||
@import "plugins/auto_position.scss";
|
||||
|
||||
.#{$selectize}-control {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.selectize-dropdown, .selectize-input, .selectize-input input {
|
||||
color: $selectize-color-text;
|
||||
font-family: $selectize-font-family;
|
||||
font-size: $selectize-font-size;
|
||||
line-height: $selectize-line-height;
|
||||
-webkit-font-smoothing: $selectize-font-smoothing;
|
||||
.#{$selectize}-dropdown,
|
||||
.#{$selectize}-input,
|
||||
.#{$selectize}-input input {
|
||||
color: $select-color-text;
|
||||
font-family: $select-font-family;
|
||||
font-size: $select-font-size;
|
||||
line-height: $select-line-height;
|
||||
font-smoothing: $select-font-smoothing;
|
||||
}
|
||||
|
||||
.selectize-input, .selectize-control.single .selectize-input.input-active {
|
||||
background: $selectize-color-input;
|
||||
.#{$selectize}-input,
|
||||
.#{$selectize}-control.single .#{$selectize}-input.input-active {
|
||||
background: $select-color-input;
|
||||
cursor: text;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.selectize-input {
|
||||
border: $selectize-border;
|
||||
padding: $selectize-padding-y $selectize-padding-x;
|
||||
.#{$selectize}-input {
|
||||
border: $select-border;
|
||||
padding: $select-padding-y $select-padding-x;
|
||||
display: inline-block;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
// overflow: hidden;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
@include selectize-box-sizing(border-box);
|
||||
@include selectize-box-shadow($selectize-shadow-input);
|
||||
@include selectize-border-radius($selectize-border-radius);
|
||||
box-sizing: border-box;
|
||||
box-shadow: $select-shadow-input;
|
||||
border-radius: $select-border-radius;
|
||||
|
||||
.selectize-control.multi &.has-items {
|
||||
$padding-x: $selectize-padding-x;
|
||||
$padding-top: calc(#{$selectize-padding-y} - #{$selectize-padding-item-y} - #{$selectize-width-item-border});
|
||||
$padding-bottom: calc(#{$selectize-padding-y} - #{$selectize-padding-item-y} - #{$selectize-margin-item-y} - #{$selectize-width-item-border});
|
||||
padding: $padding-top $padding-x $padding-bottom;
|
||||
.#{$selectize}-control.multi &.has-items {
|
||||
$padding-x: $select-padding-x;
|
||||
$padding-top: calc(
|
||||
#{$select-padding-y} - #{$select-padding-item-y} - #{$select-width-item-border}
|
||||
);
|
||||
$padding-bottom: calc(
|
||||
#{$select-padding-y} - #{$select-padding-item-y} - #{$select-margin-item-y} -
|
||||
#{$select-width-item-border}
|
||||
);
|
||||
padding: $padding-top $padding-x $padding-bottom;
|
||||
}
|
||||
|
||||
&.full {
|
||||
background-color: $selectize-color-input-full;
|
||||
background-color: $select-color-input-full;
|
||||
}
|
||||
&.disabled, &.disabled * {
|
||||
cursor: default !important;
|
||||
&.disabled,
|
||||
&.disabled * {
|
||||
cursor: default !important;
|
||||
}
|
||||
&.focus {
|
||||
@include selectize-box-shadow($selectize-shadow-input-focus);
|
||||
box-shadow: $select-shadow-input-focus;
|
||||
}
|
||||
&.dropdown-active {
|
||||
@include selectize-border-radius($selectize-border-radius $selectize-border-radius 0 0);
|
||||
border-radius: $select-border-radius $select-border-radius 0 0;
|
||||
}
|
||||
|
||||
> * {
|
||||
vertical-align: baseline;
|
||||
display: -moz-inline-stack;
|
||||
display: inline-block;
|
||||
zoom: 1;
|
||||
*display: inline;
|
||||
vertical-align: baseline;
|
||||
display: inline-block;
|
||||
zoom: 1;
|
||||
}
|
||||
.selectize-control.multi & > div {
|
||||
cursor: pointer;
|
||||
margin: 0 $selectize-margin-item-x $selectize-margin-item-y 0;
|
||||
padding: $selectize-padding-item-y $selectize-padding-item-x;
|
||||
background: $selectize-color-item;
|
||||
color: $selectize-color-item-text;
|
||||
border: $selectize-width-item-border solid $selectize-color-item-border;
|
||||
.#{$selectize}-control.multi & > div {
|
||||
cursor: pointer;
|
||||
margin: 0 $select-margin-item-x $select-margin-item-y 0;
|
||||
padding: $select-padding-item-y $select-padding-item-x;
|
||||
background: $select-color-item;
|
||||
color: $select-color-item-text;
|
||||
border: $select-width-item-border solid $select-color-item-border;
|
||||
|
||||
&.active {
|
||||
background: $selectize-color-item-active;
|
||||
color: $selectize-color-item-active-text;
|
||||
border: $selectize-width-item-border solid $selectize-color-item-active-border;
|
||||
}
|
||||
&.active {
|
||||
background: $select-color-item-active;
|
||||
color: $select-color-item-active-text;
|
||||
border: $select-width-item-border solid $select-color-item-active-border;
|
||||
}
|
||||
}
|
||||
.selectize-control.multi &.disabled > div {
|
||||
&, &.active {
|
||||
color: lighten(desaturate($selectize-color-item-text, 100%), $selectize-lighten-disabled-item-text);
|
||||
background: lighten(desaturate($selectize-color-item, 100%), $selectize-lighten-disabled-item);
|
||||
border: $selectize-width-item-border solid lighten(desaturate($selectize-color-item-border, 100%), $selectize-lighten-disabled-item-border);
|
||||
}
|
||||
.#{$selectize}-control.multi &.disabled > div {
|
||||
&,
|
||||
&.active {
|
||||
color: lighten(gray, $select-lighten-disabled-item-text);
|
||||
background: lighten(gray, $select-lighten-disabled-item);
|
||||
border: $select-width-item-border solid
|
||||
lighten(gray, $select-lighten-disabled-item-border);
|
||||
}
|
||||
}
|
||||
> input {
|
||||
&::-ms-clear {
|
||||
display: none;
|
||||
}
|
||||
display: inline-block !important;
|
||||
padding: 0 !important;
|
||||
min-height: 0 !important;
|
||||
max-height: none !important;
|
||||
max-width: 100% !important;
|
||||
margin: $selectize-caret-margin !important;
|
||||
text-indent: 0 !important;
|
||||
border: 0 none !important;
|
||||
background: none !important;
|
||||
line-height: inherit !important;
|
||||
-webkit-user-select: auto !important;
|
||||
@include selectize-box-shadow(none);
|
||||
&:focus { outline: none !important; }
|
||||
&::-ms-clear {
|
||||
display: none;
|
||||
}
|
||||
display: inline-block !important;
|
||||
padding: 0 !important;
|
||||
min-height: 0 !important;
|
||||
max-height: none !important;
|
||||
max-width: 100% !important;
|
||||
margin: 0 !important;
|
||||
text-indent: 0 !important;
|
||||
border: 0 none !important;
|
||||
background: none !important;
|
||||
line-height: inherit !important;
|
||||
user-select: auto !important;
|
||||
box-shadow: none !important;
|
||||
&:focus {
|
||||
outline: none !important;
|
||||
}
|
||||
|
||||
&[placeholder] {
|
||||
box-sizing: initial;
|
||||
}
|
||||
}
|
||||
&.has-items > input {
|
||||
margin: $select-caret-margin !important;
|
||||
}
|
||||
}
|
||||
|
||||
.selectize-input::after {
|
||||
content: ' ';
|
||||
.#{$selectize}-input::after {
|
||||
content: " ";
|
||||
display: block;
|
||||
clear: left;
|
||||
}
|
||||
|
||||
.selectize-input.dropdown-active::before {
|
||||
content: ' ';
|
||||
.#{$selectize}-input.dropdown-active::before {
|
||||
content: " ";
|
||||
display: block;
|
||||
position: absolute;
|
||||
background: $selectize-color-dropdown-border-top;
|
||||
background: $select-color-dropdown-border-top;
|
||||
height: 1px;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
.selectize-dropdown {
|
||||
.#{$selectize}-dropdown {
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
z-index: 10;
|
||||
border: $selectize-dropdown-border;
|
||||
background: $selectize-color-dropdown;
|
||||
|
||||
border: $select-dropdown-border;
|
||||
background: $select-color-dropdown;
|
||||
margin: -1px 0 0 0;
|
||||
border-top: 0 none;
|
||||
@include selectize-box-sizing(border-box);
|
||||
@include selectize-box-shadow(0 1px 3px rgba(0,0,0,0.1));
|
||||
@include selectize-border-radius(0 0 $selectize-border-radius $selectize-border-radius);
|
||||
box-sizing: border-box;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
||||
border-radius: 0 0 $select-border-radius $select-border-radius;
|
||||
|
||||
[data-selectable] {
|
||||
cursor: pointer;
|
||||
overflow: hidden;
|
||||
.highlight {
|
||||
background: $selectize-color-highlight;
|
||||
@include selectize-border-radius(1px);
|
||||
}
|
||||
cursor: pointer;
|
||||
overflow: hidden;
|
||||
.highlight {
|
||||
background: $select-color-highlight;
|
||||
border-radius: 1px;
|
||||
}
|
||||
}
|
||||
.option, .optgroup-header {
|
||||
padding: $selectize-padding-dropdown-item-y $selectize-padding-dropdown-item-x;
|
||||
.option,
|
||||
.optgroup-header,
|
||||
.no-results,
|
||||
.create {
|
||||
padding: $select-padding-dropdown-item-y $select-padding-dropdown-item-x;
|
||||
}
|
||||
.option, [data-disabled], [data-disabled] [data-selectable].option {
|
||||
cursor: inherit;
|
||||
opacity: 0.5;
|
||||
.option,
|
||||
[data-disabled],
|
||||
[data-disabled] [data-selectable].option {
|
||||
cursor: inherit;
|
||||
opacity: 0.5;
|
||||
}
|
||||
[data-selectable].option {
|
||||
opacity: 1;
|
||||
opacity: 1;
|
||||
cursor: pointer;
|
||||
}
|
||||
.optgroup:first-child .optgroup-header {
|
||||
border-top: 0 none;
|
||||
border-top: 0 none;
|
||||
}
|
||||
.optgroup-header {
|
||||
color: $selectize-color-optgroup-text;
|
||||
background: $selectize-color-optgroup;
|
||||
cursor: default;
|
||||
color: $select-color-optgroup-text;
|
||||
background: $select-color-optgroup;
|
||||
cursor: default;
|
||||
}
|
||||
.active {
|
||||
background-color: $selectize-color-dropdown-item-active;
|
||||
color: $selectize-color-dropdown-item-active-text;
|
||||
&.create {
|
||||
color: $selectize-color-dropdown-item-create-active-text;
|
||||
}
|
||||
background-color: $select-color-item-active;
|
||||
color: $select-color-item-active-text;
|
||||
&.create {
|
||||
color: $select-color-dropdown-item-create-active-text;
|
||||
}
|
||||
}
|
||||
|
||||
.selected {
|
||||
background-color: $select-color-item-active;
|
||||
color: $select-color-item-active-text;
|
||||
}
|
||||
.create {
|
||||
color: $selectize-color-dropdown-item-create-text;
|
||||
color: $select-color-dropdown-item-create-text;
|
||||
}
|
||||
|
||||
.active:not(.selected) {
|
||||
background: $select-color-dropdown-item-active;
|
||||
color: $select-color-dropdown-item-active-text;
|
||||
}
|
||||
}
|
||||
|
||||
.selectize-dropdown-content {
|
||||
.#{$selectize}-dropdown-content {
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
max-height: $selectize-max-height-dropdown;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
max-height: $select-max-height-dropdown;
|
||||
overflow-scrolling: touch;
|
||||
}
|
||||
|
||||
.selectize-control.single .selectize-input {
|
||||
&, input { cursor: pointer; }
|
||||
&.input-active, &.input-active input { cursor: text; }
|
||||
.#{$selectize}-dropdown-emptyoptionlabel {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.#{$selectize}-dropdown .spinner {
|
||||
display: inline-block;
|
||||
width: $select-spinner-size;
|
||||
height: $select-spinner-size;
|
||||
margin: $select-padding-dropdown-item-y $select-padding-dropdown-item-x;
|
||||
|
||||
&:after {
|
||||
content: ' ';
|
||||
display: block;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
right: $selectize-arrow-offset;
|
||||
margin-top: round((-1 * $selectize-arrow-size / 2));
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-style: solid;
|
||||
border-width: $selectize-arrow-size $selectize-arrow-size 0 $selectize-arrow-size;
|
||||
border-color: $selectize-arrow-color transparent transparent transparent;
|
||||
content: " ";
|
||||
display: block;
|
||||
width: $select-spinner-size * 0.8;
|
||||
height: $select-spinner-size * 0.8;
|
||||
margin: $select-spinner-size * 0.1;
|
||||
border-radius: 50%;
|
||||
border: $select-spinner-border-size solid $select-spinner-border-color;
|
||||
border-color: $select-spinner-border-color transparent
|
||||
$select-spinner-border-color transparent;
|
||||
animation: lds-dual-ring 1.2s linear infinite;
|
||||
}
|
||||
&.dropdown-active:after {
|
||||
margin-top: $selectize-arrow-size * -0.8;
|
||||
border-width: 0 $selectize-arrow-size $selectize-arrow-size $selectize-arrow-size;
|
||||
border-color: transparent transparent $selectize-arrow-color transparent;
|
||||
@keyframes lds-dual-ring {
|
||||
0% {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
100% {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.selectize-control.rtl {
|
||||
&.single .selectize-input:after {
|
||||
left: $selectize-arrow-offset;
|
||||
right: auto;
|
||||
.#{$selectize}-control.single .#{$selectize}-input {
|
||||
&,
|
||||
input {
|
||||
cursor: pointer;
|
||||
}
|
||||
.selectize-input > input {
|
||||
margin: $selectize-caret-margin-rtl !important;
|
||||
&.input-active,
|
||||
&.input-active input:not(:read-only) {
|
||||
cursor: text;
|
||||
}
|
||||
|
||||
&:not(.no-arrow):after {
|
||||
content: " ";
|
||||
display: block;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
right: $select-arrow-offset;
|
||||
margin-top: round(divide(-1 * $select-arrow-size, 2));
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-style: solid;
|
||||
border-width: $select-arrow-size $select-arrow-size 0 $select-arrow-size;
|
||||
border-color: $select-arrow-color transparent transparent transparent;
|
||||
}
|
||||
&:not(.no-arrow).dropdown-active:after {
|
||||
margin-top: $select-arrow-size * -0.8;
|
||||
border-width: 0 $select-arrow-size $select-arrow-size $select-arrow-size;
|
||||
border-color: transparent transparent $select-arrow-color transparent;
|
||||
}
|
||||
}
|
||||
|
||||
.selectize-control .selectize-input.disabled {
|
||||
opacity: $selectize-opacity-disabled;
|
||||
background-color: $selectize-color-disabled;
|
||||
.#{$selectize}-control.rtl {
|
||||
text-align: right;
|
||||
&.single .#{$selectize}-input:after {
|
||||
left: $select-arrow-offset;
|
||||
right: auto;
|
||||
}
|
||||
.#{$selectize}-input > input {
|
||||
margin: $select-caret-margin-rtl !important;
|
||||
}
|
||||
}
|
||||
|
||||
.#{$selectize}-control .#{$selectize}-input.disabled {
|
||||
opacity: $select-opacity-disabled;
|
||||
background-color: $select-color-disabled;
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1,2 +1,2 @@
|
||||
/*! shiny 1.7.4.9002 | (c) 2012-2023 RStudio, PBC. | License: GPL-3 | file LICENSE */
|
||||
/*! shiny 1.8.0.9000 | (c) 2012-2024 RStudio, PBC. | License: GPL-3 | file LICENSE */
|
||||
#showcase-well{border-radius:0}.shiny-code{background-color:#fff;margin-bottom:0}.shiny-code code{font-family:Menlo,Consolas,Courier New,monospace}.shiny-code-container{margin-top:20px;clear:both}.shiny-code-container h3{display:inline;margin-right:15px}.showcase-header{font-size:16px;font-weight:400}.showcase-code-link{text-align:right;padding:15px}#showcase-app-container{vertical-align:top}#showcase-code-tabs{margin-right:15px}#showcase-code-tabs pre{border:none;line-height:1em}#showcase-code-tabs .nav,#showcase-code-tabs ul{margin-bottom:0}#showcase-code-tabs .tab-content{border-style:solid;border-color:#e5e5e5;border-width:0px 1px 1px 1px;overflow:auto;border-bottom-right-radius:4px;border-bottom-left-radius:4px}#showcase-app-code{width:100%}#showcase-code-position-toggle{float:right}#showcase-sxs-code{padding-top:20px;vertical-align:top}.showcase-code-license{display:block;text-align:right}#showcase-code-content pre{background-color:#fff}
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -1,3 +1,3 @@
|
||||
/*! shiny 1.7.4.9002 | (c) 2012-2023 RStudio, PBC. | License: GPL-3 | file LICENSE */
|
||||
/*! shiny 1.8.0.9000 | (c) 2012-2024 RStudio, PBC. | License: GPL-3 | file LICENSE */
|
||||
"use strict";(function(){var a=eval;window.addEventListener("message",function(i){var e=i.data;e.code&&a(e.code)});})();
|
||||
//# sourceMappingURL=shiny-testmode.js.map
|
||||
|
||||
14916
inst/www/shared/shiny.js
14916
inst/www/shared/shiny.js
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
4
inst/www/shared/shiny.min.css
vendored
4
inst/www/shared/shiny.min.css
vendored
File diff suppressed because one or more lines are too long
19
inst/www/shared/shiny.min.js
vendored
19
inst/www/shared/shiny.min.js
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1,13 +1,7 @@
|
||||
$bg: $body-bg !default;
|
||||
$fg: $text-color !default; // BS3
|
||||
$theme-colors: ("primary": $brand-primary, "info": $brand-info, "warning": $brand-warning, "danger": $brand-danger) !default;
|
||||
|
||||
$fg: null !default;
|
||||
$theme-colors: null !default;
|
||||
@if variable-exists('text-color') {
|
||||
$fg: $text-color !default; // BS3
|
||||
$theme-colors: ("primary": $brand-primary, "info": $brand-info, "warning": $brand-warning, "danger": $brand-danger) !default;
|
||||
} @else {
|
||||
$fg: $body-color !default; // BS4
|
||||
}
|
||||
// No $border-width, $border-color, or $border-radius in BS3
|
||||
$border-width: 1px !default;
|
||||
$border-color: mix($bg, $fg, 70%) !default;
|
||||
@@ -16,7 +10,6 @@ $border-radius: $border-radius-base !default;
|
||||
$shiny-disconnected-bg: mix($bg, $fg, 58%) !default;
|
||||
$shiny-table-na: mix($bg, $fg, 54%) !default;
|
||||
$shiny-error-validated-color: mix($bg, $fg, 50%) !default;
|
||||
// TODO: what should this be? mix of primary and bg?
|
||||
$shiny-progress-text-bg-color: #eef8ff !default;
|
||||
$shiny-input-panel-bg: mix($bg, $fg, 96%) !default;
|
||||
$shiny-input-panel-border: $border-width solid $border-color !default;
|
||||
@@ -39,11 +32,3 @@ $notification-close-color: mix($bg, $fg, 20%) !default;
|
||||
$notification-close-hover-color: $fg !default;
|
||||
$notification-content-action-color: map-get($theme-colors, "primary") !default;
|
||||
$datepicker-disabled-color: $dropdown-link-disabled-color !default;
|
||||
|
||||
// I don't think BS3 has a file input box shadow setting
|
||||
@if variable-exists('custom-file-focus-box-shadow') {
|
||||
$shiny-file-active-shadow: $custom-file-focus-box-shadow !default;
|
||||
}
|
||||
|
||||
// TODO: should this be changed to danger?
|
||||
//$shiny-error-color: red !default;
|
||||
30
inst/www/shared/shiny_scss/shiny.bootstrap4.scss
Normal file
30
inst/www/shared/shiny_scss/shiny.bootstrap4.scss
Normal file
@@ -0,0 +1,30 @@
|
||||
$bg: $body-bg !default;
|
||||
$fg: $body-color !default;
|
||||
|
||||
$shiny-disconnected-bg: mix($bg, $fg, 58%) !default;
|
||||
$shiny-table-na: mix($bg, $fg, 54%) !default;
|
||||
$shiny-error-validated-color: mix($bg, $fg, 50%) !default;
|
||||
$shiny-progress-text-bg-color: #eef8ff !default;
|
||||
$shiny-input-panel-bg: mix($bg, $fg, 96%) !default;
|
||||
$shiny-input-panel-border: $border-width solid $border-color !default;
|
||||
$shiny-input-panel-border-radius: $border-radius !default;
|
||||
$shiny-text-output-border-radius: $border-radius !default;
|
||||
$notification-bg-color: mix($bg, $fg, 90%) !default;
|
||||
$notification-color: $fg !default;
|
||||
$notification-border: $border-width solid $border-color !default;
|
||||
$notification-border-radius: $border-radius !default;
|
||||
$notification-message-bg: mix($bg, map-get($theme-colors, "info"), 90%) !default;
|
||||
$notification-message-color: mix($fg, map-get($theme-colors, "info"), 10%) !default;
|
||||
$notification-message-border: $border-width solid mix($bg, map-get($theme-colors, "info"), 53%) !default;
|
||||
$notification-warning-bg: mix($bg, map-get($theme-colors, "warning"), 90%) !default;
|
||||
$notification-warning-color: mix($fg, map-get($theme-colors, "warning"), 10%) !default;
|
||||
$notification-warning-border: $border-width solid mix($bg, map-get($theme-colors, "warning"), 71%) !default;
|
||||
$notification-error-bg: mix($bg, map-get($theme-colors, "danger"), 90%) !default;
|
||||
$notification-error-color: mix($fg, map-get($theme-colors, "danger"), 10%) !default;
|
||||
$notification-error-border: $border-width solid mix($bg, map-get($theme-colors, "danger"), 79%) !default;
|
||||
$notification-close-color: mix($bg, $fg, 20%) !default;
|
||||
$notification-close-hover-color: $fg !default;
|
||||
$notification-content-action-color: map-get($theme-colors, "primary") !default;
|
||||
$datepicker-disabled-color: $dropdown-link-disabled-color !default;
|
||||
|
||||
$shiny-file-active-shadow: $custom-file-focus-box-shadow !default;
|
||||
45
inst/www/shared/shiny_scss/shiny.bootstrap5.scss
Normal file
45
inst/www/shared/shiny_scss/shiny.bootstrap5.scss
Normal file
@@ -0,0 +1,45 @@
|
||||
$body-emphasis-color: $body-color !default; // introduced in BS 5.3
|
||||
|
||||
$shiny-emphasis-color-rgb: var(--#{$prefix}emphasis-color-rgb, 0,0,0) !default;
|
||||
|
||||
$shiny-disconnected-bg: RGBA($shiny-emphasis-color-rgb, 0.42) !default;
|
||||
$shiny-table-na: RGBA($shiny-emphasis-color-rgb, 0.46) !default;
|
||||
$shiny-error-color: var(--#{$prefix}danger-text-emphasis) !default;
|
||||
$shiny-error-validated-color: RGBA($shiny-emphasis-color-rgb, 0.5) !default;
|
||||
$shiny-progress-text-bg-color: RGBA(var(--#{$prefix}primary-rgb, #{to-rgb($primary)}), 0.05) !default;
|
||||
|
||||
$shiny-input-panel-bg: RGBA($shiny-emphasis-color-rgb, 0.04) !default;
|
||||
$shiny-input-panel-border: $border-width solid var(--#{$prefix}border-color, #{$border-color}) !default;
|
||||
$shiny-input-panel-border-radius: $border-radius !default;
|
||||
$shiny-text-output-border-radius: $border-radius !default;
|
||||
|
||||
$notification-color: var(--#{$prefix}emphasis-color, #{$body-emphasis-color}) !default;
|
||||
$notification-bg-color: var(--#{$prefix}body-bg, #{$body-bg}) !default;
|
||||
$notification-border: $border-width solid var(--#{$prefix}border-color, #{$border-color}) !default;
|
||||
$notification-border-radius: $border-radius !default;
|
||||
|
||||
$notification-message-color: var(--#{$prefix}info-text-emphasis) !default;
|
||||
$notification-message-bg: var(--#{$prefix}info-bg-subtle) !default;
|
||||
$notification-message-border: $border-width solid var(--#{$prefix}info-border-subtle) !default;
|
||||
|
||||
$notification-warning-color: var(--#{$prefix}warning-text-emphasis) !default;
|
||||
$notification-warning-bg: var(--#{$prefix}warning-bg-subtle) !default;
|
||||
$notification-warning-border: $border-width solid var(--#{$prefix}warning-border-subtle) !default;
|
||||
|
||||
$notification-error-color: var(--#{$prefix}danger-text-emphasis) !default;
|
||||
$notification-error-bg: var(--#{$prefix}danger-bg-subtle) !default;
|
||||
$notification-error-border: $border-width solid var(--#{$prefix}danger-border-subtle) !default;
|
||||
|
||||
$notification-close-color: RGBA($shiny-emphasis-color-rgb, 0.8) !default;
|
||||
$notification-close-hover-color: RGB($shiny-emphasis-color-rgb) !default;
|
||||
$notification-content-action-color: RGB(var(--#{$prefix}primary-rgb, #{to-rgb($primary)})) !default;
|
||||
|
||||
$datepicker-disabled-color: $dropdown-link-disabled-color !default;
|
||||
|
||||
$shiny-file-active-shadow: $input-focus-box-shadow !default;
|
||||
|
||||
/* Treat conditional panels and uiOutput as "pass-through" containers */
|
||||
.shiny-panel-conditional,
|
||||
div:where(.shiny-html-output) {
|
||||
display: contents;
|
||||
}
|
||||
@@ -13,7 +13,7 @@ $notification-bg-color: #e8e8e8 !default;
|
||||
$notification-color: #333 !default;
|
||||
$notification-border: 1px solid #ccc !default;
|
||||
$notification-border-radius: 3px !default;
|
||||
$notification-padding: 10px 8px 10px 10px !default;
|
||||
$notification-padding: 10px 2rem 10px 10px !default;
|
||||
$notification-message-bg: #d9edf7 !default;
|
||||
$notification-message-color: #31708f !default;
|
||||
$notification-message-border: 1px solid #bce8f1 !default;
|
||||
@@ -88,10 +88,7 @@ pre.shiny-text-output {
|
||||
|
||||
#shiny-disconnected-overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
inset: 0;
|
||||
background-color: $shiny-disconnected-bg;
|
||||
opacity: 0.5;
|
||||
overflow: hidden;
|
||||
@@ -99,6 +96,17 @@ pre.shiny-text-output {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
html.autoreload-enabled #shiny-disconnected-overlay.reloading {
|
||||
opacity: 0;
|
||||
animation: fadeIn 250ms forwards;
|
||||
animation-delay: 1s;
|
||||
}
|
||||
@keyframes fadeIn {
|
||||
to {
|
||||
opacity: 0.1;
|
||||
}
|
||||
}
|
||||
|
||||
.table.shiny-table {
|
||||
@include table-padding($left: 12px, $right: 12px);
|
||||
}
|
||||
@@ -157,6 +165,9 @@ pre.shiny-text-output {
|
||||
margin-top: -9px;
|
||||
}
|
||||
.slider-animate-button {
|
||||
/* Ensure controls above slider line touch target */
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
opacity: 0.5;
|
||||
.pause {
|
||||
display: none;
|
||||
@@ -181,6 +192,12 @@ pre.shiny-text-output {
|
||||
}
|
||||
}
|
||||
|
||||
// fileInput()'s button should never have a top-right or bottom-right border-radius
|
||||
.btn-file {
|
||||
border-top-right-radius: 0 !important;
|
||||
border-bottom-right-radius: 0 !important;
|
||||
}
|
||||
|
||||
/* Make sure the filename doesn't extend past the bounds of the container */
|
||||
.shiny-input-container input[type=file] {
|
||||
overflow: hidden;
|
||||
@@ -304,11 +321,6 @@ pre.shiny-text-output {
|
||||
zoom: 1.0000001;
|
||||
}
|
||||
|
||||
/* consistency with bootstrap.css for selectize.js */
|
||||
.selectize-control {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.shiny-frame {
|
||||
border: none;
|
||||
}
|
||||
@@ -378,18 +390,20 @@ pre.shiny-text-output {
|
||||
right: 0;
|
||||
background-color: rgba(0,0,0,0);
|
||||
padding: 2px;
|
||||
width: 250px;
|
||||
width: 300px;
|
||||
max-width: 100%;
|
||||
z-index: 99999;
|
||||
}
|
||||
|
||||
.shiny-notification {
|
||||
position: relative;
|
||||
background-color: $notification-bg-color;
|
||||
color: $notification-color;
|
||||
border: $notification-border;
|
||||
@include border-radius-shim($notification-border-radius);
|
||||
opacity: 0.85;
|
||||
padding: $notification-padding;
|
||||
margin: 2px;
|
||||
margin: 5px;
|
||||
&-message {
|
||||
color: $notification-message-color;
|
||||
background-color: $notification-message-bg;
|
||||
@@ -406,17 +420,23 @@ pre.shiny-text-output {
|
||||
border: $notification-error-border;
|
||||
}
|
||||
&-close {
|
||||
float: right;
|
||||
font-weight: bold;
|
||||
font-size: 18px;
|
||||
bottom: 9px;
|
||||
position: relative;
|
||||
padding-left: 4px;
|
||||
position: absolute;
|
||||
width: 2rem;
|
||||
height: 2rem;
|
||||
top: 0;
|
||||
right: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-weight: normal;
|
||||
font-size: 1.125em;
|
||||
padding: 0.25rem;
|
||||
color: $notification-close-color;
|
||||
cursor: default;
|
||||
cursor: pointer;
|
||||
}
|
||||
&-close:hover {
|
||||
color: $notification-close-hover-color;
|
||||
font-weight: bold;
|
||||
}
|
||||
&-content-action a {
|
||||
color: $notification-content-action-color;
|
||||
|
||||
186
man/ExtendedTask.Rd
Normal file
186
man/ExtendedTask.Rd
Normal file
@@ -0,0 +1,186 @@
|
||||
% Generated by roxygen2: do not edit by hand
|
||||
% Please edit documentation in R/extended-task.R
|
||||
\name{ExtendedTask}
|
||||
\alias{ExtendedTask}
|
||||
\title{Task or computation that proceeds in the background}
|
||||
\description{
|
||||
In normal Shiny reactive code, whenever an observer, calc, or
|
||||
output is busy computing, it blocks the current session from receiving any
|
||||
inputs or attempting to proceed with any other computation related to that
|
||||
session.
|
||||
|
||||
The \code{ExtendedTask} class allows you to have an expensive operation that is
|
||||
started by a reactive effect, and whose (eventual) results can be accessed
|
||||
by a regular observer, calc, or output; but during the course of the
|
||||
operation, the current session is completely unblocked, allowing the user
|
||||
to continue using the rest of the app while the operation proceeds in the
|
||||
background.
|
||||
|
||||
Note that each \code{ExtendedTask} object does not represent a \emph{single
|
||||
invocation} of its long-running function. Rather, it's an object that is
|
||||
used to invoke the function with different arguments, keeps track of
|
||||
whether an invocation is in progress, and provides ways to get at the
|
||||
current status or results of the operation. A single \code{ExtendedTask} object
|
||||
does not permit overlapping invocations: if the \code{invoke()} method is called
|
||||
before the previous \code{invoke()} is completed, the new invocation will not
|
||||
begin until the previous invocation has completed.
|
||||
}
|
||||
\section{\code{ExtendedTask} versus asynchronous reactives}{
|
||||
|
||||
|
||||
Shiny has long supported \href{https://rstudio.github.io/promises/articles/promises_06_shiny.html}{using \{promises\}}
|
||||
to write asynchronous observers, calcs, or outputs. You may be wondering
|
||||
what the differences are between those techniques and this class.
|
||||
|
||||
Asynchronous observers, calcs, and outputs are not--and have never
|
||||
been--designed to let a user start a long-running operation, while keeping
|
||||
that very same (browser) session responsive to other interactions. Instead,
|
||||
they unblock other sessions, so you can take a long-running operation that
|
||||
would normally bring the entire R process to a halt and limit the blocking
|
||||
to just the session that started the operation. (For more details, see the
|
||||
section on \href{https://rstudio.github.io/promises/articles/promises_06_shiny.html#the-flush-cycle}{"The Flush Cycle"}.)
|
||||
|
||||
\code{ExtendedTask}, on the other hand, invokes an asynchronous function (that
|
||||
is, a function that quickly returns a promise) and allows even that very
|
||||
session to immediately unblock and carry on with other user interactions.
|
||||
}
|
||||
|
||||
\section{Methods}{
|
||||
\subsection{Public methods}{
|
||||
\itemize{
|
||||
\item \href{#method-ExtendedTask-new}{\code{ExtendedTask$new()}}
|
||||
\item \href{#method-ExtendedTask-invoke}{\code{ExtendedTask$invoke()}}
|
||||
\item \href{#method-ExtendedTask-status}{\code{ExtendedTask$status()}}
|
||||
\item \href{#method-ExtendedTask-result}{\code{ExtendedTask$result()}}
|
||||
\item \href{#method-ExtendedTask-clone}{\code{ExtendedTask$clone()}}
|
||||
}
|
||||
}
|
||||
\if{html}{\out{<hr>}}
|
||||
\if{html}{\out{<a id="method-ExtendedTask-new"></a>}}
|
||||
\if{latex}{\out{\hypertarget{method-ExtendedTask-new}{}}}
|
||||
\subsection{Method \code{new()}}{
|
||||
Creates a new \code{ExtendedTask} object. \code{ExtendedTask} should generally be
|
||||
created either at the top of a server function, or at the top of a module
|
||||
server function.
|
||||
\subsection{Usage}{
|
||||
\if{html}{\out{<div class="r">}}\preformatted{ExtendedTask$new(func)}\if{html}{\out{</div>}}
|
||||
}
|
||||
|
||||
\subsection{Arguments}{
|
||||
\if{html}{\out{<div class="arguments">}}
|
||||
\describe{
|
||||
\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 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[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
|
||||
invoked; instead, if the function needs to access reactive inputs, it
|
||||
should take parameters and the caller of the \code{invoke()} method should
|
||||
read reactive inputs and pass them as arguments.}
|
||||
}
|
||||
\if{html}{\out{</div>}}
|
||||
}
|
||||
}
|
||||
\if{html}{\out{<hr>}}
|
||||
\if{html}{\out{<a id="method-ExtendedTask-invoke"></a>}}
|
||||
\if{latex}{\out{\hypertarget{method-ExtendedTask-invoke}{}}}
|
||||
\subsection{Method \code{invoke()}}{
|
||||
Starts executing the long-running operation. If this \code{ExtendedTask} is
|
||||
already running (meaning, a previous call to \code{invoke()} is not yet
|
||||
complete) then enqueues this invocation until after the current
|
||||
invocation, and any already-enqueued invocation, completes.
|
||||
\subsection{Usage}{
|
||||
\if{html}{\out{<div class="r">}}\preformatted{ExtendedTask$invoke(...)}\if{html}{\out{</div>}}
|
||||
}
|
||||
|
||||
\subsection{Arguments}{
|
||||
\if{html}{\out{<div class="arguments">}}
|
||||
\describe{
|
||||
\item{\code{...}}{Parameters to use for this invocation of the underlying
|
||||
function. If reactive inputs are needed by the underlying function,
|
||||
they should be read by the caller of \code{invoke} and passed in as
|
||||
arguments.}
|
||||
}
|
||||
\if{html}{\out{</div>}}
|
||||
}
|
||||
}
|
||||
\if{html}{\out{<hr>}}
|
||||
\if{html}{\out{<a id="method-ExtendedTask-status"></a>}}
|
||||
\if{latex}{\out{\hypertarget{method-ExtendedTask-status}{}}}
|
||||
\subsection{Method \code{status()}}{
|
||||
This is a reactive read that invalidates the caller when the task's
|
||||
status changes.
|
||||
|
||||
Returns one of the following values:
|
||||
\itemize{
|
||||
\item \code{"initial"}: This \code{ExtendedTask} has not yet been invoked
|
||||
\item \code{"running"}: An invocation is currently running
|
||||
\item \code{"success"}: An invocation completed successfully, and a value can be
|
||||
retrieved via the \code{result()} method
|
||||
\item \code{"error"}: An invocation completed with an error, which will be
|
||||
re-thrown if you call the \code{result()} method
|
||||
}
|
||||
\subsection{Usage}{
|
||||
\if{html}{\out{<div class="r">}}\preformatted{ExtendedTask$status()}\if{html}{\out{</div>}}
|
||||
}
|
||||
|
||||
}
|
||||
\if{html}{\out{<hr>}}
|
||||
\if{html}{\out{<a id="method-ExtendedTask-result"></a>}}
|
||||
\if{latex}{\out{\hypertarget{method-ExtendedTask-result}{}}}
|
||||
\subsection{Method \code{result()}}{
|
||||
Attempts to read the results of the most recent invocation. This is a
|
||||
reactive read that invalidates as the task's status changes.
|
||||
|
||||
The actual behavior differs greatly depending on the current status of
|
||||
the task:
|
||||
\itemize{
|
||||
\item \code{"initial"}: Throws a silent error (like \code{\link[=req]{req(FALSE)}}). If
|
||||
this happens during output rendering, the output will be blanked out.
|
||||
\item \code{"running"}: Throws a special silent error that, if it happens during
|
||||
output rendering, makes the output appear "in progress" until further
|
||||
notice.
|
||||
\item \code{"success"}: Returns the return value of the most recent invocation.
|
||||
\item \code{"error"}: Throws whatever error was thrown by the most recent
|
||||
invocation.
|
||||
}
|
||||
|
||||
This method is intended to be called fairly naively by any output or
|
||||
reactive expression that cares about the output--you just have to be
|
||||
aware that if the result isn't ready for whatever reason, processing will
|
||||
stop in much the same way as \code{req(FALSE)} does, but when the result is
|
||||
ready you'll get invalidated, and when you run again the result should be
|
||||
there.
|
||||
|
||||
Note that the \code{result()} method is generally not meant to be used with
|
||||
\code{\link[=observeEvent]{observeEvent()}}, \code{\link[=eventReactive]{eventReactive()}}, \code{\link[=bindEvent]{bindEvent()}}, or \code{\link[=isolate]{isolate()}} as the
|
||||
invalidation will be ignored.
|
||||
\subsection{Usage}{
|
||||
\if{html}{\out{<div class="r">}}\preformatted{ExtendedTask$result()}\if{html}{\out{</div>}}
|
||||
}
|
||||
|
||||
}
|
||||
\if{html}{\out{<hr>}}
|
||||
\if{html}{\out{<a id="method-ExtendedTask-clone"></a>}}
|
||||
\if{latex}{\out{\hypertarget{method-ExtendedTask-clone}{}}}
|
||||
\subsection{Method \code{clone()}}{
|
||||
The objects of this class are cloneable with this method.
|
||||
\subsection{Usage}{
|
||||
\if{html}{\out{<div class="r">}}\preformatted{ExtendedTask$clone(deep = FALSE)}\if{html}{\out{</div>}}
|
||||
}
|
||||
|
||||
\subsection{Arguments}{
|
||||
\if{html}{\out{<div class="arguments">}}
|
||||
\describe{
|
||||
\item{\code{deep}}{Whether to make a deep clone.}
|
||||
}
|
||||
\if{html}{\out{</div>}}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,7 @@
|
||||
\alias{actionLink}
|
||||
\title{Action button/link}
|
||||
\usage{
|
||||
actionButton(inputId, label, icon = NULL, width = NULL, ...)
|
||||
actionButton(inputId, label, icon = NULL, width = NULL, disabled = FALSE, ...)
|
||||
|
||||
actionLink(inputId, label, icon = NULL, ...)
|
||||
}
|
||||
@@ -20,6 +20,9 @@ you could also use any other HTML, like an image.}
|
||||
\item{width}{The width of the input, e.g. \code{'400px'}, or \code{'100\%'};
|
||||
see \code{\link[=validateCssUnit]{validateCssUnit()}}.}
|
||||
|
||||
\item{disabled}{If \code{TRUE}, the button will not be clickable. Use
|
||||
\code{\link[=updateActionButton]{updateActionButton()}} to dynamically enable/disable the button.}
|
||||
|
||||
\item{...}{Named attributes to be applied to the button or link.}
|
||||
}
|
||||
\description{
|
||||
|
||||
@@ -166,8 +166,8 @@ instead of the default 200 MB:
|
||||
}\if{html}{\out{</div>}}
|
||||
|
||||
To use different settings for a session-scoped cache, you can set
|
||||
\code{self$cache} at the top of your server function. By default, it will create
|
||||
a 200 MB memory cache for each session, but you can replace it with
|
||||
\code{session$cache} at the top of your server function. By default, it will
|
||||
create a 200 MB memory cache for each session, but you can replace it with
|
||||
something different. To use the session-scoped cache, you must also call
|
||||
\code{bindCache()} with \code{cache="session"}. This will create a 100 MB cache for
|
||||
the session:
|
||||
@@ -448,7 +448,7 @@ shinyApp(
|
||||
bindEvent(input$go)
|
||||
# The cached, eventified reactive takes a reactive dependency on
|
||||
# input$go, but doesn't use it for the cache key. It uses input$x and
|
||||
# input$y for the cache key, but doesn't take a reactive depdency on
|
||||
# input$y for the cache key, but doesn't take a reactive dependency on
|
||||
# them, because the reactive dependency is superseded by addEvent().
|
||||
|
||||
output$txt <- renderText(r())
|
||||
|
||||
@@ -24,9 +24,9 @@ fileInput(
|
||||
multiple files at once. \strong{Does not work on older browsers, including
|
||||
Internet Explorer 9 and earlier.}}
|
||||
|
||||
\item{accept}{A character vector of "unique file type specifiers" which
|
||||
gives the browser a hint as to the type of file the server expects.
|
||||
Many browsers use this prevent the user from selecting an invalid file.
|
||||
\item{accept}{A character vector of "unique file type specifiers" which gives
|
||||
the browser a hint as to the type of file the server expects. Many browsers
|
||||
use this prevent the user from selecting an invalid file.
|
||||
|
||||
A unique file type specifier can be:
|
||||
\itemize{
|
||||
@@ -61,12 +61,19 @@ still photos only, also use \code{accept="image/*"}. For video only, use
|
||||
Create a file upload control that can be used to upload one or more files.
|
||||
}
|
||||
\details{
|
||||
Whenever a file upload completes, the corresponding input variable is set
|
||||
to a dataframe. See the \verb{Server value} section.
|
||||
Whenever a file upload completes, the corresponding input variable is set to
|
||||
a dataframe. See the \verb{Server value} section.
|
||||
|
||||
Each time files are uploaded, they are written to a new random subdirectory
|
||||
inside of R's process-level temporary directory. The Shiny user session keeps
|
||||
track of all uploads in the session, and when the session ends, Shiny deletes
|
||||
all of the subdirectories where files where uploaded to.
|
||||
}
|
||||
\section{Server value}{
|
||||
|
||||
A \code{data.frame} that contains one row for each selected file, and following columns:
|
||||
|
||||
A \code{data.frame} that contains one row for each selected file, and following
|
||||
columns:
|
||||
\describe{
|
||||
\item{\code{name}}{The filename provided by the web browser. This is
|
||||
\strong{not} the path to read to get at the actual data that was uploaded
|
||||
|
||||
@@ -26,7 +26,7 @@ These can be particularly useful if you want to display different content
|
||||
depending on the values in the query string / hash (e.g. instead of basing
|
||||
the conditional on an input or a calculated reactive, you can base it on the
|
||||
query string). However, note that, if you're changing the query string / hash
|
||||
programatically from within the server code, you must use
|
||||
programmatically from within the server code, you must use
|
||||
\verb{updateQueryString(_yourNewQueryString_, mode = "push")}. The default
|
||||
\code{mode} for \code{updateQueryString} is \code{"replace"}, which doesn't
|
||||
raise any events, so any observers or reactives that depend on it will
|
||||
|
||||
@@ -58,8 +58,7 @@ tabPanels}
|
||||
navigation bar}
|
||||
|
||||
\item{collapsible}{\code{TRUE} to automatically collapse the navigation
|
||||
elements into a menu when the width of the browser is less than 940 pixels
|
||||
(useful for viewing on smaller touchscreen device)}
|
||||
elements into an expandable menu on mobile devices or narrow window widths.}
|
||||
|
||||
\item{fluid}{\code{TRUE} to use a fluid layout. \code{FALSE} to use a fixed
|
||||
layout.}
|
||||
|
||||
@@ -86,8 +86,8 @@ Positive, negative, and zero values are allowed.}
|
||||
automatically destroyed when its domain (if any) ends.}
|
||||
|
||||
\item{ignoreNULL}{Whether the action should be triggered (or value
|
||||
calculated, in the case of \code{eventReactive}) when the input is
|
||||
\code{NULL}. See Details.}
|
||||
calculated, in the case of \code{eventReactive}) when the input event expression
|
||||
is \code{NULL}. See Details.}
|
||||
|
||||
\item{ignoreInit}{If \code{TRUE}, then, when this \code{observeEvent} is
|
||||
first created/initialized, ignore the \code{handlerExpr} (the second
|
||||
|
||||
@@ -11,7 +11,10 @@ req(..., cancelOutput = FALSE)
|
||||
|
||||
\item{cancelOutput}{If \code{TRUE} and an output is being evaluated, stop
|
||||
processing as usual but instead of clearing the output, leave it in
|
||||
whatever state it happens to be in.}
|
||||
whatever state it happens to be in. If \code{"progress"}, do the same as \code{TRUE},
|
||||
but also keep the output in recalculating state; this is intended for cases
|
||||
when an in-progress calculation will not be completed in this reactive
|
||||
flush cycle, but is still expected to provide a result in the future.}
|
||||
}
|
||||
\value{
|
||||
The first value that was passed in.
|
||||
@@ -59,7 +62,7 @@ way to check for a value "inline" with its first use.
|
||||
|
||||
You can use \code{req(FALSE)} (i.e. no condition) if you've already performed
|
||||
all the checks you needed to by that point and just want to stop the reactive
|
||||
chain now. There is no advantange to this, except perhaps ease of readibility
|
||||
chain now. There is no advantage to this, except perhaps ease of readability
|
||||
if you have a complicated condition to check for (or perhaps if you'd like to
|
||||
divide your condition into nested \code{if} statements).
|
||||
}
|
||||
|
||||
@@ -48,7 +48,7 @@ but when \code{size} is set, it will be a box instead.}
|
||||
|
||||
\item{...}{Arguments passed to \code{selectInput()}.}
|
||||
|
||||
\item{options}{A list of options. See the documentation of \pkg{selectize.js}
|
||||
\item{options}{A list of options. See the documentation of \pkg{selectize.js}(\url{https://selectize.dev/docs/usage})
|
||||
for possible options (character option values inside \code{\link[base:AsIs]{base::I()}} will
|
||||
be treated as literal JavaScript code; see \code{\link[=renderDataTable]{renderDataTable()}}
|
||||
for details).}
|
||||
@@ -62,7 +62,7 @@ from a list of values.
|
||||
}
|
||||
\details{
|
||||
By default, \code{selectInput()} and \code{selectizeInput()} use the JavaScript library
|
||||
\pkg{selectize.js} (\url{https://github.com/selectize/selectize.js}) instead of
|
||||
\pkg{selectize.js} (\url{https://selectize.dev/}) instead of
|
||||
the basic select input element. To use the standard HTML select input
|
||||
element, use \code{selectInput()} with \code{selectize=FALSE}.
|
||||
|
||||
|
||||
@@ -121,7 +121,7 @@ any \code{imageOutput} or \code{plotOutput} in the app.
|
||||
Sends a custom message to the web page. \code{type} must be a
|
||||
single-element character vector giving the type of message, while
|
||||
\code{message} can be any jsonlite-encodable value. Custom messages
|
||||
have no meaning to Shiny itself; they are used soley to convey information
|
||||
have no meaning to Shiny itself; they are used solely to convey information
|
||||
to custom JavaScript logic in the browser. You can do this by adding
|
||||
JavaScript code to the browser that calls
|
||||
\code{Shiny.addCustomMessageHandler(type, function(message){...})}
|
||||
|
||||
@@ -19,3 +19,49 @@ includes extensive annotated examples.
|
||||
\seealso{
|
||||
\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})
|
||||
|
||||
Authors:
|
||||
\itemize{
|
||||
\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 Yihui Xie \email{yihui@posit.co}
|
||||
\item Jeff Allen
|
||||
\item Jonathan McPherson \email{jonathan@posit.co}
|
||||
\item Alan Dipert
|
||||
\item Barbara Borges
|
||||
}
|
||||
|
||||
Other contributors:
|
||||
\itemize{
|
||||
\item Posit Software, PBC [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]
|
||||
\item Mark Otto (Bootstrap library) [contributor]
|
||||
\item Jacob Thornton (Bootstrap library) [contributor]
|
||||
\item Bootstrap contributors (Bootstrap library) [contributor]
|
||||
\item Twitter, Inc (Bootstrap library) [copyright holder]
|
||||
\item Prem Nawaz Khan (Bootstrap accessibility plugin) [contributor]
|
||||
\item Victor Tsaran (Bootstrap accessibility plugin) [contributor]
|
||||
\item Dennis Lembree (Bootstrap accessibility plugin) [contributor]
|
||||
\item Srinivasu Chakravarthula (Bootstrap accessibility plugin) [contributor]
|
||||
\item Cathy O'Connor (Bootstrap accessibility plugin) [contributor]
|
||||
\item PayPal, Inc (Bootstrap accessibility plugin) [copyright holder]
|
||||
\item Stefan Petre (Bootstrap-datepicker library) [contributor, copyright holder]
|
||||
\item Andrew Rowls (Bootstrap-datepicker library) [contributor, copyright holder]
|
||||
\item Brian Reavis (selectize.js library) [contributor, copyright holder]
|
||||
\item Salmen Bejaoui (selectize-plugin-a11y library) [contributor, copyright holder]
|
||||
\item Denis Ineshin (ion.rangeSlider library) [contributor, copyright holder]
|
||||
\item Sami Samhuri (Javascript strftime library) [contributor, copyright holder]
|
||||
\item SpryMedia Limited (DataTables library) [contributor, copyright holder]
|
||||
\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]
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -59,7 +59,10 @@ deprecated functions in Shiny will be printed. See
|
||||
\code{\link[=shinyDeprecated]{shinyDeprecated()}} for more information.}
|
||||
\item{shiny.error (defaults to \code{NULL})}{This can be a function which is called when an error
|
||||
occurs. For example, \code{options(shiny.error=recover)} will result a
|
||||
the debugger prompt when an error occurs.}
|
||||
the debugger prompt when an error occurs. If the function takes an \code{error}
|
||||
argument, it will be called with the error object. For example,
|
||||
\code{options(shiny.error = function(error) { })} will receive the error
|
||||
condition object when an error occurs.}
|
||||
\item{shiny.fullstacktrace (defaults to \code{FALSE})}{Controls whether "pretty" (\code{FALSE}) or full
|
||||
stack traces (\code{TRUE}) are dumped to the console when errors occur during Shiny app execution.
|
||||
Pretty stack traces attempt to only show user-supplied code, but this pruning can't always
|
||||
@@ -69,8 +72,9 @@ be done 100\% correctly.}
|
||||
\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.}
|
||||
\item{shiny.json.digits (defaults to \code{16})}{The number of digits to use when converting
|
||||
numbers to JSON format to send to the client web browser.}
|
||||
\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.}
|
||||
\item{shiny.launch.browser (defaults to \code{interactive()})}{A boolean which controls the default behavior
|
||||
when an app is run. See \code{\link[=runApp]{runApp()}} for more information.}
|
||||
\item{shiny.mathjax.url (defaults to \code{"https://mathjax.rstudio.com/latest/MathJax.js"})}{
|
||||
@@ -91,7 +95,7 @@ This incurs a substantial performance penalty and should not be used in
|
||||
production.}
|
||||
\item{shiny.sanitize.errors (defaults to \code{FALSE})}{If \code{TRUE}, then normal errors (i.e.
|
||||
errors not wrapped in \code{safeError}) won't show up in the app; a simple
|
||||
generic error message is printed instead (the error and strack trace printed
|
||||
generic error message is printed instead (the error and stack trace printed
|
||||
to the console remain unchanged). If you want to sanitize errors in general, but you DO want a
|
||||
particular error \code{e} to get displayed to the user, then set this option
|
||||
to \code{TRUE} and use \code{stop(safeError(e))} for errors you want the
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
shinyUI(ui)
|
||||
}
|
||||
\arguments{
|
||||
\item{ui}{A user interace definition}
|
||||
\item{ui}{A user interface definition}
|
||||
}
|
||||
\value{
|
||||
The user interface definition, without modifications or side effects.
|
||||
|
||||
@@ -9,7 +9,8 @@ updateActionButton(
|
||||
session = getDefaultReactiveDomain(),
|
||||
inputId,
|
||||
label = NULL,
|
||||
icon = NULL
|
||||
icon = NULL,
|
||||
disabled = NULL
|
||||
)
|
||||
|
||||
updateActionLink(
|
||||
@@ -28,6 +29,9 @@ updateActionLink(
|
||||
\item{label}{The label to set for the input object.}
|
||||
|
||||
\item{icon}{An optional \code{\link[=icon]{icon()}} to appear on the button.}
|
||||
|
||||
\item{disabled}{If \code{TRUE}, the button will not be clickable; if \code{FALSE}, it
|
||||
will be.}
|
||||
}
|
||||
\description{
|
||||
Change the label or icon of an action button on the client
|
||||
@@ -74,13 +78,13 @@ server <- function(input, output, session) {
|
||||
label = "New label",
|
||||
icon = icon("calendar"))
|
||||
|
||||
# Leaves goButton2's label unchaged and
|
||||
# Leaves goButton2's label unchanged and
|
||||
# removes its icon
|
||||
updateActionButton(session, "goButton2",
|
||||
icon = character(0))
|
||||
|
||||
# Leaves goButton3's icon, if it exists,
|
||||
# unchaged and changes its label
|
||||
# unchanged and changes its label
|
||||
updateActionButton(session, "goButton3",
|
||||
label = "New label 3")
|
||||
|
||||
|
||||
@@ -62,7 +62,7 @@ the example section for a small demo of this feature.}
|
||||
\item{selected}{The initially selected value (or multiple values if \code{multiple = TRUE}). If not specified then defaults to the first value for
|
||||
single-select lists and no values for multiple select lists.}
|
||||
|
||||
\item{options}{A list of options. See the documentation of \pkg{selectize.js}
|
||||
\item{options}{A list of options. See the documentation of \pkg{selectize.js}(\url{https://selectize.dev/docs/usage})
|
||||
for possible options (character option values inside \code{\link[base:AsIs]{base::I()}} will
|
||||
be treated as literal JavaScript code; see \code{\link[=renderDataTable]{renderDataTable()}}
|
||||
for details).}
|
||||
|
||||
@@ -42,7 +42,7 @@ but when \code{size} is set, it will be a box instead.}
|
||||
|
||||
\item{...}{Arguments passed to \code{varSelectInput()}.}
|
||||
|
||||
\item{options}{A list of options. See the documentation of \pkg{selectize.js}
|
||||
\item{options}{A list of options. See the documentation of \pkg{selectize.js}(\url{https://selectize.dev/docs/usage})
|
||||
for possible options (character option values inside \code{\link[base:AsIs]{base::I()}} will
|
||||
be treated as literal JavaScript code; see \code{\link[=renderDataTable]{renderDataTable()}}
|
||||
for details).}
|
||||
@@ -57,7 +57,7 @@ from the column names of a data frame.
|
||||
\details{
|
||||
By default, \code{varSelectInput()} and \code{selectizeInput()} use the
|
||||
JavaScript library \pkg{selectize.js}
|
||||
(\url{https://github.com/selectize/selectize.js}) to instead of the basic
|
||||
(\url{https://selectize.dev/}) to instead of the basic
|
||||
select input element. To use the standard HTML select input element, use
|
||||
\code{selectInput()} with \code{selectize=FALSE}.
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"homepage": "https://shiny.rstudio.com",
|
||||
"repository": "github:rstudio/shiny",
|
||||
"name": "@types/rstudio-shiny",
|
||||
"version": "1.7.4-alpha.9002",
|
||||
"version": "1.8.0-alpha.9000",
|
||||
"license": "GPL-3.0-only",
|
||||
"main": "",
|
||||
"browser": "",
|
||||
@@ -24,7 +24,8 @@
|
||||
"@types/datatables.net": "^1.10.19",
|
||||
"@types/ion-rangeslider": "2.3.0",
|
||||
"@types/jquery": "3.5.14",
|
||||
"@types/selectize": "0.12.34"
|
||||
"@types/selectize": "0.12.34",
|
||||
"lit": "^3.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.14.3",
|
||||
@@ -33,6 +34,7 @@
|
||||
"@babel/preset-typescript": "^7.13.0",
|
||||
"@babel/runtime": "^7.14.0",
|
||||
"@deanc/esbuild-plugin-postcss": "^1.0.2",
|
||||
"@selectize/selectize": "https://github.com/selectize/selectize.js.git#e3f2e0b4aa251375bc21b5fcd8ca7d374a921f08",
|
||||
"@testing-library/dom": "^7.31.0",
|
||||
"@testing-library/jest-dom": "^5.12.0",
|
||||
"@testing-library/user-event": "^13.1.9",
|
||||
|
||||
@@ -1,12 +1,22 @@
|
||||
# Revdeps
|
||||
|
||||
## Failed to check (5)
|
||||
## Failed to check (15)
|
||||
|
||||
|package |version |error |warning |note |
|
||||
|:----------|:-------|:-----|:-------|:----|
|
||||
|ctsem |3.6.0 |1 | | |
|
||||
|diveR |? | | | |
|
||||
|loon.shiny |? | | | |
|
||||
|NA |? | | | |
|
||||
|SSVS |? | | | |
|
||||
|package |version |error |warning |note |
|
||||
|:------------------|:-------|:-----|:-------|:----|
|
||||
|bigPint |? | | | |
|
||||
|bioCancer |? | | | |
|
||||
|ctsem |3.9.1 |1 | | |
|
||||
|diveR |? | | | |
|
||||
|EBImage |? | | | |
|
||||
|GeneNetworkBuilder |? | | | |
|
||||
|InterCellar |? | | | |
|
||||
|LACE |? | | | |
|
||||
|loon.shiny |? | | | |
|
||||
|MatrixQCvis |? | | | |
|
||||
|modchart |? | | | |
|
||||
|omicsViewer |? | | | |
|
||||
|parcats |? | | | |
|
||||
|RQuantLib |0.4.19 |1 | | |
|
||||
|rstanarm |2.26.1 |1 | | |
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
## revdepcheck results
|
||||
|
||||
We checked 1039 reverse dependencies (1038 from CRAN + 1 from Bioconductor), comparing R CMD check results across CRAN and dev versions of this package.
|
||||
We checked 1172 reverse dependencies (1162 from CRAN + 10 from Bioconductor), comparing R CMD check results across CRAN and dev versions of this package.
|
||||
|
||||
* We saw 0 new problems
|
||||
* We failed to check 4 packages
|
||||
* We failed to check 5 packages
|
||||
|
||||
Issues with CRAN packages are summarised below.
|
||||
|
||||
@@ -12,4 +12,5 @@ Issues with CRAN packages are summarised below.
|
||||
* ctsem (NA)
|
||||
* diveR (NA)
|
||||
* loon.shiny (NA)
|
||||
* SSVS (NA)
|
||||
* RQuantLib (NA)
|
||||
* rstanarm (NA)
|
||||
|
||||
@@ -55,6 +55,14 @@ build({
|
||||
minify: true,
|
||||
});
|
||||
|
||||
build({
|
||||
...opts,
|
||||
entryPoints: [outDir + "selectize/js/selectize.js"],
|
||||
outfile: outDir + "selectize/js/selectize.min.js",
|
||||
minify: true,
|
||||
target: "es6",
|
||||
});
|
||||
|
||||
build({
|
||||
...opts,
|
||||
entryPoints: [outDir + "selectize/accessibility/js/selectize-plugin-a11y.js"],
|
||||
|
||||
@@ -1,18 +1,86 @@
|
||||
/* eslint-disable unicorn/filename-case */
|
||||
const protocol = (window.location.protocol === "https:") ? "wss:" : "ws:";
|
||||
|
||||
document.documentElement.classList.add("autoreload-enabled");
|
||||
|
||||
const protocol = window.location.protocol === "https:" ? "wss:" : "ws:";
|
||||
// Add trailing slash to path, if necessary, before appending "autoreload"
|
||||
const defaultPath = window.location.pathname.replace(/\/?$/, "/") + "autoreload/";
|
||||
const defaultPath =
|
||||
window.location.pathname.replace(/\/?$/, "/") + "autoreload/";
|
||||
const defaultUrl = `${protocol}//${window.location.host}${defaultPath}`;
|
||||
|
||||
// By default, use the defaultUrl. But if there's a data-ws-url attribute on our
|
||||
// <script> tag, use that instead.
|
||||
const wsUrl = document.currentScript.dataset.wsUrl || defaultUrl;
|
||||
const ws = new WebSocket(wsUrl);
|
||||
const wsUrl = document.currentScript?.dataset?.wsUrl || defaultUrl;
|
||||
|
||||
ws.onmessage = function (event) {
|
||||
if (event.data === "autoreload") {
|
||||
window.location.reload();
|
||||
/**
|
||||
* Connects to an autoreload URL and waits for the server to tell us what to do.
|
||||
*
|
||||
* @param url The ws:// or wss:// URL to connect to.
|
||||
* @returns true if the server requests a reload, or false if the connection was
|
||||
* successfully established but then closed without the server requesting a
|
||||
* reload
|
||||
* @throws A nondescript error if the connection fails to be established.
|
||||
*/
|
||||
async function autoreload(url: string): Promise<boolean> {
|
||||
const ws = new WebSocket(url);
|
||||
|
||||
let success = false;
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
ws.onopen = () => {
|
||||
success = true;
|
||||
};
|
||||
|
||||
ws.onerror = (err) => {
|
||||
reject(err);
|
||||
};
|
||||
|
||||
ws.onclose = () => {
|
||||
if (!success) {
|
||||
reject(new Error("WebSocket connection failed"));
|
||||
} else {
|
||||
resolve(false);
|
||||
}
|
||||
};
|
||||
|
||||
ws.onmessage = function (event) {
|
||||
if (event.data === "autoreload") {
|
||||
resolve(true);
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
async function sleep(ms: number) {
|
||||
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
async function initialize() {
|
||||
while (true) {
|
||||
try {
|
||||
if (await autoreload(wsUrl)) {
|
||||
window.location.reload();
|
||||
return;
|
||||
}
|
||||
} catch (err) {
|
||||
// It's possible for the autoreload() call to throw. If it does, that
|
||||
// means we tried but failed to connect to the autoreload socket. This
|
||||
// probably means that the entire `shiny run --reload` process was
|
||||
// restarted. As of today, the autoreload websocket port number is
|
||||
// randomly chosen for each `shiny run --reload` process, so it's
|
||||
// impossible for us to recover.
|
||||
console.debug("Giving up on autoreload");
|
||||
return;
|
||||
}
|
||||
// If we get here, the connection to the autoreload server was
|
||||
// successful but then got broken. Wait for a second, and then
|
||||
// try to re-establish the connection.
|
||||
await sleep(1000);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
initialize().catch((err) => {
|
||||
console.error(err);
|
||||
});
|
||||
|
||||
export {};
|
||||
|
||||
@@ -2,7 +2,11 @@ import $ from "jquery";
|
||||
import { hasDefinedProperty } from "../../utils";
|
||||
import { InputBinding } from "./inputBinding";
|
||||
|
||||
type ActionButtonReceiveMessageData = { label?: string; icon?: string | [] };
|
||||
type ActionButtonReceiveMessageData = {
|
||||
label?: string;
|
||||
icon?: string | [];
|
||||
disabled?: boolean;
|
||||
};
|
||||
|
||||
class ActionButtonInputBinding extends InputBinding {
|
||||
find(scope: HTMLElement): JQuery<HTMLElement> {
|
||||
@@ -38,34 +42,44 @@ class ActionButtonInputBinding extends InputBinding {
|
||||
receiveMessage(el: HTMLElement, data: ActionButtonReceiveMessageData): void {
|
||||
const $el = $(el);
|
||||
|
||||
// retrieve current label and icon
|
||||
let label: string = $el.text();
|
||||
let icon = "";
|
||||
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];
|
||||
// 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");
|
||||
if (iconHtml === $el.children()[0]) {
|
||||
// another check for robustness
|
||||
icon = $(iconHtml).prop("outerHTML");
|
||||
}
|
||||
}
|
||||
|
||||
// update the requested properties
|
||||
if (hasDefinedProperty(data, "label")) {
|
||||
label = data.label;
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
if (hasDefinedProperty(data, "disabled")) {
|
||||
if (data.disabled) {
|
||||
$el.attr("disabled", "");
|
||||
} else {
|
||||
$el.attr("disabled", null);
|
||||
}
|
||||
}
|
||||
|
||||
// update the requested properties
|
||||
if (hasDefinedProperty(data, "label")) {
|
||||
label = data.label;
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
unsubscribe(el: HTMLElement): void {
|
||||
|
||||
@@ -9,6 +9,7 @@ type CheckboxReceiveMessageData = { value?: CheckboxChecked; label?: string };
|
||||
|
||||
class CheckboxInputBinding extends InputBinding {
|
||||
find(scope: HTMLElement): JQuery<HTMLElement> {
|
||||
// Inputs also have .shiny-input-checkbox class
|
||||
return $(scope).find('input[type="checkbox"]');
|
||||
}
|
||||
getValue(el: CheckedHTMLElement): CheckboxChecked {
|
||||
|
||||
@@ -195,6 +195,7 @@ function fileInputBindingGetId(this: any, el: HTMLInputElement): string {
|
||||
|
||||
class FileInputBinding extends InputBinding {
|
||||
find(scope: HTMLElement): JQuery<HTMLElement> {
|
||||
// Inputs also have .shiny-input-file class
|
||||
return $(scope).find('input[type="file"]');
|
||||
}
|
||||
getId(el: HTMLInputElement): string {
|
||||
|
||||
@@ -44,7 +44,7 @@ class InputBinding {
|
||||
// 'data' should be an object with elements corresponding to value, min,
|
||||
// max, etc., as appropriate for the type of input object. It also should
|
||||
// trigger a change event.
|
||||
receiveMessage(el: HTMLElement, data: unknown): void {
|
||||
receiveMessage(el: HTMLElement, data: unknown): Promise<void> | void {
|
||||
throw "Not implemented";
|
||||
el; // unused var
|
||||
data; // unused var
|
||||
|
||||
@@ -20,6 +20,7 @@ function getLabelNode(el: NumberHTMLElement): JQuery<HTMLElement> {
|
||||
|
||||
class NumberInputBinding extends TextInputBindingBase {
|
||||
find(scope: HTMLElement): JQuery<HTMLElement> {
|
||||
// Inputs also have .shiny-input-number class
|
||||
return $(scope).find('input[type="number"]');
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import { TextInputBinding } from "./text";
|
||||
|
||||
class PasswordInputBinding extends TextInputBinding {
|
||||
find(scope: HTMLElement): JQuery<HTMLElement> {
|
||||
// Inputs also have .shiny-input-password class
|
||||
return $(scope).find('input[type="password"]');
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@ import $ from "jquery";
|
||||
import { InputBinding } from "./inputBinding";
|
||||
import { $escape, hasDefinedProperty, updateLabel } from "../../utils";
|
||||
import { indirectEval } from "../../utils/eval";
|
||||
import type { NotUndefined } from "../../utils/extraTypes";
|
||||
|
||||
type SelectHTMLElement = HTMLSelectElement & { nonempty: boolean };
|
||||
|
||||
@@ -42,6 +41,7 @@ function isSelectize(el: HTMLElement): boolean {
|
||||
|
||||
class SelectInputBinding extends InputBinding {
|
||||
find(scope: HTMLElement): JQuery<HTMLElement> {
|
||||
// Inputs also have .shiny-input-select class
|
||||
return $(scope).find("select");
|
||||
}
|
||||
getType(el: HTMLElement): string | null {
|
||||
@@ -60,10 +60,14 @@ class SelectInputBinding extends InputBinding {
|
||||
getId(el: SelectHTMLElement): string {
|
||||
return InputBinding.prototype.getId.call(this, el) || el.name;
|
||||
}
|
||||
getValue(
|
||||
el: HTMLElement
|
||||
): NotUndefined<ReturnType<JQuery<HTMLElement>["val"]>> {
|
||||
return $(el).val() as NotUndefined<ReturnType<JQuery<HTMLElement>["val"]>>;
|
||||
getValue(el: SelectHTMLElement): any {
|
||||
if (!isSelectize(el)) {
|
||||
return $(el).val();
|
||||
} else {
|
||||
const selectize = this._selectize(el);
|
||||
|
||||
return selectize?.getValue();
|
||||
}
|
||||
}
|
||||
setValue(el: SelectHTMLElement, value: string): void {
|
||||
if (!isSelectize(el)) {
|
||||
@@ -144,6 +148,7 @@ class SelectInputBinding extends InputBinding {
|
||||
selectize.settings.load = function (query: string, callback: CallbackFn) {
|
||||
const settings = selectize.settings;
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-floating-promises */
|
||||
$.ajax({
|
||||
url: data.url,
|
||||
data: {
|
||||
@@ -291,6 +296,7 @@ class SelectInputBinding extends InputBinding {
|
||||
control.destroy();
|
||||
control = $el.selectize(settings)[0].selectize as SelectizeInfo;
|
||||
}
|
||||
|
||||
return control;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -197,7 +197,16 @@ class SliderInputBinding extends TextInputBindingBase {
|
||||
msg.to = data.value[1];
|
||||
} else {
|
||||
if (Array.isArray(data.value)) {
|
||||
throw "Slider only contains a single value and cannot be updated with an array";
|
||||
const errorReason = [
|
||||
"an empty array.",
|
||||
"a single-value array.",
|
||||
"an array with more than two values.",
|
||||
];
|
||||
throw (
|
||||
"Slider requires two values to update with an array, " +
|
||||
"but message value was " +
|
||||
errorReason[Math.min(data.value.length, 2)]
|
||||
);
|
||||
}
|
||||
msg.from = data.value;
|
||||
}
|
||||
|
||||
@@ -29,7 +29,8 @@ class TextInputBindingBase extends InputBinding {
|
||||
// id that ends in '-selectized'. The .not() selector below
|
||||
// is to prevent textInputBinding from accidentally picking up
|
||||
// this hidden element as a shiny input (#2396)
|
||||
|
||||
//
|
||||
// Inputs also now have .shiny-input-text class
|
||||
return $inputs.not('input[type="text"][id$="-selectized"]');
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import { TextInputBinding } from "./text";
|
||||
|
||||
class TextareaInputBinding extends TextInputBinding {
|
||||
find(scope: HTMLElement): JQuery<HTMLElement> {
|
||||
// Inputs now also have the .shiny-input-textarea class
|
||||
return $(scope).find("textarea");
|
||||
}
|
||||
}
|
||||
|
||||
531
srcts/src/components/errorConsole.ts
Normal file
531
srcts/src/components/errorConsole.ts
Normal file
@@ -0,0 +1,531 @@
|
||||
/* eslint-disable @typescript-eslint/naming-convention */
|
||||
|
||||
import { LitElement, html, css } from "lit";
|
||||
import { ShinyClientError } from "../shiny/error";
|
||||
|
||||
const buttonStyles = css`
|
||||
button {
|
||||
background-color: transparent;
|
||||
outline: none;
|
||||
border-style: none;
|
||||
padding: var(--space-3);
|
||||
border-radius: var(--space-1);
|
||||
font-size: var(--font-lg);
|
||||
background-color: inherit;
|
||||
display: block;
|
||||
}
|
||||
|
||||
button > svg {
|
||||
display: block;
|
||||
}
|
||||
`;
|
||||
class ShinyErrorConsole extends LitElement {
|
||||
static styles = [
|
||||
css`
|
||||
:host {
|
||||
/* We declare hard pixel values here to avoid body font size changes
|
||||
messing up the size of the console. This was an issue with bslib setting
|
||||
the body font-size at 16px relative to base shiny's 14px. */
|
||||
--font-md: 14px;
|
||||
--font-lg: 16px;
|
||||
--font-xl: 18px;
|
||||
|
||||
/* These are all taken from open-props */
|
||||
--space-1: 6px;
|
||||
--space-2: calc(var(--space-1) * 2);
|
||||
--space-3: calc(var(--space-1) * 3);
|
||||
--space-4: calc(var(--space-1) * 4);
|
||||
--space-8: calc(var(--space-1) * 8);
|
||||
|
||||
--red-2: #ffc9c9;
|
||||
--red-6: #fa5252;
|
||||
--red-7: #f03e3e;
|
||||
--red-8: #e03131;
|
||||
--red-10: #b02525;
|
||||
--red-11: #962020;
|
||||
--red-12: #7d1a1a;
|
||||
|
||||
--gray-1: #f8f9fa;
|
||||
--gray-2: #e9ecef;
|
||||
--gray-3: #dee2e6;
|
||||
--gray-4: #ced4da;
|
||||
--gray-6: #868e96;
|
||||
--gray-8: #6c757d;
|
||||
|
||||
--green-8: #51cf66;
|
||||
|
||||
--shadow-color: 220 3% 15%;
|
||||
--shadow-strength: 1%;
|
||||
--shadow-3: 0 -1px 3px 0 hsl(var(--shadow-color) /
|
||||
calc(var(--shadow-strength) + 2%)),
|
||||
0 1px 2px -5px hsl(var(--shadow-color) /
|
||||
calc(var(--shadow-strength) + 2%)),
|
||||
0 2px 5px -5px hsl(var(--shadow-color) /
|
||||
calc(var(--shadow-strength) + 4%)),
|
||||
0 4px 12px -5px hsl(var(--shadow-color) /
|
||||
calc(var(--shadow-strength) + 5%)),
|
||||
0 12px 15px -5px hsl(var(--shadow-color) /
|
||||
calc(var(--shadow-strength) + 7%));
|
||||
|
||||
--ring-shadow: 0 0 0 1px var(--gray-2);
|
||||
|
||||
/* How fast should the message pop in and out of the screen? */
|
||||
--animation-speed: 500ms;
|
||||
|
||||
/* Taken from open-props */
|
||||
--ease-3: cubic-bezier(0.25, 0, 0.3, 1);
|
||||
--animation-slide-in-left: slide-in-left var(--animation-speed)
|
||||
var(--ease-3);
|
||||
|
||||
--animation-slide-out-left: slide-out-left var(--animation-speed)
|
||||
var(--ease-3);
|
||||
--modal-bg-color: white;
|
||||
|
||||
position: fixed;
|
||||
top: var(--space-1);
|
||||
right: var(--space-1);
|
||||
z-index: 1000;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
background-color: var(--modal-bg-color);
|
||||
border-radius: var(--space-1);
|
||||
|
||||
animation: var(--animation-slide-in-left);
|
||||
box-shadow: var(--shadow-3), var(--ring-shadow);
|
||||
|
||||
/* Dont let the error console burst out of the viewport */
|
||||
max-height: calc(100vh - 2 * var(--space-1));
|
||||
}
|
||||
|
||||
@keyframes slide-in-left {
|
||||
from {
|
||||
transform: translateX(100%);
|
||||
}
|
||||
}
|
||||
@keyframes slide-out-left {
|
||||
to {
|
||||
transform: translateX(100%);
|
||||
}
|
||||
}
|
||||
|
||||
:host(.leaving) {
|
||||
animation: var(--animation-slide-out-left);
|
||||
}
|
||||
|
||||
.header {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
align-items: flex-start;
|
||||
gap: var(--space-2);
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: var(--font-xl);
|
||||
margin-right: auto;
|
||||
padding: var(--space-3);
|
||||
line-height: 1;
|
||||
font-weight: 600;
|
||||
color: var(--red-12);
|
||||
}
|
||||
|
||||
${buttonStyles}
|
||||
|
||||
button:hover {
|
||||
background-color: var(--gray-2);
|
||||
}
|
||||
|
||||
.toggle-button {
|
||||
width: fit-content;
|
||||
border: none;
|
||||
aspect-ratio: 1;
|
||||
border-color: var(--gray-4);
|
||||
}
|
||||
|
||||
.close-button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
color: var(--red-11);
|
||||
}
|
||||
|
||||
.close-button > svg {
|
||||
margin-right: 3px;
|
||||
}
|
||||
|
||||
.toggle-button:focus {
|
||||
outline: 1px solid black;
|
||||
}
|
||||
|
||||
.toggle-icon {
|
||||
transition: transform var(--animation-speed) ease-in-out;
|
||||
}
|
||||
|
||||
:host(.collapsed) .toggle-icon {
|
||||
transform: scaleX(-1) scaleY(-1);
|
||||
}
|
||||
|
||||
:host(.collapsed) .close-button {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.content {
|
||||
display: block;
|
||||
padding-inline: var(--space-4);
|
||||
padding-block-start: 0;
|
||||
padding-block-end: var(--space-4);
|
||||
max-height: 100%;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
:host(.collapsed) .content {
|
||||
display: none;
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
||||
toggleCollapsed(): void {
|
||||
this.classList.toggle("collapsed");
|
||||
// Remove focus from the toggle button
|
||||
(this.querySelector(".toggle-button") as HTMLButtonElement)?.blur();
|
||||
}
|
||||
|
||||
handleDismissAll(): void {
|
||||
// Animate out by adding the class "leaving" and then
|
||||
// wait for the animation to finish before removing the element
|
||||
this.classList.add("leaving");
|
||||
this.addEventListener("animationend", () => {
|
||||
this.remove();
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
return html` <div class="header">
|
||||
<span class="title"> Shiny Client Errors </span>
|
||||
<button
|
||||
class="close-button"
|
||||
@click=${this.handleDismissAll}
|
||||
title="Dismiss all console messages and close console"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="1.5"
|
||||
height="1em"
|
||||
width="1em"
|
||||
stroke="currentColor"
|
||||
class="close-icon"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="M6 18L18 6M6 6l12 12"
|
||||
/>
|
||||
</svg>
|
||||
Dismiss all
|
||||
</button>
|
||||
<button class="toggle-button" @click=${this.toggleCollapsed}>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="1.5"
|
||||
height="1em"
|
||||
width="1em"
|
||||
stroke="currentColor"
|
||||
class="toggle-icon"
|
||||
>
|
||||
<path
|
||||
class="collapse"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="M4.5 19.5l15-15m0 0H8.25m11.25 0v11.25"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<slot class="content"></slot>`;
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("shiny-error-console", ShinyErrorConsole);
|
||||
|
||||
export class ShinyErrorMessage extends LitElement {
|
||||
static properties = {
|
||||
headline: {},
|
||||
message: {},
|
||||
};
|
||||
|
||||
headline = "";
|
||||
message = "";
|
||||
|
||||
static styles = [
|
||||
css`
|
||||
:host {
|
||||
color: var(--red-11);
|
||||
display: block;
|
||||
font-size: var(--font-md);
|
||||
|
||||
position: relative;
|
||||
--icon-size: var(--font-lg)
|
||||
|
||||
/* Reset box sizing */
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.container {
|
||||
display: flex;
|
||||
gap: var(--space-2);
|
||||
}
|
||||
|
||||
.contents {
|
||||
width: 40ch;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-1);
|
||||
padding-block-start: 0;
|
||||
padding-block-end: var(--space-3);
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
:host(:last-of-type) .contents {
|
||||
|
||||
padding-block-end: var(--space-1);
|
||||
}
|
||||
|
||||
.contents > h3 {
|
||||
font-size: 1em;
|
||||
font-weight: 500;
|
||||
color: var(--red-12);
|
||||
}
|
||||
|
||||
.contents > * {
|
||||
margin-block: 0;
|
||||
}
|
||||
|
||||
.error-message {
|
||||
font-family: "Courier New", Courier, monospace;
|
||||
}
|
||||
|
||||
.decoration-container {
|
||||
flex-shrink: 0;
|
||||
position: relative;
|
||||
|
||||
--line-w: 2px;
|
||||
--dot-size: 11px;
|
||||
}
|
||||
|
||||
:host(:hover) .decoration-container {
|
||||
--scale: 1.25;
|
||||
}
|
||||
|
||||
.vertical-line {
|
||||
margin-inline: auto;
|
||||
width: var(--line-w);
|
||||
height: 100%;
|
||||
|
||||
background-color: var(--red-10);
|
||||
}
|
||||
|
||||
:host(:first-of-type) .vertical-line {
|
||||
height: calc(100% - var(--dot-size));
|
||||
margin-top: var(--dot-size);
|
||||
}
|
||||
|
||||
.dot {
|
||||
position: absolute;
|
||||
width: var(--dot-size);
|
||||
height: var(--dot-size);
|
||||
top: calc(-1px + var(--dot-size) / 2);
|
||||
left: calc(50% - var(--dot-size) / 2);
|
||||
border-radius: 100%;
|
||||
transform: scale(var(--scale, 1));
|
||||
|
||||
color: var(--red-6);
|
||||
background-color: var(--red-10);
|
||||
}
|
||||
|
||||
.actions {
|
||||
transform: scaleX(0);
|
||||
transition: transform calc(var(--animation-speed) / 2) ease-in-out;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
/* Delay transition on mouseout so the buttons don't jump away if the user
|
||||
overshoots them with their mouse */
|
||||
:host(:not(:hover)) .actions {
|
||||
transition-delay: 0.15s;
|
||||
}
|
||||
|
||||
:host(:hover) .actions {
|
||||
transform: scaleX(1);
|
||||
}
|
||||
|
||||
${buttonStyles}
|
||||
|
||||
.copy-button {
|
||||
padding: 0;
|
||||
width: var(--space-8);
|
||||
height: var(--space-8);
|
||||
position: relative;
|
||||
--pad: var(--space-2);
|
||||
}
|
||||
|
||||
.copy-button-inner {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: inherit;
|
||||
transition: transform 0.5s;
|
||||
transform-style: preserve-3d;
|
||||
}
|
||||
|
||||
/* Animate flipping to the other side when the .copy-success class is
|
||||
added to the host */
|
||||
:host(.copy-success) .copy-button-inner {
|
||||
transform: rotateY(180deg);
|
||||
}
|
||||
|
||||
/* Position the front and back side */
|
||||
.copy-button .front,
|
||||
.copy-button .back {
|
||||
--side: calc(100% - 2 * var(--pad));
|
||||
position: absolute;
|
||||
inset: var(--pad);
|
||||
height: var(--side);
|
||||
width: var(--side);
|
||||
-webkit-backface-visibility: hidden; /* Safari */
|
||||
backface-visibility: hidden;
|
||||
}
|
||||
|
||||
.copy-button:hover .copy-button-inner {
|
||||
background-color: var(--gray-2);
|
||||
}
|
||||
|
||||
/* Style the back side */
|
||||
.copy-button .back {
|
||||
--pad: var(--space-1);
|
||||
color: var(--green-8);
|
||||
transform: rotateY(180deg);
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
||||
async copyErrorToClipboard(): Promise<void> {
|
||||
await navigator.clipboard.writeText(this.message);
|
||||
|
||||
this.classList.add("copy-success");
|
||||
|
||||
// After a second, remove the copy success class
|
||||
setTimeout(() => {
|
||||
this.classList.remove("copy-success");
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
||||
render() {
|
||||
return html`
|
||||
<div class="container">
|
||||
<div class="decoration-container">
|
||||
<div class="vertical-line"></div>
|
||||
<div class="dot"></div>
|
||||
</div>
|
||||
<div class="contents">
|
||||
<h3>${this.headline}</h3>
|
||||
<pre class="error-message">${this.message}</pre>
|
||||
</div>
|
||||
|
||||
<div class="actions">
|
||||
<button
|
||||
class="copy-button"
|
||||
@click=${this.copyErrorToClipboard}
|
||||
title="Copy error to clipboard"
|
||||
>
|
||||
<div class="copy-button-inner">
|
||||
<svg
|
||||
class="front"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="1.5"
|
||||
stroke="currentColor"
|
||||
height="1em"
|
||||
width="1em"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="M15.666 3.888A2.25 2.25 0 0013.5 2.25h-3c-1.03 0-1.9.693-2.166 1.638m7.332 0c.055.194.084.4.084.612v0a.75.75 0 01-.75.75H9a.75.75 0 01-.75-.75v0c0-.212.03-.418.084-.612m7.332 0c.646.049 1.288.11 1.927.184 1.1.128 1.907 1.077 1.907 2.185V19.5a2.25 2.25 0 01-2.25 2.25H6.75A2.25 2.25 0 014.5 19.5V6.257c0-1.108.806-2.057 1.907-2.185a48.208 48.208 0 011.927-.184"
|
||||
/>
|
||||
</svg>
|
||||
|
||||
<svg
|
||||
class="back"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="1.5"
|
||||
stroke="currentColor"
|
||||
height="1em"
|
||||
width="1em"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="M9 12.75L11.25 15 15 9.75M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("shiny-error-message", ShinyErrorMessage);
|
||||
|
||||
/**
|
||||
* Function to show an error message to user in shiny-error-message web
|
||||
* component. Only shows the error if we're in development mode.
|
||||
* @param e - Error object to show to user. This is whatever is caught in
|
||||
* a try-catch statement so it may be a string or it may be a proper Error
|
||||
* object.
|
||||
*/
|
||||
export function showErrorInClientConsole(e: unknown): void {
|
||||
if (!Shiny.inDevMode()) {
|
||||
// If we're in production, don't show the error to the user
|
||||
return;
|
||||
}
|
||||
|
||||
let errorMsg: string | null = null;
|
||||
let headline = "Error on client while running Shiny app";
|
||||
|
||||
if (typeof e === "string") {
|
||||
errorMsg = e;
|
||||
} else if (e instanceof ShinyClientError) {
|
||||
errorMsg = e.message;
|
||||
headline = e.headline;
|
||||
} else if (e instanceof Error) {
|
||||
errorMsg = e.message;
|
||||
} else {
|
||||
errorMsg = "Unknown error";
|
||||
}
|
||||
|
||||
// Check to see if an Error Console Container element already exists. If it
|
||||
// doesn't we need to add it before putting an error on the screen
|
||||
let errorConsoleContainer = document.querySelector("shiny-error-console");
|
||||
if (!errorConsoleContainer) {
|
||||
errorConsoleContainer = document.createElement("shiny-error-console");
|
||||
document.body.appendChild(errorConsoleContainer);
|
||||
}
|
||||
|
||||
const errorConsole = document.createElement("shiny-error-message");
|
||||
errorConsole.setAttribute("headline", headline || "");
|
||||
errorConsole.setAttribute("message", errorMsg);
|
||||
|
||||
errorConsoleContainer.appendChild(errorConsole);
|
||||
}
|
||||
@@ -180,6 +180,7 @@ class FileUploader extends FileProcessor {
|
||||
onFile(file: File, cont: () => void): void {
|
||||
this.onProgress(file, 0);
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-floating-promises */
|
||||
$.ajax(this.uploadUrl, {
|
||||
type: "POST",
|
||||
cache: false,
|
||||
|
||||
@@ -8,10 +8,7 @@ import type {
|
||||
} from "../inputPolicies";
|
||||
import { shinyAppBindOutput, shinyAppUnbindOutput } from "./initedMethods";
|
||||
import { sendImageSizeFns } from "./sendImageSize";
|
||||
|
||||
const boundInputs: {
|
||||
[key: string]: { binding: InputBinding; node: HTMLElement };
|
||||
} = {};
|
||||
import { ShinyClientError } from "./error";
|
||||
|
||||
type BindScope = HTMLElement | JQuery<HTMLElement>;
|
||||
|
||||
@@ -44,6 +41,150 @@ function valueChangeCallback(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
* immediately invoked function to keep the sets private and not clutter the
|
||||
* scope.
|
||||
*/
|
||||
const bindingsRegistry = (() => {
|
||||
type BindingTypes = "input" | "output";
|
||||
|
||||
/**
|
||||
* Keyed by binding IDs to the array of each type of binding that ID is associated for in current app state.
|
||||
*
|
||||
* Ideally the
|
||||
* value would be a length 1 array but in some (invalid) cases there could be
|
||||
* multiple types for a single ID.
|
||||
*/
|
||||
type IdToBindingTypes = Map<string, BindingTypes[]>;
|
||||
|
||||
// Main store of bindings.
|
||||
const bindings: IdToBindingTypes = new Map();
|
||||
|
||||
/**
|
||||
* Checks if the bindings registry is valid. Currently this just checks if IDs
|
||||
* are duplicated within a binding typ but in the future could be expanded to
|
||||
* check more conditions.
|
||||
*
|
||||
* @description IDs are allowed to be duplicated across binding types, but
|
||||
* when duplicated within a binding type we report all uses of the ID.
|
||||
* Currently the IDs are typically stored in the bound element's `id`
|
||||
* attribute, in which case they really *should* be globally unique for
|
||||
* accessibility and other reasons. However, in practice our bindings still
|
||||
* work as long as inputs the IDs within a binding type don't overlap.
|
||||
*
|
||||
* @returns ShinyClientError if current ID bindings are invalid, otherwise
|
||||
* returns an ok status.
|
||||
*/
|
||||
function checkValidity():
|
||||
| { status: "error"; error: ShinyClientError }
|
||||
| { status: "ok" } {
|
||||
type BindingCounts = { [T in BindingTypes]: number };
|
||||
const duplicateIds = new Map<string, BindingCounts>();
|
||||
|
||||
// count duplicate IDs of each binding type
|
||||
bindings.forEach((idTypes, id) => {
|
||||
const counts: { [T in BindingTypes]: number } = { input: 0, output: 0 };
|
||||
|
||||
idTypes.forEach((type) => (counts[type] += 1));
|
||||
|
||||
// If there's a single duplication of ids across both binding types, then
|
||||
// when we're not in devmode, we allow this to pass because a good amount of
|
||||
// existing applications use this pattern even though its invalid. Eventually
|
||||
// this behavior should be removed.
|
||||
if (counts.input === 1 && counts.output === 1 && !Shiny.inDevMode()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If we have duplicated IDs, then add them to the set of duplicated IDs
|
||||
// to be reported to the user.
|
||||
if (counts.input + counts.output > 1) {
|
||||
duplicateIds.set(id, counts);
|
||||
}
|
||||
});
|
||||
|
||||
if (duplicateIds.size === 0) return { status: "ok" };
|
||||
|
||||
const duplicateIdMsg = Array.from(duplicateIds.entries())
|
||||
.map(([id, counts]) => {
|
||||
const messages = [
|
||||
pluralize(counts.input, "input"),
|
||||
pluralize(counts.output, "output"),
|
||||
]
|
||||
.filter((msg) => msg !== "")
|
||||
.join(" and ");
|
||||
|
||||
return `- "${id}": ${messages}`;
|
||||
})
|
||||
.join("\n");
|
||||
|
||||
return {
|
||||
status: "error",
|
||||
error: new ShinyClientError({
|
||||
headline: "Duplicate input/output IDs found",
|
||||
message: `The following ${
|
||||
duplicateIds.size === 1 ? "ID was" : "IDs were"
|
||||
} repeated:\n${duplicateIdMsg}`,
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a binding id to the binding ids registry
|
||||
* @param id Id to add
|
||||
* @param bindingType Binding type, either "input" or "output"
|
||||
*/
|
||||
function addBinding(id: string, bindingType: BindingTypes): void {
|
||||
if (id === "") {
|
||||
throw new ShinyClientError({
|
||||
headline: `Empty ${bindingType} ID found`,
|
||||
message: "Binding IDs must not be empty.",
|
||||
});
|
||||
}
|
||||
|
||||
const existingBinding = bindings.get(id);
|
||||
|
||||
if (existingBinding) {
|
||||
existingBinding.push(bindingType);
|
||||
} else {
|
||||
bindings.set(id, [bindingType]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a binding id from the binding ids registry
|
||||
* @param id Id to remove
|
||||
* @param bindingType Binding type, either "input" or "output"
|
||||
*/
|
||||
function removeBinding(id: string, bindingType: BindingTypes): void {
|
||||
const existingBinding = bindings.get(id);
|
||||
|
||||
if (existingBinding) {
|
||||
const index = existingBinding.indexOf(bindingType);
|
||||
if (index > -1) {
|
||||
existingBinding.splice(index, 1);
|
||||
}
|
||||
}
|
||||
|
||||
if (existingBinding?.length === 0) {
|
||||
bindings.delete(id);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
addBinding,
|
||||
removeBinding,
|
||||
checkValidity,
|
||||
};
|
||||
})();
|
||||
|
||||
function pluralize(num: number, word: string): string {
|
||||
if (num === 0) return "";
|
||||
if (num === 1) return `${num} ${word}`;
|
||||
return `${num} ${word}s`;
|
||||
}
|
||||
|
||||
type BindInputsCtx = {
|
||||
inputs: InputValidateDecorator;
|
||||
inputsRate: InputRateDecorator;
|
||||
@@ -82,10 +223,11 @@ function bindInputs(
|
||||
|
||||
for (let j = 0; j < matches.length; j++) {
|
||||
const el = matches[j];
|
||||
if (el.hasAttribute("data-shiny-no-bind-input")) continue;
|
||||
const id = binding.getId(el);
|
||||
|
||||
// Check if ID is falsy, or if already bound
|
||||
if (!id || boundInputs[id]) continue;
|
||||
// Don't bind if ID is falsy or is currently bound
|
||||
if (!id || $(el).hasClass("shiny-bound-input")) continue;
|
||||
|
||||
const type = binding.getType(el);
|
||||
const effectiveId = type ? id + ":" + type : id;
|
||||
@@ -122,11 +264,7 @@ function bindInputs(
|
||||
);
|
||||
}
|
||||
|
||||
boundInputs[id] = {
|
||||
binding: binding,
|
||||
node: el,
|
||||
};
|
||||
|
||||
bindingsRegistry.addBinding(id, "input");
|
||||
$(el).trigger({
|
||||
type: "shiny:bound",
|
||||
// @ts-expect-error; Can not remove info on a established, malformed Event object
|
||||
@@ -139,14 +277,14 @@ function bindInputs(
|
||||
return inputItems;
|
||||
}
|
||||
|
||||
function bindOutputs(
|
||||
async function bindOutputs(
|
||||
{
|
||||
sendOutputHiddenState,
|
||||
maybeAddThemeObserver,
|
||||
outputBindings,
|
||||
}: BindInputsCtx,
|
||||
scope: BindScope = document.documentElement
|
||||
): void {
|
||||
): Promise<void> {
|
||||
const $scope = $(scope);
|
||||
|
||||
const bindings = outputBindings.getBindings();
|
||||
@@ -155,6 +293,7 @@ function bindOutputs(
|
||||
const binding = bindings[i].binding;
|
||||
const matches = binding.find($scope) || [];
|
||||
|
||||
// First loop over the matches and assemble map of id->element
|
||||
for (let j = 0; j < matches.length; j++) {
|
||||
const el = matches[j];
|
||||
const id = binding.getId(el);
|
||||
@@ -183,10 +322,12 @@ function bindOutputs(
|
||||
|
||||
const bindingAdapter = new OutputBindingAdapter(el, binding);
|
||||
|
||||
shinyAppBindOutput(id, bindingAdapter);
|
||||
await shinyAppBindOutput(id, bindingAdapter);
|
||||
$el.data("shiny-output-binding", bindingAdapter);
|
||||
$el.addClass("shiny-bound-output");
|
||||
if (!$el.attr("aria-live")) $el.attr("aria-live", "polite");
|
||||
|
||||
bindingsRegistry.addBinding(id, "output");
|
||||
$el.trigger({
|
||||
type: "shiny:bound",
|
||||
// @ts-expect-error; Can not remove info on a established, malformed Event object
|
||||
@@ -221,7 +362,8 @@ function unbindInputs(
|
||||
const id = binding.getId(el);
|
||||
|
||||
$(el).removeClass("shiny-bound-input");
|
||||
delete boundInputs[id];
|
||||
|
||||
bindingsRegistry.removeBinding(id, "input");
|
||||
binding.unsubscribe(el);
|
||||
$(el).trigger({
|
||||
type: "shiny:unbound",
|
||||
@@ -252,6 +394,8 @@ function unbindOutputs(
|
||||
const id = bindingAdapter.binding.getId(outputs[i]);
|
||||
|
||||
shinyAppUnbindOutput(id, bindingAdapter);
|
||||
|
||||
bindingsRegistry.removeBinding(id, "output");
|
||||
$el.removeClass("shiny-bound-output");
|
||||
$el.removeData("shiny-output-binding");
|
||||
$el.trigger({
|
||||
@@ -269,12 +413,24 @@ function unbindOutputs(
|
||||
|
||||
// (Named used before TS conversion)
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
function _bindAll(
|
||||
async function _bindAll(
|
||||
shinyCtx: BindInputsCtx,
|
||||
scope: BindScope
|
||||
): ReturnType<typeof bindInputs> {
|
||||
bindOutputs(shinyCtx, scope);
|
||||
return bindInputs(shinyCtx, scope);
|
||||
): Promise<ReturnType<typeof bindInputs>> {
|
||||
await bindOutputs(shinyCtx, scope);
|
||||
const currentInputs = bindInputs(shinyCtx, scope);
|
||||
|
||||
// Check to make sure the bindings setup is valid. By checking the validity
|
||||
// _after_ we've attempted all the bindings we can give the user a more
|
||||
// complete error message that contains everything they will need to fix. If
|
||||
// we threw as we saw collisions then the user would fix the first collision,
|
||||
// re-run, and then see the next collision, etc.
|
||||
const bindingValidity = bindingsRegistry.checkValidity();
|
||||
if (bindingValidity.status === "error") {
|
||||
throw bindingValidity.error;
|
||||
}
|
||||
|
||||
return currentInputs;
|
||||
}
|
||||
function unbindAll(
|
||||
shinyCtx: BindInputsCtx,
|
||||
@@ -284,10 +440,13 @@ function unbindAll(
|
||||
unbindInputs(scope, includeSelf);
|
||||
unbindOutputs(shinyCtx, scope, includeSelf);
|
||||
}
|
||||
function bindAll(shinyCtx: BindInputsCtx, scope: BindScope): void {
|
||||
async function bindAll(
|
||||
shinyCtx: BindInputsCtx,
|
||||
scope: BindScope
|
||||
): Promise<void> {
|
||||
// _bindAll returns input values; it doesn't send them to the server.
|
||||
// Shiny.bindAll needs to send the values to the server.
|
||||
const currentInputItems = _bindAll(shinyCtx, scope);
|
||||
const currentInputItems = await _bindAll(shinyCtx, scope);
|
||||
|
||||
const inputs = shinyCtx.inputs;
|
||||
|
||||
|
||||
16
srcts/src/shiny/error.ts
Normal file
16
srcts/src/shiny/error.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
/**
|
||||
* Custom error to throw when a we detect a known error type on the client
|
||||
* @param headline - Error headline to show to user. Will be shown in normal
|
||||
* font and should be used to give plain language description of problem
|
||||
* @param message - Error message to show to user. Will be shown in monospaced
|
||||
* font
|
||||
*/
|
||||
export class ShinyClientError extends Error {
|
||||
headline: string;
|
||||
|
||||
constructor({ headline, message }: { headline: string; message: string }) {
|
||||
super(message);
|
||||
this.name = "ShinyClientError";
|
||||
this.headline = headline;
|
||||
}
|
||||
}
|
||||
@@ -27,6 +27,7 @@ import type { Handler, ShinyApp } from "./shinyapp";
|
||||
import { addCustomMessageHandler } from "./shinyapp";
|
||||
import { initInputBindings } from "../bindings/input";
|
||||
import { initOutputBindings } from "../bindings/output";
|
||||
import { showErrorInClientConsole } from "../components/errorConsole";
|
||||
|
||||
interface Shiny {
|
||||
version: string;
|
||||
@@ -67,6 +68,14 @@ interface Shiny {
|
||||
// Eventually deprecate
|
||||
// For old-style custom messages - should deprecate and migrate to new
|
||||
oncustommessage?: Handler;
|
||||
|
||||
/**
|
||||
* Method to check if Shiny is running in development mode. By packaging as a
|
||||
* method, we can we can avoid needing to look for the `__SHINY_DEV_MODE__`
|
||||
* variable in the global scope.
|
||||
* @returns `true` if Shiny is running in development mode, `false` otherwise.
|
||||
*/
|
||||
inDevMode: () => boolean;
|
||||
}
|
||||
|
||||
let windowShiny: Shiny;
|
||||
@@ -107,11 +116,23 @@ function setShiny(windowShiny_: Shiny): void {
|
||||
windowShiny.renderHtmlAsync = renderHtmlAsync;
|
||||
windowShiny.renderHtml = renderHtml;
|
||||
|
||||
windowShiny.inDevMode = () => {
|
||||
if ("__SHINY_DEV_MODE__" in window)
|
||||
return Boolean(window.__SHINY_DEV_MODE__);
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
$(function () {
|
||||
// Init Shiny a little later than document ready, so user code can
|
||||
// run first (i.e. to register bindings)
|
||||
setTimeout(function () {
|
||||
initShiny(windowShiny);
|
||||
setTimeout(async function () {
|
||||
try {
|
||||
await initShiny(windowShiny);
|
||||
} catch (e) {
|
||||
showErrorInClientConsole(e);
|
||||
throw e;
|
||||
}
|
||||
}, 1);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ import { registerNames as singletonsRegisterNames } from "./singletons";
|
||||
import type { InputPolicyOpts } from "../inputPolicies/inputPolicy";
|
||||
|
||||
// "init_shiny.js"
|
||||
function initShiny(windowShiny: Shiny): void {
|
||||
async function initShiny(windowShiny: Shiny): Promise<void> {
|
||||
setShinyObj(windowShiny);
|
||||
const shinyapp = (windowShiny.shinyapp = new ShinyApp());
|
||||
|
||||
@@ -95,8 +95,8 @@ function initShiny(windowShiny: Shiny): void {
|
||||
};
|
||||
}
|
||||
|
||||
windowShiny.bindAll = function (scope: BindScope) {
|
||||
bindAll(shinyBindCtx(), scope);
|
||||
windowShiny.bindAll = async function (scope: BindScope) {
|
||||
await bindAll(shinyBindCtx(), scope);
|
||||
};
|
||||
windowShiny.unbindAll = function (scope: BindScope, includeSelf = false) {
|
||||
unbindAll(shinyBindCtx(), scope, includeSelf);
|
||||
@@ -146,7 +146,7 @@ function initShiny(windowShiny: Shiny): void {
|
||||
// have a reference to the DOM element, which would prevent it from being
|
||||
// GC'd.
|
||||
const initialValues = mapValues(
|
||||
_bindAll(shinyBindCtx(), document.documentElement),
|
||||
await _bindAll(shinyBindCtx(), document.documentElement),
|
||||
(x) => x.value
|
||||
);
|
||||
|
||||
|
||||
@@ -55,8 +55,8 @@ function setShinyUser(user: string): void {
|
||||
function shinyForgetLastInputValue(name: string): void {
|
||||
validateShinyHasBeenSet().forgetLastInputValue(name);
|
||||
}
|
||||
function shinyBindAll(scope: BindScope): void {
|
||||
validateShinyHasBeenSet().bindAll(scope);
|
||||
async function shinyBindAll(scope: BindScope): Promise<void> {
|
||||
await validateShinyHasBeenSet().bindAll(scope);
|
||||
}
|
||||
function shinyUnbindAll(scope: BindScope, includeSelf = false): void {
|
||||
validateShinyHasBeenSet().unbindAll(scope, includeSelf);
|
||||
@@ -65,8 +65,11 @@ function shinyInitializeInputs(scope: BindScope): void {
|
||||
validateShinyHasBeenSet().initializeInputs(scope);
|
||||
}
|
||||
|
||||
function shinyAppBindOutput(id: string, binding: OutputBindingAdapter): void {
|
||||
shinyShinyApp().bindOutput(id, binding);
|
||||
async function shinyAppBindOutput(
|
||||
id: string,
|
||||
binding: OutputBindingAdapter
|
||||
): Promise<void> {
|
||||
await shinyShinyApp().bindOutput(id, binding);
|
||||
}
|
||||
|
||||
function shinyAppUnbindOutput(
|
||||
|
||||
@@ -50,6 +50,7 @@ function initReactlog(): void {
|
||||
window.escape(shinyAppConfig().sessionId);
|
||||
|
||||
// send notification
|
||||
/* eslint-disable @typescript-eslint/no-floating-promises */
|
||||
$.get(url, function (result: "marked" | void) {
|
||||
if (result !== "marked") return;
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user