Compare commits

...

37 Commits

Author SHA1 Message Date
nstrayer
d0fbc5e769 Document (GitHub Actions) 2021-07-02 19:35:07 +00:00
Nick Strayer
eb99e0bc7d Added clarifying message for prereqs for compiling sass 2021-07-02 15:33:14 -04:00
Nick Strayer
508e3d46c2 Recompiled sass after changes to the disconnected overlay styles 2021-07-02 15:32:53 -04:00
Nick Strayer
f2b7185c2f Add max width and height attributes to shiny-disconected-overlay to make sure it is the full page size 2021-07-02 15:18:05 -04:00
Winston Chang
d1e7e6c63a Add note about R version support 2021-07-02 14:04:35 -05:00
Winston Chang
29b574bf94 Merge pull request #3456 from heds1/update-rejected-ports 2021-07-01 16:34:25 -05:00
Barret Schloerke
7e4248bbca TypeScript: Globally declare Shiny variable, window.Shiny variable, and Shiny type (#3457) 2021-07-01 14:51:16 -04:00
heds1
fee267dc2e docs: update runapp port parameter docs, and add three more tcp ports to be blocked 2021-07-01 21:40:59 +12:00
Carson Sievert
9864130435 Use random inline styles to ensure transitionend fires everytime (#3452)
* Follow up to #3333: use random inline styles to ensure transitionend fires everytime

* yarn lint (GitHub Actions)

* Add missing '#'

* yarn lint (GitHub Actions)

Co-authored-by: cpsievert <cpsievert@users.noreply.github.com>
2021-06-30 15:26:49 -05:00
Carson Sievert
c9770cbd03 Close #3443: Fix sliderInput()'s grid tick positioning without Bootstrap (#3444) 2021-06-29 15:56:47 -05:00
Carson Sievert
ed6a40ba41 Close #3446: get removeModel() working with Bootstrap 4 (#3447) 2021-06-29 15:54:48 -05:00
Carson
3c22cdf90c roxygenize 2021-06-29 15:11:39 -05:00
Marcus Spittler
e55749b897 Update utils.R example to validate() (#2809)
Added an empty option to `choices` in `selectizeInput` in order to make the second `need` statement in `validate` meaningful. Otherwise the second `need` ("Please choose a state") is never displayed.
2021-06-29 15:10:42 -05:00
Carson Sievert
88cd87a5f7 Revert "Set selectize dropdownParent to "body" to prevent clipping" (#3450)
This reverts commit ce90d5cd0a.
2021-06-29 12:22:33 -05:00
Barret Schloerke
244fdc72bc Leverage more eslint rules (#3439) 2021-06-22 21:20:54 -04:00
Barret Schloerke
b9d163a71d TypeScript other distributed JS/CSS files (#3436) 2021-06-18 10:18:51 -04:00
Barret Schloerke
61ee467dee Replace dev versions with -alpha versions for JS code (#3435) 2021-06-17 16:02:39 -04:00
Carson Sievert
7c0829d553 Change from .nav-item to .dropdown-item when inserting inside .dropdown-menu (#3434)
* Change from .nav-item to .dropdown-item when inserting inside .dropdown-menu

* Update srcts/src/shiny/shinyapp.ts

* Update srcts/src/shiny/shinyapp.ts

* yarn lint (GitHub Actions)

* yarn build (GitHub Actions)

Co-authored-by: cpsievert <cpsievert@users.noreply.github.com>
2021-06-16 17:44:03 -05:00
Carson Sievert
68eb4c6965 update news for breaking insertTab() change (#3433) 2021-06-16 16:41:07 -05:00
Barret Schloerke
6d4015f61b ./package.json updates to make TS Types package cleaner to install (#3430)
Co-authored-by: Barret Schloerke <schloerke@gmail.com>
Co-authored-by: Carson Sievert <cpsievert1@gmail.com>
2021-06-16 16:08:50 -04:00
Barret Schloerke
d89513b7e0 Match casing for plot alt text "Plot object" (#3432)
* Match spelling for Plot Object phrase

From #3398

* Document (GitHub Actions)

* Consistent casing for `"Plot object"` for plot alt text

Co-authored-by: schloerke <schloerke@users.noreply.github.com>
2021-06-16 15:08:13 -05:00
Carson Sievert
a159594a45 insertTab(position = "after") by default (#3431)
* Follow up to #3404: change insertTab()'s default position so that default behavior doesn't change

* Update news

* Document (GitHub Actions)

Co-authored-by: cpsievert <cpsievert@users.noreply.github.com>
2021-06-16 15:01:36 -05:00
Carson Sievert
78c62ad819 Various cleanup (#3428)
* Follow up to #3366: don't change sliderInput()'s default accent color

* Update news

* nav_append not tab_append 🤦

* bslib no longer tries to mark a non-tabPanel as active
2021-06-15 16:45:23 -05:00
Barret Schloerke
b3247d5a3b Move ./srcts configs to top level to support types installation from GitHub (#3425) 2021-06-15 14:18:53 -04:00
Winston Chang
91f920e14c Merge pull request #3413 from rstudio/feature/selectize-dropdown-parent-body
Set selectize dropdownParent to "body" to prevent clipping
2021-06-15 11:50:37 -05:00
Carson Sievert
bcb7cde44b insertTab() now handles position correctly when target is NULL (#3404)
* Close #3403: insertTab() now handles position correctly when target is NULL

* Have insertTab()'s target default to NULL

* yarn tsc (GitHub Actions)

* yarn build (GitHub Actions)

Co-authored-by: cpsievert <cpsievert@users.noreply.github.com>
2021-06-14 15:51:38 -05:00
Carson Sievert
052c9458b7 yarn add node-gyp; yarn build (#3424) 2021-06-14 15:51:03 -05:00
Barret Schloerke
3fe8c27d21 Export TypeScript type definitions to local folder (#3418) 2021-06-14 14:25:05 -04:00
Barret Schloerke
1dd256b210 TypeScript: Remove any types / improve type definitions (#3414) 2021-06-14 14:22:39 -04:00
Carson Sievert
dc9c6ae769 Better color constrasting in sliderInput() (#3366)
* Better color constrasting in sliderInput()

Closes https://github.com/rstudio/bslib/issues/228

* Update build script; recompile

* bslib tabsets now include data-bs-toggle
2021-06-14 12:48:57 -05:00
Carson Sievert
2cdafed2e0 Use ggplot2::get_alt_text() if available to provide better default alt text (#3398)
* Close #3397: Use ggplot2::get_alt_text() if available to provide more informative default alt text for ggplots in renderPlot()

* Update R/render-plot.R

Co-authored-by: Winston Chang <winston@stdout.org>

* better Rd docs

* make logic more self-contained

* Add news

Co-authored-by: Winston Chang <winston@stdout.org>
2021-06-14 10:22:07 -05:00
JJ Allaire
ce90d5cd0a Set selectize dropdownParent to "body" to prevent clipping
To prevent clipping of the selectize drop-down we set the dropdownParent to "body". This might be necessary if e.g. overflow-x: scroll is set on it's container, which forces overflow-y to 'auto' (as per https://developer.mozilla.org/en-US/docs/Web/CSS/overflow-y).

See option docs here: https://github.com/selectize/selectize.js/blob/master/docs/usage.md

Additional discussion of usage here: https://github.com/selectize/selectize.js/issues/192
2021-06-09 19:41:10 -04:00
Barret Schloerke
b4caa9137d Distribute TypeScript code into separate files (#3317)
Co-authored-by: Barret Schloerke <schloerke@gmail.com>
Co-authored-by: Carson Sievert <cpsievert1@gmail.com>
2021-06-09 14:54:47 -04:00
Carson Sievert
dcca77c936 Fix tab input value updating for BS4 dropdowns (#3412)
* Fix tab input value updating for BS4 dropdowns

* Add comments

* yarn build (GitHub Actions)

* Better comment

* yarn lint (GitHub Actions)

* yarn build (GitHub Actions)

Co-authored-by: Barret Schloerke <barret@rstudio.com>
Co-authored-by: schloerke <schloerke@users.noreply.github.com>
Co-authored-by: cpsievert <cpsievert@users.noreply.github.com>
2021-06-02 15:55:32 -05:00
Carson Sievert
871b1baacc Follow up to #3410: bump version and update news (#3411) 2021-06-02 13:03:09 -05:00
Carson Sievert
4deb699066 Bootstrap 5 support (#3410)
* Bootstrap 5 support for modals & showcase mode

* selectizeInput() BS5 compatibility

* Both BS4 and 5 define window.bootstrap

* Document (GitHub Actions)

Co-authored-by: cpsievert <cpsievert@users.noreply.github.com>
2021-06-02 12:36:04 -05:00
Carson Sievert
ccc8e053c6 Use bslib's new nav() api to implement tabPanel() and friends (#3388)
* Use bslib's new nav() api to implement tabPanel() and friends

* bslib won't be re-exporting prepend/append tab since they've been superceded by insertTab()

* Update DESCRIPTION

* Use the new bslib::page_navbar()

* Leverage bslib::page_navbar()'s more intelligent title->windowTitle handling

Closes #2310

* fix name change

* Make sure navbarPage() isn't browsable by default
2021-06-02 12:10:41 -05:00
311 changed files with 28340 additions and 28050 deletions

View File

@@ -21,3 +21,19 @@
^TODO-promises.md$
^manualtests$
^\.github$
^\.yarn$
^\.vscode$
^\.madgerc$
^\.prettierrc\.yml$
^babel\.config\.json$
^jest\.config\.js$
^package\.json$
^tsconfig\.json$
^yarn\.lock$
^node_modules$
^coverage$
^.ignore$
^\.browserslistrc$
^\.eslintrc\.yml$
^\.yarnrc\.yml$

108
.eslintrc.yml Normal file
View File

@@ -0,0 +1,108 @@
root: true
env:
browser: true
es6: true
extends:
- 'eslint:recommended'
- 'plugin:@typescript-eslint/recommended'
- 'plugin:jest/recommended'
- 'prettier/@typescript-eslint'
- 'plugin:prettier/recommended'
- 'plugin:jest-dom/recommended'
globals:
Atomics: readonly
SharedArrayBuffer: readonly
parser: '@typescript-eslint/parser'
parserOptions:
ecmaVersion: 2018
sourceType: module
plugins:
- '@typescript-eslint'
- prettier
- jest-dom
- unicorn
rules:
"@typescript-eslint/explicit-function-return-type":
- off
"@typescript-eslint/no-explicit-any":
- off
"@typescript-eslint/explicit-module-boundary-types":
- error
default-case:
- error
indent:
- error
- 2
- SwitchCase: 1
linebreak-style:
- error
- unix
quotes:
- error
- double
- avoid-escape
semi:
- error
- always
newline-after-var:
- error
- always
dot-location:
- error
- property
camelcase:
# - error
- "off"
unicorn/filename-case:
- error
- case: camelCase
"@typescript-eslint/array-type":
- error
- default: array-simple
readonly: array-simple
"@typescript-eslint/consistent-indexed-object-style":
- error
- index-signature
"@typescript-eslint/sort-type-union-intersection-members":
- error
"@typescript-eslint/consistent-type-imports":
- error
"@typescript-eslint/naming-convention":
- error
- selector: default
format: [camelCase]
- selector: method
modifiers: [private]
format: [camelCase]
leadingUnderscore: require
- selector: method
modifiers: [protected]
format: [camelCase]
leadingUnderscore: require
- selector: variable
format: [camelCase]
trailingUnderscore: forbid
leadingUnderscore: forbid
- selector: parameter
format: [camelCase]
trailingUnderscore: allow
leadingUnderscore: forbid
- selector: [enum, enumMember]
format: [PascalCase]
- selector: typeLike
format: [PascalCase]
custom:
regex: "(t|T)ype$"
match: false

2
.gitattributes vendored
View File

@@ -1,4 +1,6 @@
/NEWS merge=union
/inst/www/shared/shiny.js -merge -diff
/inst/www/shared/shiny-*.js -merge -diff
/inst/www/shared/shiny*.css -merge -diff
*.min.js -merge -diff
*.js.map -merge -diff

View File

@@ -71,13 +71,12 @@ jobs:
shell: Rscript {0}
run: |
pak::local_install_dev_deps(upgrade = TRUE)
pak::pkg_install("sessioninfo")
pak::pkg_install("devtools")
- name: Session info
shell: Rscript {0}
run: |
options(width = 100)
pak::pkg_install("sessioninfo")
pkgs <- installed.packages()[, "Package"]
sessioninfo::session_info(pkgs, include_base = TRUE)
@@ -100,6 +99,7 @@ jobs:
- name: Document
run: |
Rscript -e 'pak::pkg_install("devtools")'
Rscript -e 'devtools::document()'
git add man/\* NAMESPACE
git commit -m 'Document (GitHub Actions)' || echo "No documentation changes to commit"
@@ -123,28 +123,48 @@ jobs:
key: ${{ matrix.config.os }}-${{ matrix.config.node }}-yarn-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
${{ matrix.config.os }}-${{ matrix.config.node }}-yarn-
- name: Sync DESCRIPTION and package.json versions
run: |
Rscript -e 'pak::pkg_install("jsonlite")'
Rscript -e 'pkg <- jsonlite::read_json("package.json", simplifyVector = TRUE)' \
-e 'version <- as.list(read.dcf("DESCRIPTION")[1,])$Version' \
-e 'pkg$version <- gsub("^(\\d+).(\\d+).(\\d+).(.+)$", "\\1.\\2.\\3-alpha.\\4", version)' \
-e 'pkg$files <- as.list(pkg$files)' \
-e 'jsonlite::write_json(pkg, path = "package.json", pretty = TRUE, auto_unbox = TRUE)'
git add package.json && git commit -m 'sync package version (GitHub Actions)' || echo "No version changes to commit"
- name: Build JS
run: |
cd srcts
tree src
tree srcts
rm -r srcts/types
yarn install --immutable && yarn build
git add ./src && git commit -m 'yarn lint (GitHub Actions)' || echo "No yarn lint changes to commit"
git add ../inst && git commit -m 'yarn build (GitHub Actions)' || echo "No yarn build changes to commit"
- name: Check JS build is latest
run: |
./tools/checkJSCurrent.sh
git add ./srcts/src && git commit -m 'yarn lint (GitHub Actions)' || echo "No yarn lint changes to commit"
git add ./srcts/types && git commit -m 'yarn tsc (GitHub Actions)' || echo "No type definition changes to commit"
git add ./inst && git commit -m 'yarn build (GitHub Actions)' || echo "No yarn build changes to commit"
if [ -n "$(git status --porcelain)" ]
then
git status --porcelain
>&2 echo "The above files changed when we built the JavaScript assets."
exit 1
else
echo "No difference detected; TypeScript build is current."
fi
- name: Git Push (PR)
uses: r-lib/actions/pr-push@master
if: github.event_name == 'pull_request'
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
- name: Git Push (MASTER)
- name: Verify no un-pushed commits (MASTER)
if: github.event_name == 'push'
run: |
git push https://${{github.actor}}:${{secrets.GITHUB_TOKEN}}@github.com/${{github.repository}}.git HEAD:${{ github.ref }} || echo "No changes to push"
# Can't push to a protected branch
if [ -z "`git cherry`"]; then
echo "Un-pushed commits:"
git cherry -v
echo "\nCan not push to a protected branch. Exiting"
exit 1
fi
# Execute after pushing, as no updated files will be produced
- name: Test TypeScript code

13
.gitignore vendored
View File

@@ -11,5 +11,18 @@ README.html
.*.Rnb.cached
tools/yarn-error.log
# TypeScript / yarn
node_modules/
.cache
.yarn/*
!.yarn/releases
!.yarn/plugins
!.yarn/sdks
!.yarn/versions
.pnp.*
coverage/
madge.svg
# GHA remotes installation
.github/r-depends.rds

7
.madgerc Normal file
View File

@@ -0,0 +1,7 @@
{
"detectiveOptions": {
"ts": {
"skipTypeImports": true
}
}
}

File diff suppressed because one or more lines are too long

View File

@@ -3,5 +3,7 @@ nodeLinker: node-modules
plugins:
- path: .yarn/plugins/@yarnpkg/plugin-outdated.cjs
spec: "https://github.com/mskelton/yarn-plugin-outdated/raw/main/bundles/@yarnpkg/plugin-outdated.js"
- path: .yarn/plugins/@yarnpkg/plugin-interactive-tools.cjs
spec: "@yarnpkg/plugin-interactive-tools"
yarnPath: .yarn/releases/yarn-2.4.0.cjs

View File

@@ -1,7 +1,7 @@
Package: shiny
Type: Package
Title: Web Application Framework for R
Version: 1.6.0.9000
Version: 1.6.0.9021
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"),
@@ -91,7 +91,7 @@ Imports:
withr,
commonmark (>= 1.7),
glue (>= 1.3.2),
bslib (>= 0.2.4.9003),
bslib (>= 0.2.5.9002),
cachem,
ellipsis,
lifecycle (>= 0.2.0)
@@ -199,7 +199,11 @@ Collate:
'test.R'
'update-input.R'
'utils-lang.R'
'version_bs_date_picker.R'
'version_ion_range_slider.R'
'version_jquery.R'
'version_selectize.R'
'version_strftime.R'
'viewer.R'
RoxygenNote: 7.1.1
Encoding: UTF-8

22
NEWS.md
View File

@@ -1,3 +1,7 @@
Addresses #2521: Updated the list of TCP ports that will [be rejected](https://chromium.googlesource.com/chromium/src.git/+/refs/heads/main/net/base/port_util.cc)
by default in runapp.R, adding 5060, 5061 and 6566. Added documentation describing the port range (3000:8000)
and which ports are rejected.
shiny 1.6.0.9000
================
@@ -7,19 +11,23 @@ shiny 1.6.0.9000
* The `format` and `locale` arguments to `sliderInput()` have been removed. They have been deprecated since 0.10.2.2 (released on 2014-12-08).
* Closed #3403: `insertTab()`'s `position` parameter now defaults to `"after"` instead of `"before"`. This has the benefit of allowing us to fix a bug in positioning when `target = NULL`, but has the drawback of changing the default behavior when `target` is not `NULL`. (#3404)
### New features and improvements
* All uses of `list(...)` have been replaced with `rlang::list2(...)`. This means that you can use trailing `,` without error and use rlang's `!!!` operator to "splice" a list of argument values into `...`. We think this'll be particularly useful for passing a list of `tabPanel()` to their consumers (i.e., `tabsetPanel()`, `navbarPage()`, etc). For example, `tabs <- list(tabPanel("A", "a"), tabPanel("B", "b")); navbarPage(!!!tabs)`. (#3315 and #3328)
* Bootstrap 5 support. (#3410 and rstudio/bslib#304)
* As explained [here](https://rstudio.github.io/bslib/index.html#basic-usage), to opt-in to Bootstrap 5, provide `bslib::bs_theme(version = 5)` to a page layout function with a `theme` argument (e.g., `fluidPage()`, `navbarPage()`, etc).
* Numerous improvements tabset panels (i.e., `tabPanel()`, `navbarMenu()`, `tabsetPanel()`, `navbarPage()`, etc) (#3315):
* Closed #3322: `tabsetPanel()` and `navlistPanel()` gain `header`/`footer` arguments (inspired by `navbarPage()`'s already existing `header`/`footer`), making it easier to include content that should appear on every tab.
* Closed #3313 and #1823: More informative error when non-`tabPanel()`/`shiny.tag` objects are supplied to `...`.
* Closed #3321: New informative warning when `shiny.tag` object(s) are supplied to `...`. In this case we will continue to create an "empty" nav item and include the content on every tab, but the warning will mention the (new) `header`/`footer` args, which is likely what the user wants.
* Closed #3320: The HTML markup that `tabPanel()` et. al generate (for Bootstrap nav) is now Bootstrap 4+ compliant when used with `theme = bslib::bs_theme()`.
* Closed #1928: `NULL` values are now dropped instead of producing an empty nav item.
* Closed #3322, #3313, #1823, #3321, #3320, #1928, and #2310: Various improvements to `navbarPage()`, `tabsetPanel()`, `tabPanel()`, `navbarMenu()`, etc. Also, these functions are now powered by the `{bslib}` package's new `nav()` API (consider using `{bslib}`'s API to create better looking and more fully featured navs). (#3388)
* All uses of `list(...)` have been replaced with `rlang::list2(...)`. This means that you can use trailing `,` without error and use rlang's `!!!` operator to "splice" a list of argument values into `...`. We think this'll be particularly useful for passing a list of `tabPanel()` to their consumers (i.e., `tabsetPanel()`, `navbarPage()`, etc). For example, `tabs <- list(tabPanel("A", "a"), tabPanel("B", "b")); navbarPage(!!!tabs)`. (#3315 and #3328)
* `icon(lib="fontawesome")` is now powered by the `{fontawesome}` package, which will make it easier to use the latest FA icons in the future (by updating the `{fontawesome}` package). (#3302)
* Closed #3397: `renderPlot()` new uses `ggplot2::get_alt_text()` to inform an `alt` text default (for `{ggplot2}` plots). (#3398)
* `modalDialog()` gains support for `size = "xl"`. (#3410)
### Other improvements
* Shiny's core JavaScript code was converted to TypeScript. For the latest development information, please see the [README.md in `./srcts`](https://github.com/rstudio/shiny/tree/master/srcts). (#3296)

View File

@@ -380,8 +380,10 @@ collapseSizes <- function(padding) {
#' (useful for viewing on smaller touchscreen device)
#' @param fluid `TRUE` to use a fluid layout. `FALSE` to use a fixed
#' layout.
#' @param windowTitle The title that should be displayed by the browser window.
#' Useful if `title` is not a string.
#' @param windowTitle the browser window title (as a character string). The
#' default value, `NA`, means to use any character strings that appear in
#' `title` (if none are found, the host URL of the page is displayed by
#' default).
#' @inheritParams bootstrapPage
#' @param icon Optional icon to appear on a `navbarMenu` tab.
#'
@@ -425,70 +427,18 @@ navbarPage <- function(title,
collapsible = FALSE,
fluid = TRUE,
theme = NULL,
windowTitle = title,
windowTitle = NA,
lang = NULL) {
# alias title so we can avoid conflicts w/ title in withTags
pageTitle <- title
# navbar class based on options
# TODO: tagFunction() the navbar logic?
navbarClass <- "navbar navbar-default"
position <- match.arg(position)
if (!is.null(position))
navbarClass <- paste0(navbarClass, " navbar-", position)
if (inverse)
navbarClass <- paste(navbarClass, "navbar-inverse")
if (!is.null(id))
selected <- restoreInput(id = id, default = selected)
# build the tabset
tabset <- buildTabset(..., ulClass = "nav navbar-nav", id = id, selected = selected)
containerClass <- paste0("container", if (fluid) "-fluid")
# built the container div dynamically to support optional collapsibility
if (collapsible) {
navId <- paste0("navbar-collapse-", p_randomInt(1000, 10000))
containerDiv <- div(class=containerClass,
div(class="navbar-header",
tags$button(type="button", class="navbar-toggle collapsed",
`data-toggle`="collapse", `data-target`=paste0("#", navId),
span(class="sr-only", "Toggle navigation"),
span(class="icon-bar"),
span(class="icon-bar"),
span(class="icon-bar")
),
span(class="navbar-brand", pageTitle)
),
div(class="navbar-collapse collapse", id=navId, tabset$navList)
)
} else {
containerDiv <- div(class=containerClass,
div(class="navbar-header",
span(class="navbar-brand", pageTitle)
),
tabset$navList
)
}
# build the main tab content div
contentDiv <- div(class=containerClass)
if (!is.null(header))
contentDiv <- tagAppendChild(contentDiv, div(class="row", header))
contentDiv <- tagAppendChild(contentDiv, tabset$content)
if (!is.null(footer))
contentDiv <- tagAppendChild(contentDiv, div(class="row", footer))
# build the page
bootstrapPage(
title = windowTitle,
remove_first_class(bslib::page_navbar(
..., title = title, id = id, selected = selected,
position = match.arg(position),
header = header, footer = footer,
inverse = inverse, collapsible = collapsible,
fluid = fluid,
theme = theme,
lang = lang,
tags$nav(class=navbarClass, role="navigation", containerDiv),
contentDiv
)
window_title = windowTitle,
lang = lang
))
}
#' @param menuName A name that identifies this `navbarMenu`. This
@@ -498,19 +448,7 @@ navbarPage <- function(title,
#' @rdname navbarPage
#' @export
navbarMenu <- function(title, ..., menuName = title, icon = NULL) {
icon <- prepTabIcon(icon)
structure(list(title = title,
menuName = menuName,
tabs = list2(...),
# Here for legacy reasons
# https://github.com/cran/miniUI/blob/74c87d3/R/layout.R#L369
iconClass = tagGetAttribute(icon, "class"),
icon = icon),
class = "shiny.navbarmenu")
}
isNavbarMenu <- function(x) {
inherits(x, "shiny.navbarmenu")
bslib::nav_menu(title, ..., value = menuName, icon = icon)
}
#' Create a well panel
@@ -645,39 +583,14 @@ helpText <- function(...) {
#' @export
#' @describeIn tabPanel Create a tab panel that can be included within a [tabsetPanel()] or a [navbarPage()].
tabPanel <- function(title, ..., value = title, icon = NULL) {
icon <- prepTabIcon(icon)
pane <- div(
class = "tab-pane",
title = title,
`data-value` = value,
# Here for legacy reasons
# https://github.com/cran/miniUI/blob/74c87d/R/layout.R#L395
`data-icon-class` = tagGetAttribute(icon, "class"),
...
)
attr(pane, "_shiny_icon") <- icon
pane
}
isTabPanel <- function(x) {
if (!inherits(x, "shiny.tag")) return(FALSE)
class <- tagGetAttribute(x, "class") %||% ""
"tab-pane" %in% strsplit(class, "\\s+")[[1]]
bslib::nav(title, ..., value = value, icon = icon)
}
#' @export
#' @describeIn tabPanel Create a tab panel that drops the title argument.
#' This function should be used within `tabsetPanel(type = "hidden")`. See [tabsetPanel()] for example usage.
tabPanelBody <- function(value, ..., icon = NULL) {
if (
!is.character(value) ||
length(value) != 1 ||
any(is.na(value)) ||
nchar(value) == 0
) {
stop("`value` must be a single, non-empty string value")
}
tabPanel(title = NULL, ..., value = value, icon = icon)
bslib::nav_content(value, ..., icon = icon)
}
#' Create a tabset panel
@@ -753,20 +666,17 @@ tabsetPanel <- function(...,
header = NULL,
footer = NULL) {
if (!is.null(id))
selected <- restoreInput(id = id, default = selected)
func <- switch(
match.arg(type),
tabs = bslib::navs_tab,
pills = bslib::navs_pill,
hidden = bslib::navs_hidden
)
type <- match.arg(type)
tabset <- buildTabset(..., ulClass = paste0("nav nav-", type), id = id, selected = selected)
tags$div(
class = "tabbable",
!!!dropNulls(list(
tabset$navList,
header,
tabset$content,
footer
))
# bslib adds a class to make the content browsable() by default,
# but that's probably too big of a change for shiny
remove_first_class(
func(..., id = id, selected = selected, header = header, footer = footer)
)
}
@@ -822,275 +732,18 @@ navlistPanel <- function(...,
well = TRUE,
fluid = TRUE,
widths = c(4, 8)) {
if (!is.null(id))
selected <- restoreInput(id = id, default = selected)
tabset <- buildTabset(
..., ulClass = "nav nav-pills nav-stacked",
textFilter = function(text) tags$li(class = "navbar-brand", text),
id = id, selected = selected
)
row <- if (fluid) fluidRow else fixedRow
row(
column(widths[[1]], class = if (well) "well", tabset$navList),
column(
widths[[2]],
!!!dropNulls(list(header, tabset$content, footer))
)
)
remove_first_class(bslib::navs_pill_list(
..., id = id, selected = selected,
header = header, footer = footer,
well = well, fluid = fluid, widths = widths
))
}
# Helpers to build tabsetPanels (& Co.) and their elements
markTabAsSelected <- function(x) {
attr(x, "selected") <- TRUE
remove_first_class <- function(x) {
class(x) <- class(x)[-1]
x
}
isTabSelected <- function(x) {
isTRUE(attr(x, "selected", exact = TRUE))
}
containsSelectedTab <- function(tabs) {
any(vapply(tabs, isTabSelected, logical(1)))
}
findAndMarkSelectedTab <- function(tabs, selected, foundSelected) {
tabs <- lapply(tabs, function(x) {
if (foundSelected || is.character(x)) {
# Strings are not selectable items
} else if (isNavbarMenu(x)) {
# Recur for navbarMenus
res <- findAndMarkSelectedTab(x$tabs, selected, foundSelected)
x$tabs <- res$tabs
foundSelected <<- res$foundSelected
} else {
# Base case: regular tab item. If the `selected` argument is
# provided, check for a match in the existing tabs; else,
# mark first available item as selected
if (is.null(selected)) {
foundSelected <<- TRUE
x <- markTabAsSelected(x)
} else {
tabValue <- x$attribs$`data-value` %||% x$attribs$title
if (identical(selected, tabValue)) {
foundSelected <<- TRUE
x <- markTabAsSelected(x)
}
}
}
return(x)
})
return(list(tabs = tabs, foundSelected = foundSelected))
}
prepTabIcon <- function(x = NULL) {
if (is.null(x)) return(NULL)
if (!inherits(x, "shiny.tag")) {
stop(
"`icon` must be a `shiny.tag` object. ",
"Try passing `icon()` (or `tags$i()`) to the `icon` parameter.",
call. = FALSE
)
}
is_fa <- grepl("fa-", tagGetAttribute(x, "class") %||% "", fixed = TRUE)
if (!is_fa) {
return(x)
}
# for font-awesome we specify fixed-width
tagAppendAttributes(x, class = "fa-fw")
}
# Text filter for navbarMenu's (plain text) separators
navbarMenuTextFilter <- function(text) {
if (grepl("^\\-+$", text)) tags$li(class = "divider")
else tags$li(class = "dropdown-header", text)
}
# This function is called internally by navbarPage, tabsetPanel
# and navlistPanel
buildTabset <- function(..., ulClass, textFilter = NULL, id = NULL,
selected = NULL, foundSelected = FALSE) {
tabs <- dropNulls(list2(...))
res <- findAndMarkSelectedTab(tabs, selected, foundSelected)
tabs <- res$tabs
foundSelected <- res$foundSelected
# add input class if we have an id
if (!is.null(id)) ulClass <- paste(ulClass, "shiny-tab-input")
if (anyNamed(tabs)) {
nms <- names(tabs)
nms <- nms[nzchar(nms)]
stop("Tabs should all be unnamed arguments, but some are named: ",
paste(nms, collapse = ", "))
}
tabsetId <- p_randomInt(1000, 10000)
tabs <- lapply(seq_len(length(tabs)), buildTabItem,
tabsetId = tabsetId, foundSelected = foundSelected,
tabs = tabs, textFilter = textFilter)
tabNavList <- tags$ul(class = ulClass, id = id,
`data-tabsetid` = tabsetId, !!!lapply(tabs, "[[", "liTag"))
tabContent <- tags$div(class = "tab-content",
`data-tabsetid` = tabsetId, !!!lapply(tabs, "[[", "divTag"))
list(navList = tabNavList, content = tabContent)
}
# Builds tabPanel/navbarMenu items (this function used to be
# declared inside the buildTabset() function and it's been
# refactored for clarity and reusability). Called internally
# by buildTabset.
buildTabItem <- function(index, tabsetId, foundSelected, tabs = NULL,
divTag = NULL, textFilter = NULL) {
divTag <- divTag %||% tabs[[index]]
# Handles navlistPanel() headers and dropdown dividers
if (is.character(divTag) && !is.null(textFilter)) {
return(list(liTag = textFilter(divTag), divTag = NULL))
}
if (isNavbarMenu(divTag)) {
# tabPanelMenu item: build the child tabset
tabset <- buildTabset(
!!!divTag$tabs, ulClass = "dropdown-menu",
textFilter = navbarMenuTextFilter,
foundSelected = foundSelected
)
return(buildDropdown(divTag, tabset))
}
if (isTabPanel(divTag)) {
return(buildNavItem(divTag, tabsetId, index))
}
# The behavior is undefined at this point, so construct a condition message
msg <- paste0(
"Expected a collection `tabPanel()`s",
if (is.null(textFilter)) " and `navbarMenu()`.",
if (!is.null(textFilter)) ", `navbarMenu()`, and/or character strings.",
" Consider using `header` or `footer` if you wish to place content above (or below) every panel's contents"
)
# Luckily this case has never worked, so it's safe to throw here
# https://github.com/rstudio/shiny/issues/3313
if (!inherits(divTag, "shiny.tag")) {
stop(msg, call. = FALSE)
}
# Unfortunately, this 'off-label' use case creates an 'empty' nav and includes
# the divTag content on every tab. There shouldn't be any reason to be relying on
# this behavior since we now have pre/post arguments, so throw a warning, but still
# support the use case since we don't make breaking changes
warning(msg, call. = FALSE)
return(buildNavItem(divTag, tabsetId, index))
}
buildNavItem <- function(divTag, tabsetId, index) {
id <- paste("tab", tabsetId, index, sep = "-")
# Get title attribute directory (not via tagGetAttribute()) so that contents
# don't get passed to as.character().
# https://github.com/rstudio/shiny/issues/3352
title <- divTag$attribs[["title"]]
value <- divTag$attribs[["data-value"]]
active <- isTabSelected(divTag)
divTag <- tagAppendAttributes(divTag, class = if (active) "active")
divTag$attribs$id <- id
divTag$attribs$title <- NULL
list(
divTag = divTag,
liTag = tagAddRenderHook(
liTag(id, title, value, attr(divTag, "_shiny_icon")),
function(x) {
if (isTRUE(getCurrentThemeVersion() >= 4)) {
tagQuery(x)$
addClass("nav-item")$
find("a")$
addClass(c("nav-link", if (active) "active"))$
allTags()
} else {
tagAppendAttributes(x, class = if (active) "active")
}
}
)
)
}
liTag <- function(id, title, value, icon) {
tags$li(
tags$a(
href = paste0("#", id),
`data-toggle` = "tab",
`data-value` = value,
icon, title
)
)
}
buildDropdown <- function(divTag, tabset) {
navList <- tagAddRenderHook(
tabset$navList,
function(x) {
if (isTRUE(getCurrentThemeVersion() >= 4)) {
tagQuery(x)$
find(".nav-item")$
removeClass("nav-item")$
find(".nav-link")$
removeClass("nav-link")$
addClass("dropdown-item")$
allTags()
} else {
x
}
}
)
active <- containsSelectedTab(divTag$tabs)
dropdown <- tags$li(
class = "dropdown",
tags$a(
href = "#",
class = "dropdown-toggle",
`data-toggle` = "dropdown",
`data-value` = divTag$menuName,
divTag$icon,
divTag$title,
tags$b(class = "caret")
),
navList,
.renderHook = function(x) {
if (isTRUE(getCurrentThemeVersion() >= 4)) {
tagQuery(x)$
addClass("nav-item")$
find(".dropdown-toggle")$
addClass("nav-link")$
allTags()
} else {
x
}
}
)
list(
divTag = tabset$content$children,
liTag = dropdown
)
}
#' Create a text output element
#'
#' Render a reactive output variable as text within an application page.

View File

@@ -133,13 +133,11 @@ dateInput <- function(inputId, label, value = NULL, min = NULL, max = NULL,
}
datePickerVersion <- "1.9.0"
datePickerDependency <- function(theme) {
list(
htmlDependency(
name = "bootstrap-datepicker-js",
version = datePickerVersion,
version = version_bs_date_picker,
src = c(href = "shared/datepicker"),
script = if (getOption("shiny.minified", TRUE)) "js/bootstrap-datepicker.min.js"
else "js/bootstrap-datepicker.js",
@@ -158,7 +156,7 @@ datePickerCSS <- function(theme) {
if (!is_bs_theme(theme)) {
return(htmlDependency(
name = "bootstrap-datepicker-css",
version = datePickerVersion,
version = version_bs_date_picker,
src = c(href = "shared/datepicker"),
stylesheet = "css/bootstrap-datepicker3.min.css"
))
@@ -170,7 +168,7 @@ datePickerCSS <- function(theme) {
input = sass::sass_file(scss_file),
theme = theme,
name = "bootstrap-datepicker",
version = datePickerVersion,
version = version_bs_date_picker,
cache_key_extra = shinyPackageVersion()
)
}

View File

@@ -243,19 +243,14 @@ selectizeDependency <- function() {
}
selectizeDependencyFunc <- function(theme) {
selectizeVersion <- "0.12.4"
if (!is_bs_theme(theme)) {
return(selectizeStaticDependency(selectizeVersion))
return(selectizeStaticDependency(version_selectize))
}
selectizeDir <- system.file(package = "shiny", "www/shared/selectize/")
bs_version <- bslib::theme_version(theme)
stylesheet <- file.path(
selectizeDir, "scss",
if ("3" %in% bslib::theme_version(theme)) {
"selectize.bootstrap3.scss"
} else {
"selectize.bootstrap4.scss"
}
selectizeDir, "scss", paste0("selectize.bootstrap", bs_version, ".scss")
)
# It'd be cleaner to ship the JS in a separate, href-based,
# HTML dependency (which we currently do for other themable widgets),
@@ -271,7 +266,7 @@ selectizeDependencyFunc <- function(theme) {
input = sass::sass_file(stylesheet),
theme = theme,
name = "selectize",
version = selectizeVersion,
version = version_selectize,
cache_key_extra = shinyPackageVersion(),
.dep_args = list(script = script)
)

View File

@@ -201,18 +201,16 @@ sliderInput <- function(inputId, label, min, max, value, step = NULL,
}
ionRangeSliderVersion <- "2.3.1"
ionRangeSliderDependency <- function() {
list(
# ion.rangeSlider also needs normalize.css, which is already included in Bootstrap.
htmlDependency(
"ionrangeslider-javascript", ionRangeSliderVersion,
"ionrangeslider-javascript", version_ion_range_slider,
src = c(href = "shared/ionrangeslider"),
script = "js/ion.rangeSlider.min.js"
),
htmlDependency(
"strftime", "0.9.2",
"strftime", version_strftime,
src = c(href = "shared/strftime"),
script = "strftime-min.js"
),
@@ -224,35 +222,22 @@ ionRangeSliderDependencyCSS <- function(theme) {
if (!is_bs_theme(theme)) {
return(htmlDependency(
"ionrangeslider-css",
ionRangeSliderVersion,
version_ion_range_slider,
src = c(href = "shared/ionrangeslider"),
stylesheet = "css/ion.rangeSlider.css"
))
}
# Remap some variable names for ionRangeSlider's scss
sass_input <- list(
list(
# The bootswatch materia theme sets $input-bg: transparent;
# which is an issue for the slider's handle(s) (#3130)
bg = "if(alpha($input-bg)==0, $body-bg, $input-bg)",
fg = sprintf(
"if(alpha($input-color)==0, $%s, $input-color)",
if ("3" %in% bslib::theme_version(theme)) "text-color" else "body-color"
),
accent = "$component-active-bg",
`font-family` = "$font-family-base"
),
sass::sass_file(
system.file(package = "shiny", "www/shared/ionrangeslider/scss/shiny.scss")
)
)
bslib::bs_dependency(
input = sass_input,
input = list(
list(accent = "$component-active-bg"),
sass::sass_file(
system.file(package = "shiny", "www/shared/ionrangeslider/scss/shiny.scss")
)
),
theme = theme,
name = "ionRangeSlider",
version = ionRangeSliderVersion,
version = version_ion_range_slider,
cache_key_extra = shinyPackageVersion()
)
}

View File

@@ -112,35 +112,13 @@
#'
#' }
#' @export
insertTab <- function(inputId, tab, target,
position = c("before", "after"), select = FALSE,
insertTab <- function(inputId, tab, target = NULL,
position = c("after", "before"), select = FALSE,
session = getDefaultReactiveDomain()) {
force(target)
force(select)
position <- match.arg(position)
inputId <- session$ns(inputId)
# Barbara -- August 2017
# Note: until now, the number of tabs in a tabsetPanel (or navbarPage
# or navlistPanel) was always fixed. So, an easy way to give an id to
# a tab was simply incrementing a counter. (Just like it was easy to
# give a random 4-digit number to identify the tabsetPanel). Since we
# can only know this in the client side, we'll just pass `id` and
# `tsid` (TabSetID) as dummy values that will be fixed in the JS code.
item <- buildTabItem("id", "tsid", TRUE, divTag = tab,
textFilter = if (is.character(tab)) navbarMenuTextFilter else NULL)
callback <- function() {
session$sendInsertTab(
inputId = inputId,
liTag = processDeps(item$liTag, session),
divTag = processDeps(item$divTag, session),
menuName = NULL,
target = target,
position = position,
select = select)
}
session$onFlush(callback, once = TRUE)
bslib::nav_insert(
inputId, tab, target,
match.arg(position), select, session
)
}
#' @param menuName This argument should only be used when you want to
@@ -159,63 +137,21 @@ insertTab <- function(inputId, tab, target,
#' @export
prependTab <- function(inputId, tab, select = FALSE, menuName = NULL,
session = getDefaultReactiveDomain()) {
force(select)
force(menuName)
inputId <- session$ns(inputId)
item <- buildTabItem("id", "tsid", TRUE, divTag = tab,
textFilter = if (is.character(tab)) navbarMenuTextFilter else NULL)
callback <- function() {
session$sendInsertTab(
inputId = inputId,
liTag = processDeps(item$liTag, session),
divTag = processDeps(item$divTag, session),
menuName = menuName,
target = NULL,
position = "after",
select = select)
}
session$onFlush(callback, once = TRUE)
bslib::nav_prepend(inputId, tab, menu_title = menuName, select = select, session = session)
}
#' @rdname insertTab
#' @export
appendTab <- function(inputId, tab, select = FALSE, menuName = NULL,
session = getDefaultReactiveDomain()) {
force(select)
force(menuName)
inputId <- session$ns(inputId)
item <- buildTabItem("id", "tsid", TRUE, divTag = tab,
textFilter = if (is.character(tab)) navbarMenuTextFilter else NULL)
callback <- function() {
session$sendInsertTab(
inputId = inputId,
liTag = processDeps(item$liTag, session),
divTag = processDeps(item$divTag, session),
menuName = menuName,
target = NULL,
position = "before",
select = select)
}
session$onFlush(callback, once = TRUE)
bslib::nav_append(inputId, tab, menu_title = menuName, select = select, session = session)
}
#' @rdname insertTab
#' @export
removeTab <- function(inputId, target,
session = getDefaultReactiveDomain()) {
force(target)
inputId <- session$ns(inputId)
callback <- function() {
session$sendRemoveTab(
inputId = inputId,
target = target)
}
session$onFlush(callback, once = TRUE)
bslib::nav_remove(inputId, target, session)
}

View File

@@ -151,18 +151,25 @@ removeModal <- function(session = getDefaultReactiveDomain()) {
#' }
#' @export
modalDialog <- function(..., title = NULL, footer = modalButton("Dismiss"),
size = c("m", "s", "l"), easyClose = FALSE, fade = TRUE) {
size = c("m", "s", "l", "xl"), easyClose = FALSE, fade = TRUE) {
size <- match.arg(size)
cls <- if (fade) "modal fade" else "modal"
div(id = "shiny-modal", class = cls, tabindex = "-1",
`data-backdrop` = if (!easyClose) "static",
`data-keyboard` = if (!easyClose) "false",
backdrop <- if (!easyClose) "static"
keyboard <- if (!easyClose) "false"
div(
id = "shiny-modal",
class = "modal",
class = if (fade) "fade",
tabindex = "-1",
`data-backdrop` = backdrop,
`data-bs-backdrop` = backdrop,
`data-keyboard` = keyboard,
`data-bs-keyboard` = keyboard,
div(
class = "modal-dialog",
class = switch(size, s = "modal-sm", m = NULL, l = "modal-lg"),
class = switch(size, s = "modal-sm", m = NULL, l = "modal-lg", xl = "modal-xl"),
div(class = "modal-content",
if (!is.null(title)) div(class = "modal-header",
tags$h4(class = "modal-title", title)
@@ -171,14 +178,26 @@ modalDialog <- function(..., title = NULL, footer = modalButton("Dismiss"),
if (!is.null(footer)) div(class = "modal-footer", footer)
)
),
tags$script("$('#shiny-modal').modal().focus();")
# jQuery plugin doesn't work in Bootstrap 5, but vanilla JS doesn't work in Bootstrap 4 :sob:
tags$script(HTML(
"if (window.bootstrap && !window.bootstrap.Modal.VERSION.match(/^4\\./)) {
var modal = new bootstrap.Modal(document.getElementById('shiny-modal'));
modal.show();
} else {
$('#shiny-modal').modal().focus();
}"
))
)
}
#' @export
#' @rdname modalDialog
modalButton <- function(label, icon = NULL) {
tags$button(type = "button", class = "btn btn-default",
`data-dismiss` = "modal", validateIcon(icon), label
tags$button(
type = "button",
class = "btn btn-default",
`data-dismiss` = "modal",
`data-bs-dismiss` = "modal",
validateIcon(icon), label
)
}

View File

@@ -36,12 +36,14 @@
#' @param res Resolution of resulting plot, in pixels per inch. This value is
#' passed to [grDevices::png()]. Note that this affects the resolution of PNG
#' rendering in R; it won't change the actual ppi of the browser.
#' @param alt Alternate text for the HTML `<img>` tag
#' if it cannot be displayed or viewed (i.e., the user uses a screen reader).
#' In addition to a character string, the value may be a reactive expression
#' (or a function referencing reactive values) that returns a character string.
#' NULL or "" is not recommended because those should be limited to decorative images
#' (the default is "Plot object").
#' @param alt Alternate text for the HTML `<img>` tag if it cannot be displayed
#' or viewed (i.e., the user uses a screen reader). In addition to a character
#' string, the value may be a reactive expression (or a function referencing
#' reactive values) that returns a character string. If the value is `NA` (the
#' default), then `ggplot2::get_alt_text()` is used to extract alt text from
#' ggplot objects; for other plots, `NA` results in alt text of "Plot object".
#' `NULL` or `""` is not recommended because those should be limited to
#' decorative images.
#' @param ... Arguments to be passed through to [grDevices::png()].
#' These can be used to set the width, height, background color, etc.
#' @param env The environment in which to evaluate `expr`.
@@ -58,7 +60,7 @@
#' interactive R Markdown document.
#' @export
renderPlot <- function(expr, width = 'auto', height = 'auto', res = 72, ...,
alt = "Plot object",
alt = NA,
env = parent.frame(), quoted = FALSE,
execOnResize = FALSE, outputArgs = list()
) {
@@ -212,7 +214,7 @@ resizeSavedPlot <- function(name, session, result, width, height, alt, pixelrati
src = session$fileUrl(name, outfile, contentType = "image/png"),
width = width,
height = height,
alt = alt,
alt = result$alt,
coordmap = coordmap,
error = attr(coordmap, "error", exact = TRUE)
)
@@ -288,6 +290,7 @@ drawPlot <- function(name, session, func, width, height, alt, pixelratio, res, .
recordedPlot = grDevices::recordPlot(),
coordmap = getCoordmap(value, width*pixelratio, height*pixelratio, res*pixelratio),
pixelratio = pixelratio,
alt = if (anyNA(alt)) getAltText(value) else alt,
res = res
)
}
@@ -302,10 +305,10 @@ drawPlot <- function(name, session, func, width, height, alt, pixelratio, res, .
),
function(result) {
result$img <- dropNulls(list(
src = session$fileUrl(name, outfile, contentType='image/png'),
src = session$fileUrl(name, outfile, contentType = 'image/png'),
width = width,
height = height,
alt = alt,
alt = result$alt,
coordmap = result$coordmap,
# Get coordmap error message if present
error = attr(result$coordmap, "error", exact = TRUE)
@@ -339,6 +342,24 @@ custom_print.ggplot <- function(x) {
), class = "ggplot_build_gtable")
}
# Infer alt text description from renderPlot() value
# (currently just ggplot2 is supported)
getAltText <- function(x, default = "Plot object") {
# Since, inside renderPlot(), custom_print.ggplot()
# overrides print.ggplot, this class indicates a ggplot()
if (!inherits(x, "ggplot_build_gtable")) {
return(default)
}
# ggplot2::get_alt_text() was added in v3.3.4
# https://github.com/tidyverse/ggplot2/pull/4482
get_alt <- getNamespace("ggplot2")$get_alt_text
if (!is.function(get_alt)) {
return(default)
}
alt <- paste(get_alt(x$build), collapse = " ")
if (nzchar(alt)) alt else default
}
# The coordmap extraction functions below return something like the examples
# below. For base graphics:
# plot(mtcars$wt, mtcars$mpg)

View File

@@ -22,7 +22,10 @@
#' @param port The TCP port that the application should listen on. If the
#' `port` is not specified, and the `shiny.port` option is set (with
#' `options(shiny.port = XX)`), then that port will be used. Otherwise,
#' use a random port.
#' use a random port between 3000:8000, excluding ports that are blocked
#' by Google Chrome for being considered unsafe: 3659, 4045, 5060,
#' 5061, 6000, 6566, 6665:6669 and 6697. Up to twenty random
#' ports will be tried.
#' @param launch.browser If true, the system's default web browser will be
#' launched automatically after the app is started. Defaults to true in
#' interactive sessions only. This value of this parameter can also be a
@@ -301,7 +304,8 @@ runApp <- function(appDir=getwd(),
# Reject ports in this range that are considered unsafe by Chrome
# http://superuser.com/questions/188058/which-ports-are-considered-unsafe-on-chrome
# https://github.com/rstudio/shiny/issues/1784
if (!port %in% c(3659, 4045, 6000, 6665:6669, 6697)) {
# https://chromium.googlesource.com/chromium/src.git/+/refs/heads/main/net/base/port_util.cc
if (!port %in% c(3659, 4045, 5060, 5061, 6000, 6566, 6665:6669, 6697)) {
break
}
}

View File

@@ -83,7 +83,7 @@ navTabsHelper <- function(files, prefix = "") {
with(tags,
li(class=if (tolower(file) %in% c("app.r", "server.r")) "active" else "",
a(href=paste("#", gsub(".", "_", file, fixed=TRUE), "_code", sep=""),
"data-toggle"="tab", paste0(prefix, file)))
"data-toggle"="tab", "data-bs-toggle"="tab", paste0(prefix, file)))
)
})
}
@@ -92,7 +92,7 @@ navTabsDropdown <- function(files) {
if (length(files) > 0) {
with(tags,
li(role="presentation", class="dropdown",
a(class="dropdown-toggle", `data-toggle`="dropdown", href="#",
a(class="dropdown-toggle", `data-toggle`="dropdown", `data-bs-toggle`="dropdown", href="#",
role="button", `aria-haspopup`="true", `aria-expanded`="false",
"www", span(class="caret")
),

View File

@@ -1159,7 +1159,7 @@ reactiveStop <- function(message = "", class = NULL) {
#'
#' ui <- fluidPage(
#' checkboxGroupInput('in1', 'Check some letters', choices = head(LETTERS)),
#' selectizeInput('in2', 'Select a state', choices = state.name),
#' selectizeInput('in2', 'Select a state', choices = c("", state.name)),
#' plotOutput('plot')
#' )
#'

View File

@@ -0,0 +1,2 @@
# Generated by tools/updateBootstrapDatepicker.R; do not edit by hand
version_bs_date_picker <- "1.9.0"

View File

@@ -0,0 +1,2 @@
# Generated by tools/updateIonRangeSlider.R; do not edit by hand
version_ion_range_slider <- "2.3.1"

2
R/version_selectize.R Normal file
View File

@@ -0,0 +1,2 @@
# Generated by tools/updateSelectize.R; do not edit by hand
version_selectize <- "0.12.4"

2
R/version_strftime.R Normal file
View File

@@ -0,0 +1,2 @@
# Generated by tools/updateStrftime.R; do not edit by hand
version_strftime <- "0.9.2"

View File

@@ -58,3 +58,7 @@ We welcome contributions to the **shiny** package. Please see our [CONTRIBUTING.
## License
The shiny package as a whole is licensed under the GPLv3. See the [LICENSE](LICENSE) file for more details.
## R version support
Shiny is supported on the latest release version of R, as well as the previous four minor release versions of R. For example, if the latest release R version is 4.1, then that version is supported, as well as 4.0, 3.6, 3.5, and 3.4.

View File

@@ -5,7 +5,7 @@
"@babel/preset-env",
{
"useBuiltIns": "usage",
"corejs": "3.9"
"corejs": "3.12"
}
]
],

File diff suppressed because one or more lines are too long

View File

@@ -13,8 +13,13 @@
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
font-size: 12px;
font-family: Arial, sans-serif;
/* https://github.com/rstudio/shiny/issues/3443 */
/* https://css-tricks.com/inheriting-box-sizing-probably-slightly-better-best-practice/ */
box-sizing: border-box;
}
.irs *, .irs *:before, .irs *:after {
box-sizing: inherit;
}
.irs-line {
@@ -92,7 +97,6 @@
left: 0;
width: 1px;
height: 8px;
background: #000;
}
.irs-grid-pol.small {
@@ -108,7 +112,6 @@
font-size: 9px;
line-height: 9px;
padding: 0 3px;
color: #000;
}
.irs-disable-mask {
@@ -153,7 +156,7 @@
}
.irs {
font-family: Arial, sans-serif;
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
}
.irs--shiny {
@@ -167,7 +170,7 @@
.irs--shiny .irs-line {
top: 25px;
height: 8px;
background: linear-gradient(to bottom, #dedede -50%, white 150%);
background: linear-gradient(to bottom, #dedede -50%, #fff 150%);
background-color: #ededed;
border: 1px solid #cccccc;
border-radius: 8px;
@@ -207,14 +210,13 @@
}
.irs--shiny .irs-handle.state_hover, .irs--shiny .irs-handle:hover {
background: white;
background: #fff;
}
.irs--shiny .irs-min,
.irs--shiny .irs-max {
top: 0;
padding: 1px 3px;
color: #333333;
text-shadow: none;
background-color: rgba(0, 0, 0, 0.1);
border-radius: 3px;
@@ -250,12 +252,11 @@
}
.irs--shiny .irs-grid-pol {
background-color: black;
background-color: #000;
}
.irs--shiny .irs-grid-text {
bottom: 5px;
color: #1a1a1a;
}
.irs--shiny .irs-grid-pol.small {

View File

@@ -4,8 +4,12 @@
@include pos-r();
-webkit-touch-callout: none;
@include no-click();
font-size: 12px;
font-family: Arial, sans-serif;
/* https://github.com/rstudio/shiny/issues/3443 */
/* https://css-tricks.com/inheriting-box-sizing-probably-slightly-better-best-practice/ */
box-sizing: border-box;
*, *:before, *:after {
box-sizing: inherit;
}
&-line {
@include pos-r();
@@ -83,7 +87,6 @@
left: 0;
width: 1px;
height: 8px;
background: #000;
&.small {
height: 4px;
@@ -99,7 +102,6 @@
font-size: 9px;
line-height: 9px;
padding: 0 3px;
color: #000;
}
}

View File

@@ -19,8 +19,7 @@
////////////////////////////////////////////////////////////////////////////
// Re-define font-family on .irs to make it configurable
$font-family: Arial, sans-serif !default;
$font-family: $font-family-base !default;
.irs {
font-family: $font-family;
}
@@ -36,8 +35,8 @@ $font-family: Arial, sans-serif !default;
$custom_radius: 3px !default;
// "High-level" coloring
$bg: white !default;
$fg: black !default;
$bg: $body-bg !default;
$fg: color-contrast($body-bg) !default;
$accent: #428bca !default;
// "Low-level" coloring, borders, and fonts
@@ -52,7 +51,7 @@ $font-family: Arial, sans-serif !default;
$handle_border: 1px solid mix($bg, $fg, 67%) !default;
$handle_box_shadow: 1px 1px 3px rgba($bg, 0.3) !default;
$minmax_text_color: mix($bg, $fg, 20%) !default;
$minmax_text_color: null !default;
$minmax_bg_color: rgba($fg, 0.1) !default;
$minmax_font_size: 10px !default;
$minmax_line_height: 1.333 !default;
@@ -64,7 +63,7 @@ $font-family: Arial, sans-serif !default;
$grid_major_color: $fg !default;
$grid_minor_color: mix($bg, $fg, 60%) !default;
$grid_text_color: mix($bg, $fg, 10%) !default;
$grid_text_color: null !default;
height: 40px;

View File

@@ -13,7 +13,7 @@ $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: theme-color("danger") !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;

View File

@@ -0,0 +1,3 @@
$input-line-height-sm: $form-select-line-height !default;
@import 'selectize.bootstrap4';
.selectize-control{padding:0;}

View File

@@ -1,17 +1,3 @@
(function() {
var protocol = 'ws:';
if (window.location.protocol === 'https:')
protocol = 'wss:';
var defaultPath = window.location.pathname;
if (!/\/$/.test(defaultPath))
defaultPath += '/';
defaultPath += 'autoreload/';
var ws = new WebSocket(protocol + '//' + window.location.host + defaultPath);
ws.onmessage = function(event) {
if (event.data === "autoreload") {
window.location.reload()
}
}
})();
/*! shiny 1.6.0.9021 | (c) 2012-2021 RStudio, PBC. | License: GPL-3 | file LICENSE */
(function(){var t="ws:";window.location.protocol==="https:"&&(t="wss:");var o=window.location.pathname;/\/$/.test(o)||(o+="/");o+="autoreload/";var n=new WebSocket(t+"//"+window.location.host+o);n.onmessage=function(a){a.data==="autoreload"&&window.location.reload()};})();
//# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsiLi4vLi4vLi4vc3JjdHMvZXh0cmFzL3NoaW55LWF1dG9yZWxvYWQudHMiXSwKICAic291cmNlc0NvbnRlbnQiOiBbIi8qIGVzbGludC1kaXNhYmxlIHVuaWNvcm4vZmlsZW5hbWUtY2FzZSAqL1xudmFyIHByb3RvY29sID0gXCJ3czpcIjtcbmlmICh3aW5kb3cubG9jYXRpb24ucHJvdG9jb2wgPT09IFwiaHR0cHM6XCIpIHByb3RvY29sID0gXCJ3c3M6XCI7XG52YXIgZGVmYXVsdFBhdGggPSB3aW5kb3cubG9jYXRpb24ucGF0aG5hbWU7XG5pZiAoIS9cXC8kLy50ZXN0KGRlZmF1bHRQYXRoKSkgZGVmYXVsdFBhdGggKz0gXCIvXCI7XG5kZWZhdWx0UGF0aCArPSBcImF1dG9yZWxvYWQvXCI7XG52YXIgd3MgPSBuZXcgV2ViU29ja2V0KHByb3RvY29sICsgXCIvL1wiICsgd2luZG93LmxvY2F0aW9uLmhvc3QgKyBkZWZhdWx0UGF0aCk7XG5cbndzLm9ubWVzc2FnZSA9IGZ1bmN0aW9uIChldmVudCkge1xuICBpZiAoZXZlbnQuZGF0YSA9PT0gXCJhdXRvcmVsb2FkXCIpIHtcbiAgICB3aW5kb3cubG9jYXRpb24ucmVsb2FkKCk7XG4gIH1cbn07XG5cbmV4cG9ydCB7fTsiXSwKICAibWFwcGluZ3MiOiAiO1lBQ0EsR0FBSSxHQUFXLE1BQ2YsQUFBSSxPQUFPLFNBQVMsV0FBYSxVQUFVLEdBQVcsUUFDdEQsR0FBSSxHQUFjLE9BQU8sU0FBUyxTQUNsQyxBQUFLLE1BQU0sS0FBSyxJQUFjLElBQWUsS0FDN0MsR0FBZSxjQUNmLEdBQUksR0FBSyxHQUFJLFdBQVUsRUFBVyxLQUFPLE9BQU8sU0FBUyxLQUFPLEdBRWhFLEVBQUcsVUFBWSxTQUFVLEVBQU8sQ0FDOUIsQUFBSSxFQUFNLE9BQVMsY0FDakIsT0FBTyxTQUFTIiwKICAibmFtZXMiOiBbXQp9Cg==

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1,87 +1,2 @@
#showcase-well {
border-radius: 0;
-webkit-border-radius: 0;
-moz-border-radius: 0;
}
.shiny-code {
background-color: white;
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: normal;
}
.showcase-code-link {
text-align: right;
padding: 15px;
}
#showcase-app-container {
vertical-align: top;
}
#showcase-code-tabs pre {
border: none;
line-height: 1em;
}
#showcase-code-tabs .nav,
#showcase-code-tabs ul {
margin-bottom: 0px;
}
#showcase-app-code {
width: 100%;
}
#showcase-code-tabs {
margin-right: 15px;
}
#showcase-code-tabs .tab-content {
border-style: solid;
border-color: #e5e5e5;
border-width: 0px 1px 1px 1px;
overflow:auto;
-webkit-border-bottom-right-radius: 4px;
-webkit-border-bottom-left-radius: 4px;
-moz-border-radius-bottomright: 4px;
-moz-border-radius-bottomleft: 4px;
border-bottom-right-radius: 4px;
border-bottom-left-radius: 4px;
}
#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: white;
}
/*! shiny 1.6.0.9021 | (c) 2012-2021 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:normal}.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{margin-bottom:0}#showcase-code-tabs ul{margin-bottom:0}#showcase-code-tabs .tab-content{border-style:solid;border-color:#e5e5e5;border-width:0px 1px 1px 1px;overflow:auto;border-bottom-right-radius:4px;border-bottom-left-radius:4px}#showcase-app-code{width:100%}#showcase-code-position-toggle{float:right}#showcase-sxs-code{padding-top:20px;vertical-align:top}.showcase-code-license{display:block;text-align:right}#showcase-code-content pre{background-color:#fff}

File diff suppressed because one or more lines are too long

View File

@@ -1,8 +1,3 @@
// Listen for messages from parent frame. This file is only added when the
// shiny.testmode option is TRUE.
window.addEventListener("message", function(e) {
var message = e.data;
if (message.code)
eval(message.code);
});
/*! shiny 1.6.0.9021 | (c) 2012-2021 RStudio, PBC. | License: GPL-3 | file LICENSE */
(function(){var a=eval;window.addEventListener("message",function(i){var e=i.data;e.code&&a(e.code)});})();
//# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsiLi4vLi4vLi4vc3JjdHMvc3JjL3V0aWxzL2V2YWwudHMiLCAiLi4vLi4vLi4vc3JjdHMvZXh0cmFzL3NoaW55LXRlc3Rtb2RlLnRzIl0sCiAgInNvdXJjZXNDb250ZW50IjogWyIvL2VzYnVpbGQuZ2l0aHViLmlvL2NvbnRlbnQtdHlwZXMvI2RpcmVjdC1ldmFsXG4vL3RsL2RyO1xuLy8gKiBEaXJlY3QgdXNhZ2Ugb2YgYGV2YWwoXCJ4XCIpYCBpcyBiYWQgd2l0aCBidW5kbGVkIGNvZGUuXG4vLyAqIEluc3RlYWQsIHVzZSBpbmRpcmVjdCBjYWxscyB0byBgZXZhbGAgc3VjaCBhcyBgaW5kaXJlY3RFdmFsKFwieFwiKWBcbi8vICAgKiBFdmVuIGp1c3QgcmVuYW1pbmcgdGhlIGZ1bmN0aW9uIHdvcmtzIHdlbGwgZW5vdWdoLlxuLy8gPiBUaGlzIGlzIGtub3duIGFzIFwiaW5kaXJlY3QgZXZhbFwiIGJlY2F1c2UgZXZhbCBpcyBub3QgYmVpbmcgY2FsbGVkIGRpcmVjdGx5LCBhbmQgc28gZG9lcyBub3QgdHJpZ2dlciB0aGUgZ3JhbW1hdGljYWwgc3BlY2lhbCBjYXNlIGZvciBkaXJlY3QgZXZhbCBpbiB0aGUgSmF2YVNjcmlwdCBWTS4gWW91IGNhbiBjYWxsIGluZGlyZWN0IGV2YWwgdXNpbmcgYW55IHN5bnRheCBhdCBhbGwgZXhjZXB0IGZvciBhbiBleHByZXNzaW9uIG9mIHRoZSBleGFjdCBmb3JtIGV2YWwoJ3gnKS4gRm9yIGV4YW1wbGUsIHZhciBldmFsMiA9IGV2YWw7IGV2YWwyKCd4JykgYW5kIFtldmFsXVswXSgneCcpIGFuZCB3aW5kb3cuZXZhbCgneCcpIGFyZSBhbGwgaW5kaXJlY3QgZXZhbCBjYWxscy5cbi8vID4gV2hlbiB5b3UgdXNlIGluZGlyZWN0IGV2YWwsIHRoZSBjb2RlIGlzIGV2YWx1YXRlZCBpbiB0aGUgZ2xvYmFsIHNjb3BlIGluc3RlYWQgb2YgaW4gdGhlIGlubGluZSBzY29wZSBvZiB0aGUgY2FsbGVyLlxudmFyIGluZGlyZWN0RXZhbCA9IGV2YWw7XG5leHBvcnQgeyBpbmRpcmVjdEV2YWwgfTsiLCAiLyogZXNsaW50LWRpc2FibGUgdW5pY29ybi9maWxlbmFtZS1jYXNlICovXG5pbXBvcnQgeyBpbmRpcmVjdEV2YWwgfSBmcm9tIFwiLi4vc3JjL3V0aWxzL2V2YWxcIjsgLy8gTGlzdGVuIGZvciBtZXNzYWdlcyBmcm9tIHBhcmVudCBmcmFtZS4gVGhpcyBmaWxlIGlzIG9ubHkgYWRkZWQgd2hlbiB0aGVcbi8vIHNoaW55LnRlc3Rtb2RlIG9wdGlvbiBpcyBUUlVFLlxuXG53aW5kb3cuYWRkRXZlbnRMaXN0ZW5lcihcIm1lc3NhZ2VcIiwgZnVuY3Rpb24gKGUpIHtcbiAgdmFyIG1lc3NhZ2UgPSBlLmRhdGE7XG4gIGlmIChtZXNzYWdlLmNvZGUpIGluZGlyZWN0RXZhbChtZXNzYWdlLmNvZGUpO1xufSk7Il0sCiAgIm1hcHBpbmdzIjogIjtZQU9BLEdBQUksR0FBZSxLQ0huQixPQUFPLGlCQUFpQixVQUFXLFNBQVUsRUFBRyxDQUM5QyxHQUFJLEdBQVUsRUFBRSxLQUNoQixBQUFJLEVBQVEsTUFBTSxFQUFhLEVBQVEiLAogICJuYW1lcyI6IFtdCn0K

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -92,6 +92,8 @@ pre.shiny-text-output {
bottom: 0;
left: 0;
right: 0;
max-width: 100%; /* Make sure width isn't restricted by styles on selectors like "body > div" */
max-height: 100%;
background-color: $shiny-disconnected-bg;
opacity: 0.5;
overflow: hidden;

View File

@@ -10,8 +10,8 @@
insertTab(
inputId,
tab,
target,
position = c("before", "after"),
target = NULL,
position = c("after", "before"),
select = FALSE,
session = getDefaultReactiveDomain()
)

View File

@@ -9,7 +9,7 @@ modalDialog(
...,
title = NULL,
footer = modalButton("Dismiss"),
size = c("m", "s", "l"),
size = c("m", "s", "l", "xl"),
easyClose = FALSE,
fade = TRUE
)

View File

@@ -17,7 +17,7 @@ navbarPage(
collapsible = FALSE,
fluid = TRUE,
theme = NULL,
windowTitle = title,
windowTitle = NA,
lang = NULL
)
@@ -73,8 +73,10 @@ build of Bootstrap 3 with a customized version of Bootstrap 3 or higher.
(normally a css file within the www directory, e.g. \code{www/bootstrap.css}).
}}
\item{windowTitle}{The title that should be displayed by the browser window.
Useful if \code{title} is not a string.}
\item{windowTitle}{the browser window title (as a character string). The
default value, \code{NA}, means to use any character strings that appear in
\code{title} (if none are found, the host URL of the page is displayed by
default).}
\item{lang}{ISO 639-1 language code for the HTML page, such as "en" or "ko".
This will be used as the lang in the \code{<html>} tag, as in \code{<html lang="en">}.

View File

@@ -40,12 +40,14 @@ information on the default sizing policy.}
\item{...}{Arguments to be passed through to \code{\link[grDevices:png]{grDevices::png()}}.
These can be used to set the width, height, background color, etc.}
\item{alt}{Alternate text for the HTML \verb{<img>} tag
if it cannot be displayed or viewed (i.e., the user uses a screen reader).
In addition to a character string, the value may be a reactive expression
(or a function referencing reactive values) that returns a character string.
NULL or "" is not recommended because those should be limited to decorative images
(the default is "Plot object").}
\item{alt}{Alternate text for the HTML \verb{<img>} tag if it cannot be displayed
or viewed (i.e., the user uses a screen reader). In addition to a character
string, the value may be a reactive expression (or a function referencing
reactive values) that returns a character string. If the value is \code{NA} (the
default), then \code{ggplot2::get_alt_text()} is used to extract alt text from
ggplot objects; for other plots, \code{NA} results in alt text of "Plot object".
\code{NULL} or \code{""} is not recommended because those should be limited to
decorative images.}
\item{outputArgs}{A list of arguments to be passed through to the implicit
call to \code{\link[=plotOutput]{plotOutput()}} when \code{renderPlot} is used in an

View File

@@ -10,7 +10,7 @@ renderPlot(
height = "auto",
res = 72,
...,
alt = "Plot object",
alt = NA,
env = parent.frame(),
quoted = FALSE,
execOnResize = FALSE,
@@ -41,12 +41,14 @@ rendering in R; it won't change the actual ppi of the browser.}
\item{...}{Arguments to be passed through to \code{\link[grDevices:png]{grDevices::png()}}.
These can be used to set the width, height, background color, etc.}
\item{alt}{Alternate text for the HTML \verb{<img>} tag
if it cannot be displayed or viewed (i.e., the user uses a screen reader).
In addition to a character string, the value may be a reactive expression
(or a function referencing reactive values) that returns a character string.
NULL or "" is not recommended because those should be limited to decorative images
(the default is "Plot object").}
\item{alt}{Alternate text for the HTML \verb{<img>} tag if it cannot be displayed
or viewed (i.e., the user uses a screen reader). In addition to a character
string, the value may be a reactive expression (or a function referencing
reactive values) that returns a character string. If the value is \code{NA} (the
default), then \code{ggplot2::get_alt_text()} is used to extract alt text from
ggplot objects; for other plots, \code{NA} results in alt text of "Plot object".
\code{NULL} or \code{""} is not recommended because those should be limited to
decorative images.}
\item{env}{The environment in which to evaluate \code{expr}.}

View File

@@ -30,7 +30,10 @@ expression that produces a Shiny app object.
\item{port}{The TCP port that the application should listen on. If the
\code{port} is not specified, and the \code{shiny.port} option is set (with
\code{options(shiny.port = XX)}), then that port will be used. Otherwise,
use a random port.}
use a random port between 3000:8000, excluding ports that are blocked
by Google Chrome for being considered unsafe: 3659, 4045, 5060,
5061, 6000, 6566, 6665:6669 and 6697. Up to twenty random
ports will be tried.}
\item{launch.browser}{If true, the system's default web browser will be
launched automatically after the app is started. Defaults to true in

View File

@@ -19,7 +19,10 @@ list the available examples.}
\item{port}{The TCP port that the application should listen on. If the
\code{port} is not specified, and the \code{shiny.port} option is set (with
\code{options(shiny.port = XX)}), then that port will be used. Otherwise,
use a random port.}
use a random port between 3000:8000, excluding ports that are blocked
by Google Chrome for being considered unsafe: 3659, 4045, 5060,
5061, 6000, 6566, 6665:6669 and 6697. Up to twenty random
ports will be tried.}
\item{launch.browser}{If true, the system's default web browser will be
launched automatically after the app is started. Defaults to true in

View File

@@ -68,7 +68,7 @@ options(device.ask.default = FALSE)
ui <- fluidPage(
checkboxGroupInput('in1', 'Check some letters', choices = head(LETTERS)),
selectizeInput('in2', 'Select a state', choices = state.name),
selectizeInput('in2', 'Select a state', choices = c("", state.name)),
plotOutput('plot')
)

98
package.json Normal file
View File

@@ -0,0 +1,98 @@
{
"private": true,
"homepage": "https://shiny.rstudio.com",
"repository": "github:rstudio/shiny",
"name": "@types/rstudio-shiny",
"version": "1.6.0-alpha.9021",
"license": "GPL-3.0-only",
"main": "",
"browser": "",
"types": "srcts/types/extras/globalShiny.d.ts",
"files": [
"DESCRIPTION",
"LICENSE",
"NEWS.md",
"srcts/types/**/*.d.ts"
],
"engines": {
"node": ">= 14",
"yarn": ">= 1.22"
},
"dependencies": {
"@types/bootstrap": "3.4.0",
"@types/bootstrap-datepicker": "0.0.14",
"@types/datatables.net": "^1.10.19",
"@types/ion-rangeslider": "2.3.0",
"@types/jquery": "patch:@types/jquery@3.5.5#./srcts/patch/types-jquery.patch",
"@types/selectize": "0.12.34"
},
"devDependencies": {
"@babel/core": "^7.14.3",
"@babel/plugin-proposal-class-properties": "^7.13.0",
"@babel/preset-env": "^7.14.2",
"@babel/preset-typescript": "^7.13.0",
"@babel/runtime": "^7.14.0",
"@deanc/esbuild-plugin-postcss": "^1.0.1",
"@testing-library/dom": "^7.31.0",
"@testing-library/jest-dom": "^5.12.0",
"@testing-library/user-event": "^13.1.9",
"@types/highlightjs": "^9.12.1",
"@types/jest": "^26.0.23",
"@types/jqueryui": "1.12.15",
"@types/lodash": "^4.14.170",
"@types/node": "^15.6.1",
"@types/showdown": "^1.9.3",
"@typescript-eslint/eslint-plugin": "^4.25.0",
"@typescript-eslint/parser": "^4.25.0",
"autoprefixer": "^10.2.6",
"bootstrap-datepicker": "1.9.0",
"browserslist": "^4.16.6",
"core-js": "^3.13.0",
"esbuild": "^0.12.4",
"esbuild-plugin-babel": "https://github.com/schloerke/esbuild-plugin-babel#patch-2",
"esbuild-plugin-globals": "^0.1.1",
"esbuild-plugin-sass": "https://github.com/schloerke/esbuild-plugin-sass#js-files-typo",
"eslint": "^7.27.0",
"eslint-config-prettier": "^7.2.0",
"eslint-plugin-jest": "^24.3.6",
"eslint-plugin-jest-dom": "^3.9.0",
"eslint-plugin-prettier": "^3.4.0",
"eslint-plugin-unicorn": "^33.0.1",
"ion-rangeslider": "2.3.1",
"jest": "^26.6.3",
"jquery": "3.6.0",
"lodash": "^4.17.21",
"madge": "^4.0.2",
"node-gyp": "^8.1.0",
"phantomjs-prebuilt": "^2.1.16",
"postcss": "^8.3.5",
"prettier": "2.3.0",
"readcontrol": "^1.0.0",
"replace": "^1.2.1",
"selectize": "0.12.4",
"strftime": "0.9.2",
"ts-jest": "^26",
"ts-node": "^10.0.0",
"type-coverage": "^2.17.5",
"typescript": "~4.1.5",
"util-inspect": "https://github.com/deecewan/browser-util-inspect#c0b4350df4378ffd743e8c36dd3898ce3992823e"
},
"scripts": {
"prepare": "node --eval \"1+1; // help yarn users not install the whole repo; https://github.com/yarnpkg/yarn/issues/2822#issuecomment-847360267\"",
"watch": "yarn run build_shiny --watch",
"build": "yarn run build_shiny && yarn run bundle_extras && yarn run bundle_external_libs",
"build_shiny": "yarn run checks && yarn run bundle_shiny",
"bundle_shiny": "ts-node srcts/build/shiny.ts",
"bundle_external_libs": "ts-node srcts/build/external_libs.ts",
"bundle_extras": "ts-node srcts/build/extras.ts",
"test": "jest --coverage",
"test_phantom": "echo '\n\t!! Must manually stop phantomjs test !!\n\n' && yarn bundle_shiny && phantomjs --debug=yes ../inst/www/shared/shiny.js",
"checks": "yarn run lint && yarn run build_types && yarn run coverage && yarn run circular",
"lint": "node --eval \"console.log('linting code...')\" && eslint --fix --ext .ts srcts/src",
"build_types": "tsc -p tsconfig.json",
"coverage_detailed": "yarn type-check --detail",
"coverage": "type-coverage -p tsconfig.json --at-least 90",
"circular": "madge --circular --extensions ts srcts/src",
"circular_image": "madge --circular --extensions ts --image madge.svg srcts/src"
}
}

View File

@@ -1,51 +0,0 @@
root: true
env:
browser: true
es6: true
extends:
- 'eslint:recommended'
- 'plugin:@typescript-eslint/recommended'
- 'plugin:jest/recommended'
- 'prettier/@typescript-eslint'
- 'plugin:prettier/recommended'
- 'plugin:jest-dom/recommended'
globals:
Atomics: readonly
SharedArrayBuffer: readonly
parser: '@typescript-eslint/parser'
parserOptions:
ecmaVersion: 2018
sourceType: module
plugins:
- '@typescript-eslint'
- prettier
- jest-dom
rules:
"@typescript-eslint/explicit-function-return-type":
- off
"@typescript-eslint/no-explicit-any":
- off
camelcase:
- error
default-case:
- error
indent:
- error
- 2
- SwitchCase: 1
linebreak-style:
- error
- unix
quotes:
- error
- double
- avoid-escape
semi:
- error
- always
newline-after-var:
- error
- always
dot-location:
- error
- property

9
srcts/.gitignore vendored
View File

@@ -1,9 +0,0 @@
node_modules/
.cache
.yarn/*
!.yarn/releases
!.yarn/plugins
!.yarn/sdks
!.yarn/versions
.pnp.*
coverage/

View File

@@ -1,20 +0,0 @@
#!/usr/bin/env node
const {existsSync} = require(`fs`);
const {createRequire, createRequireFromPath} = require(`module`);
const {resolve} = require(`path`);
const relPnpApiPath = "../../../../.pnp.js";
const absPnpApiPath = resolve(__dirname, relPnpApiPath);
const absRequire = (createRequire || createRequireFromPath)(absPnpApiPath);
if (existsSync(absPnpApiPath)) {
if (!process.versions.pnp) {
// Setup the environment to be able to require eslint/bin/eslint.js
require(absPnpApiPath).setup();
}
}
// Defer to the real eslint/bin/eslint.js your application uses
module.exports = absRequire(`eslint/bin/eslint.js`);

View File

@@ -1,20 +0,0 @@
#!/usr/bin/env node
const {existsSync} = require(`fs`);
const {createRequire, createRequireFromPath} = require(`module`);
const {resolve} = require(`path`);
const relPnpApiPath = "../../../../.pnp.js";
const absPnpApiPath = resolve(__dirname, relPnpApiPath);
const absRequire = (createRequire || createRequireFromPath)(absPnpApiPath);
if (existsSync(absPnpApiPath)) {
if (!process.versions.pnp) {
// Setup the environment to be able to require eslint/lib/api.js
require(absPnpApiPath).setup();
}
}
// Defer to the real eslint/lib/api.js your application uses
module.exports = absRequire(`eslint/lib/api.js`);

View File

@@ -1,6 +0,0 @@
{
"name": "eslint",
"version": "7.19.0-pnpify",
"main": "./lib/api.js",
"type": "commonjs"
}

View File

@@ -1,5 +0,0 @@
# This file is automatically generated by PnPify.
# Manual changes will be lost!
integrations:
- vscode

View File

@@ -1,30 +0,0 @@
#!/usr/bin/env node
const {existsSync} = require(`fs`);
const {createRequire, createRequireFromPath} = require(`module`);
const {resolve, dirname} = require(`path`);
const relPnpApiPath = "../../../.pnp.js";
const absPnpApiPath = resolve(__dirname, relPnpApiPath);
const absRequire = (createRequire || createRequireFromPath)(absPnpApiPath);
if (existsSync(absPnpApiPath)) {
if (!process.versions.pnp) {
// Setup the environment to be able to require prettier/index.js
require(absPnpApiPath).setup();
}
const pnpifyResolution = require.resolve(`@yarnpkg/pnpify`, {paths: [dirname(absPnpApiPath)]});
if (typeof global[`__yarnpkg_sdk_is_using_pnpify__`] === `undefined`) {
Object.defineProperty(global, `__yarnpkg_sdk_is_using_pnpify__`, {configurable: true, value: true});
process.env.NODE_OPTIONS += ` -r ${pnpifyResolution}`;
// Apply PnPify to the current process
absRequire(pnpifyResolution).patchFs();
}
}
// Defer to the real prettier/index.js your application uses
module.exports = absRequire(`prettier/index.js`);

View File

@@ -1,6 +0,0 @@
{
"name": "prettier",
"version": "2.2.1-pnpify",
"main": "./index.js",
"type": "commonjs"
}

View File

@@ -1,20 +0,0 @@
#!/usr/bin/env node
const {existsSync} = require(`fs`);
const {createRequire, createRequireFromPath} = require(`module`);
const {resolve} = require(`path`);
const relPnpApiPath = "../../../../.pnp.js";
const absPnpApiPath = resolve(__dirname, relPnpApiPath);
const absRequire = (createRequire || createRequireFromPath)(absPnpApiPath);
if (existsSync(absPnpApiPath)) {
if (!process.versions.pnp) {
// Setup the environment to be able to require typescript/bin/tsc
require(absPnpApiPath).setup();
}
}
// Defer to the real typescript/bin/tsc your application uses
module.exports = absRequire(`typescript/bin/tsc`);

View File

@@ -1,20 +0,0 @@
#!/usr/bin/env node
const {existsSync} = require(`fs`);
const {createRequire, createRequireFromPath} = require(`module`);
const {resolve} = require(`path`);
const relPnpApiPath = "../../../../.pnp.js";
const absPnpApiPath = resolve(__dirname, relPnpApiPath);
const absRequire = (createRequire || createRequireFromPath)(absPnpApiPath);
if (existsSync(absPnpApiPath)) {
if (!process.versions.pnp) {
// Setup the environment to be able to require typescript/bin/tsserver
require(absPnpApiPath).setup();
}
}
// Defer to the real typescript/bin/tsserver your application uses
module.exports = absRequire(`typescript/bin/tsserver`);

View File

@@ -1,20 +0,0 @@
#!/usr/bin/env node
const {existsSync} = require(`fs`);
const {createRequire, createRequireFromPath} = require(`module`);
const {resolve} = require(`path`);
const relPnpApiPath = "../../../../.pnp.js";
const absPnpApiPath = resolve(__dirname, relPnpApiPath);
const absRequire = (createRequire || createRequireFromPath)(absPnpApiPath);
if (existsSync(absPnpApiPath)) {
if (!process.versions.pnp) {
// Setup the environment to be able to require typescript/lib/tsc.js
require(absPnpApiPath).setup();
}
}
// Defer to the real typescript/lib/tsc.js your application uses
module.exports = absRequire(`typescript/lib/tsc.js`);

View File

@@ -1,111 +0,0 @@
#!/usr/bin/env node
const {existsSync} = require(`fs`);
const {createRequire, createRequireFromPath} = require(`module`);
const {resolve} = require(`path`);
const relPnpApiPath = "../../../../.pnp.js";
const absPnpApiPath = resolve(__dirname, relPnpApiPath);
const absRequire = (createRequire || createRequireFromPath)(absPnpApiPath);
const moduleWrapper = tsserver => {
const {isAbsolute} = require(`path`);
const pnpApi = require(`pnpapi`);
const dependencyTreeRoots = new Set(pnpApi.getDependencyTreeRoots().map(locator => {
return `${locator.name}@${locator.reference}`;
}));
// VSCode sends the zip paths to TS using the "zip://" prefix, that TS
// doesn't understand. This layer makes sure to remove the protocol
// before forwarding it to TS, and to add it back on all returned paths.
function toEditorPath(str) {
// We add the `zip:` prefix to both `.zip/` paths and virtual paths
if (isAbsolute(str) && !str.match(/^\^zip:/) && (str.match(/\.zip\//) || str.match(/\$\$virtual\//))) {
// We also take the opportunity to turn virtual paths into physical ones;
// this makes is much easier to work with workspaces that list peer
// dependencies, since otherwise Ctrl+Click would bring us to the virtual
// file instances instead of the real ones.
//
// We only do this to modules owned by the the dependency tree roots.
// This avoids breaking the resolution when jumping inside a vendor
// with peer dep (otherwise jumping into react-dom would show resolution
// errors on react).
//
const resolved = pnpApi.resolveVirtual(str);
if (resolved) {
const locator = pnpApi.findPackageLocator(resolved);
if (locator && dependencyTreeRoots.has(`${locator.name}@${locator.reference}`)) {
str = resolved;
}
}
str = str.replace(/\\/g, `/`)
str = str.replace(/^\/?/, `/`);
// Absolute VSCode `Uri.fsPath`s need to start with a slash.
// VSCode only adds it automatically for supported schemes,
// so we have to do it manually for the `zip` scheme.
// The path needs to start with a caret otherwise VSCode doesn't handle the protocol
//
// Ref: https://github.com/microsoft/vscode/issues/105014#issuecomment-686760910
//
if (str.match(/\.zip\//)) {
str = `${isVSCode ? `^` : ``}zip:${str}`;
}
}
return str;
}
function fromEditorPath(str) {
return process.platform === `win32`
? str.replace(/^\^?zip:\//, ``)
: str.replace(/^\^?zip:/, ``);
}
// And here is the point where we hijack the VSCode <-> TS communications
// by adding ourselves in the middle. We locate everything that looks
// like an absolute path of ours and normalize it.
const Session = tsserver.server.Session;
const {onMessage: originalOnMessage, send: originalSend} = Session.prototype;
let isVSCode = false;
return Object.assign(Session.prototype, {
onMessage(/** @type {string} */ message) {
const parsedMessage = JSON.parse(message)
if (
parsedMessage != null &&
typeof parsedMessage === `object` &&
parsedMessage.arguments &&
parsedMessage.arguments.hostInfo === `vscode`
) {
isVSCode = true;
}
return originalOnMessage.call(this, JSON.stringify(parsedMessage, (key, value) => {
return typeof value === `string` ? fromEditorPath(value) : value;
}));
},
send(/** @type {any} */ msg) {
return originalSend.call(this, JSON.parse(JSON.stringify(msg, (key, value) => {
return typeof value === `string` ? toEditorPath(value) : value;
})));
}
});
};
if (existsSync(absPnpApiPath)) {
if (!process.versions.pnp) {
// Setup the environment to be able to require typescript/lib/tsserver.js
require(absPnpApiPath).setup();
}
}
// Defer to the real typescript/lib/tsserver.js your application uses
module.exports = moduleWrapper(absRequire(`typescript/lib/tsserver.js`));

View File

@@ -1,20 +0,0 @@
#!/usr/bin/env node
const {existsSync} = require(`fs`);
const {createRequire, createRequireFromPath} = require(`module`);
const {resolve} = require(`path`);
const relPnpApiPath = "../../../../.pnp.js";
const absPnpApiPath = resolve(__dirname, relPnpApiPath);
const absRequire = (createRequire || createRequireFromPath)(absPnpApiPath);
if (existsSync(absPnpApiPath)) {
if (!process.versions.pnp) {
// Setup the environment to be able to require typescript/lib/typescript.js
require(absPnpApiPath).setup();
}
}
// Defer to the real typescript/lib/typescript.js your application uses
module.exports = absRequire(`typescript/lib/typescript.js`);

View File

@@ -1,6 +0,0 @@
{
"name": "typescript",
"version": "4.1.5-pnpify",
"main": "./lib/typescript.js",
"type": "commonjs"
}

View File

@@ -1,5 +1,7 @@
# TypeScript build tools
All files will be described as if the working directory is the root folder of `rstudio/shiny`, not relative to this `README.md` file.
## First-time setup
Shiny's TypeScript build tools use Node.js, along with [yarn](https://yarnpkg.com/) v2 to manage the JavaScript packages.
@@ -14,9 +16,10 @@ node --version
yarn --version
```
Once both are installed, run the following in this directory (`srcts/`) to install the packages :
Once both are installed, run the following in the root repo directory to install the packages :
```bash
# Sitting in `rstudio/shiny` repo
yarn install
```
@@ -47,12 +50,12 @@ If in the future you want to upgrade or add a package, run:
yarn add --dev [packagename]
```
This will automatically add the package to the dependencies in `./package.json`, and it will also update the `./yarn.lock` to reflect that change. If someone other than yourself does this, simply run `yarn` to update your local packages to match the new `./package.json`.
This will automatically add the package to the dependencies in `package.json`, and it will also update the `yarn.lock` to reflect that change. If someone other than yourself does this, simply run `yarn` to update your local packages to match the new `package.json`.
## Upgrading packages
Periodically, it's good to upgrade the packages to a recent version. There's two ways of doing this, depending on your intention:
1. Use `yarn up` to upgrade all dependencies to their latest version based on the version range specified in the package.json file (the `./yarn.lock` file will be recreated as well. Yarn packages use [semantic versioning](https://yarnpkg.com/en/docs/dependency-versions), i.e. each version is writen with a maximum of 3 dot-separated numbers such that: `major.minor.patch`. For example in the version `3.1.4`, 3 is the major version number, 1 is the minor version number and 4 is the patch version number. Here are the most used operators (these appear before the version number):
1. Use `yarn up` to upgrade all dependencies to their latest version based on the version range specified in the package.json file (the `yarn.lock` file will be recreated as well. Yarn packages use [semantic versioning](https://yarnpkg.com/en/docs/dependency-versions), i.e. each version is writen with a maximum of 3 dot-separated numbers such that: `major.minor.patch`. For example in the version `3.1.4`, 3 is the major version number, 1 is the minor version number and 4 is the patch version number. Here are the most used operators (these appear before the version number):
- `~` is for upgrades that keep the minor version the same (assuming that was specified);
@@ -62,16 +65,70 @@ Periodically, it's good to upgrade the packages to a recent version. There's two
3. To see all outdated packages, run `yarn outdated`
# Configure TypeScript
# TypeScript
## Learn about TypeScript
The documentation by [TypeScript](https://www.typescriptlang.org/docs/) is a solid resource to know each and every bell and whistle. Most features have examples and convey the thoughts well.
[TypeScript Deep Dive](https://basarat.gitbook.io/typescript/) is an online `bookdown`-like approach to TypeScript by "Microsoft MVP for TypeScript", Basarat Ali Syed. In his book, he goes through many examples of what you "should do", not necessarily "what is possible" like the [TypeScript docs](https://www.typescriptlang.org/docs/).
## TypeScript StyleGuide
Using the style guid from [TypeScript Deep Dive / StyleGuide](https://basarat.gitbook.io/typescript/styleguide), we extend it to have the usage be more familiar to R developers and preexisting Shiny development. The goal is to produce consistent code that can be injested quickly.
### StyleGuide
* `null` vs. `undefined`
* Do not use `x === null` unless you truly mean it.
* Safer to use _truthy_ or _falsey_ checks instead. Ex: `if (x) {}`
* `type` vs `interface`
* > Use `type` when you might need a union or intersection: `type Foo = number | { someProperty: number }`
* > Use `interface` when you want extends or implements: `interface FooBar extends Foo { bar: string;}`
* > Otherwise use whatever makes you happy that day.
* Namespace
* `PascalCase`
* Ex: `Shiny`
### Enforced (by `eslint`) StyleGuide
* Variable
* `camelCase`
* Ex: `const hello = "world`
* Class
* `PascalCase`
* Ex: `class InputBinding {}`
* Type, Interface definitions:
* `PascalCase`
* Ex: `type BindingBase = {name: string}`
* Ex: `interface ShinyEventMessage extends JQuery.Event {}`
* Enum
* `PascalCase`
* (Currently unused)
* Single vs. Double Quotes
* While the JS community has decided on single quotes, R has decided on double quotes.
* > When you can't use double quotes, try using back ticks (`).
* Annotate Arrays as `Type[]`
* Ex: `Foo[]` (vs `Array<Foo>`)
* Annotate Records as `{[key: string]: valueType}`
* Ex: `const x: {[key: string]: number} = {a: 4}`
* Ex: Extend the unknown key definition with static keys: `const x: {known: string, [key: string]: number} = {known: "yes", a: 4}`
* File Names
* `camelCase` - Enforced by `eslint`
## Config files
The JavaScript community likes to build many small, effective packages that do minimal work. The unfortunate side effect is needing a config file for everything.
## Config files
All config files are located in the root folder to avoid opening two separate VS Code projects.
* `.browserslistrc`
* Used with `browserslist` and `core-js` to determine which polyfills should be incorporated.
* `.eslintrc.yml`
* Used with `eslint` and `prettier` to determine how the TypeScript files should be formatted and which lint failures should cause warnings, errors, or be ignored.
* `.madgerc`
* Package used to determine if circular dependencies are found. `type` only imports are ignored as they are not included in the final bundle.
* `.prettierrc.yml`
* Used by `prettier` to know how to adjust code when a file is saved in VSCode or within `eslint`'s linting process.
* `yarnrc.yml`
@@ -82,15 +139,13 @@ The JavaScript community likes to build many small, effective packages that do m
* `"useBuiltIns": "usage"` - `core-js` polyfills are only added as they are _used_.
* `"corejs": "3.9"` - This number should match the installed `core-js` number.
* `"ignore":["node_modules/core-js"]` - The `core-js` library is directly ignored to [avoid being processed by `babel`](https://github.com/zloirock/core-js/issues/743#issuecomment-571983318).
* `esbuild.config.mjs`
* Script that will build `shiny.js` and `shiny.min.js` with their sourcemaps
* `jest.config.js`
* Used to configure [`jest` testing](https://jestjs.io/)
* `package.json`
* Contains useful scripts that can be run by `yarn` via `yarn run SCRIPTNAME`.
* The scripts described below are inteded for developer use. All other scripts are means to an end.
* `yarn run watch` - Watch `./src` for changes and rebuild the JavaScript files.
* `yarn run build` - Build `shiny.js` and `shiny.min.js` in `../inst/www/shared`. Both files will have a corresponding sourcemap
* `yarn run watch` - Watch `srcts/src` for changes and rebuild the JavaScript files.
* `yarn run build` - Build `shiny.js` and `shiny.min.js` in `inst/www/shared`. Both files will have a corresponding sourcemap
* `yarn run lint` - Fix all TypeScript lints using [`eslint`](https://eslint.org/) and [`prettier`](https://prettier.io/)
* `yarn run test` - Run all TypeScript tests
* `tsconfig.json` -
@@ -100,13 +155,11 @@ The JavaScript community likes to build many small, effective packages that do m
* `preserveConstEnums: false` - Do no preserve enum values into the final code. (If true, produces bloat / unused code)
* `isolatedModules: true` & `esModuleInterop: true` - Requested by `esbuild`. This [allows for `esbuild`](https://esbuild.github.io/content-types/#typescript) to safely compile the files in parallel
## Bundle TypeScript
[esbuild](https://esbuild.github.io/) is a build tool that (for Shiny's purposes) compiles the TypeScript into a single JavaScript file.
To run all build tasks, from within the `./srcts` directory, run:
To run all build tasks, run:
```bash
yarn build
@@ -120,6 +173,25 @@ yarn watch
Both JavaScript files will produce a sourcemap (`**.js.map`) that the browser will understand. This will help you debug Shiny's JavaScript code within the browser and point back to the original TypeScript files.
### Exported types
`./extras/globalShiny.ts` contains global declarations to define `window.Shiny`, a globally available `Shiny` variable, and a globally available `Shiny` type. This file is in a parallel folder to `./src` to avoid `Shiny` from being globally accessable within the source code. However, this file is the default type defintion when the Type definitions are installed by external developers.
#### External development
When developing TypeScript projects that leverage Shiny, we recommend installing the Shiny TypeScript definitions to your package. To install the definitions, call
```bash
yarn add https://github.com/rstudio/shiny\#v1.7.0
```
, matching the GitHub tag to your current the Shiny CRAN release (ex: `v1.7.0`). If you are asked to select a version of `@types/jquery`, please select the closest version.
This will provide a global type defintion of `Shiny`, let your IDE know that `window.Shiny` is of type `Shiny`, and declare a globally available variable `Shiny` within your project. You **should not** need to import anything. Similar to `jQuery`, it should _Just Work_<sup>TM</sup>.
When loading your compiled file, it should be loaded after Shiny is loaded. If you are using an `htmlDependency()` to add your code to the page, your script will automatically be loaded after has been loaded.
### GitHub Actions
On push to the `master` branch or push to a Pull Request to the `master` branch, a GitHub Action will be run to make sure the bundled JavaScript code is up to date. If the source code does not compile to the exact same file, it will be committed an pushed back to the outdated branch. (This makes it so the full build tools are not necessary for small tweaks and comments. 🎉)
@@ -141,13 +213,6 @@ For this to work you must first install `xdotool` using your distribution's pack
```bash
find ../srcts/ | entr bash -c './node_modules/grunt/bin/grunt && xdotool search --onlyvisible --class Chrome windowfocus key ctrl+r'
``` -->
# Development in VSCode
VSCode does not like to develop TypeScript with the configuration files in a subfolder. To leverage full VSCode capabilities, it is recommended to open the `./srcts` folder as the root folder of a VSCode project. This will enable VSCode to readily find all of the configuration files.
# Updating dependencies
### `@types/jquery`
@@ -165,3 +230,14 @@ To update the version of `core-js`:
* Check if there is a newer version available by running `yarn outdated core-js`. (If there's no output, then you have the latest version.)
* Run `yarn add --dev core-js --exact`.
### External libraries
Shiny already has a handful of html dependencies that should NOT be bundled within `shiny.js`. To update the dependencies below, see the directions in in [`tools/README.md`](../tools).
* `jquery` / `@types/jquery`
* `bootstrap` / `@types/bootstrap`
* Bootstrap is not being updated anymore. Only bootstrap 3.4 will be utilized within shiny.js. To use the latest bootstrap, see [`rstudio/bslib`](https://github.com/rstudio/bslib)
* `bootstrap-datepicker` / `@types/bootstrap-datepicker`
* `ion-rangeslider` / `@types/ion-rangeslider`
* `selectize` / `@types/selectize`

View File

@@ -36,19 +36,67 @@
* √ Verify it works on phantomjs / shinytest
* √ Set up initial jest tests
* √ Use a global shim to avoid importing jquery directly, but make testing easy to test
* Update the /tools/update*.R scripts to produce a version and install node dependencies
* √ jquery
* √ ion range slider
* √ selectize
* √ strftime
* √ bootstrap date picker
* font awesome?
* bootstrap accessibility plugin?
# Round #2
* Convert registered bindings
* √ Input bindings
* √ Output bindings
* Add default value to `subscribe(callback)` callback function of `false`. B/c if the value was not provided, it was not truthy, therefore equivalent to `false`.
* √ radio
* √ checkboxgroup
* √ daterange
* √ actionbutton
* √ bootstraptabinput
* √ snake_case to camelCase conversions.
* √ globally import strftime from `window.strftime`
* Remove `evt` from jQuery.on callbacks where `evt` was not used.
* √ checkbox.subscribe
* √ checkboxgroup.subscribe
* √ radio.subscribe
* √ slider.subscribe
* √ date.subscribe
* √ selectInput.subscribe
* √ actionButton.subscribe
* √ bootstraptabinput.subscribe
* Convert usage of `+x` to `Number(x)`
* https://stackoverflow.com/a/15872631/591574
* √ slider.getValue()
* √ number.getValue()
* √ Adjust tabinput.ts `setValue()` to return either `false | void`, not `false | true`.
* What matters is that `false` is returned, or nothing is returned. Replaced `return true;` with `return;`
* Questions
* Why does `receiveMessage(data)` sometimes have a `label`?
* Should we have a update datatables script?
# Later TODO
* Each _file_ will be pulled out as possible into smaller files in separate PRs
* Convert `FileProcessor` to a true class definition
* Use --strictNullChecks in tsconfig.json
* Make `_*()` methods `private *()`
* √ Each _file_ will be pulled out as possible into smaller files in separate PRs
* √ Convert `FileProcessor` to a true class definition
* Break up `./utils` into many files
* Remove any `: any` types
* Make `@typescript-eslint/explicit-module-boundary-types` an error
* Fix all `// eslint-disable-next-line no-prototype-builtins` lines
* Remove any `: any` types
* Make `@typescript-eslint/explicit-module-boundary-types` an error
* Fix all `// eslint-disable-next-line no-prototype-builtins` lines
* TypeScript other shiny files (ex: showcasemode)
* Completely remove `parcel` from `./package.json` and only use `esbuild`
* Delete 'shiny-es5' files
* Completely remove `parcel` from `./package.json` and only use `esbuild`
* Delete 'shiny-es5' files
* Delete 'old' folder
* _Uglify_ js files (like in previous Gruntfile.js)
* datepicker
* ionrangeslider
* selectize
# Eventual TODO

80
srcts/build/_build.ts Normal file
View File

@@ -0,0 +1,80 @@
import {
build as esbuildBuild,
BuildIncremental,
BuildOptions,
BuildResult,
} from "esbuild";
import readcontrol from "readcontrol";
import process from "process";
import { basename } from "path";
const outDir = "./inst/www/shared/";
type ShinyDesc = { version: string; package: string; license: string };
const shinyDesc = readcontrol.readSync("./DESCRIPTION") as ShinyDesc;
const bannerTxt = [
`/*! ${shinyDesc.package} ${shinyDesc.version}`,
`(c) 2012-${new Date().getFullYear()} RStudio, PBC.`,
`License: ${shinyDesc.license} */`,
].join(" | ");
const banner = {
js: bannerTxt,
css: bannerTxt,
};
async function build(
opts: BuildOptions
): Promise<BuildIncremental | BuildResult> {
const outFileNames = opts.outfile
? [basename(opts.outfile)]
: (opts.entryPoints as string[]).map((entry) => basename(entry));
const strSizes = outFileNames.map((outFileName) => outFileName.length);
strSizes.push("shiny.min.js".length);
const strSize = Math.max(...strSizes);
const printNames = outFileNames;
for (let i = 0; i < printNames.length; i++) {
while (printNames[i].length < strSize) {
printNames[i] = printNames[i] + " ";
}
}
const onRebuild = function (error?: string) {
if (error) {
console.error(printNames.join(", "), "watch build failed:\n", error);
} else {
printNames.map((printName) => {
console.log("√ -", printName, "-", new Date().toJSON());
});
}
return;
};
let incremental = false;
let watch: false | { onRebuild: (error, result) => void } = false;
if (process.argv.length >= 3 && process.argv[2] == "--watch") {
incremental = true;
watch = {
onRebuild: onRebuild,
};
}
outFileNames.map((outFileName) => {
console.log("Building " + outFileName);
});
return esbuildBuild({
incremental: incremental,
watch: watch,
target: "es5",
...opts,
}).then((x) => {
onRebuild();
return x;
});
}
export { outDir, build, shinyDesc, banner };

View File

@@ -0,0 +1,57 @@
// This build script must be executed from the root repo directory via
// ```
// yarn build
// ```
import { build, outDir } from "./_build";
import { readdir, unlink, writeFile } from "fs/promises";
import globalsPlugin from "esbuild-plugin-globals";
const opts = {
bundle: false,
sourcemap: false,
};
readdir(outDir + "datepicker/js/locales/").then(async (localeFiles) => {
const requireFiles = localeFiles
.map(function (filename) {
return `require("./locales/${filename}");`;
})
.join("\n");
const tmpFile = outDir + "datepicker/js/temp.js";
await writeFile(
tmpFile,
`require("./bootstrap-datepicker.js");\n${requireFiles}`
);
await build({
...opts,
plugins: [
globalsPlugin({
jquery: "window.jQuery",
}),
],
bundle: true,
entryPoints: [tmpFile],
outfile: outDir + "datepicker/js/bootstrap-datepicker.min.js",
minify: true,
});
// Clean up
unlink(tmpFile);
});
build({
...opts,
entryPoints: [outDir + "ionrangeslider/js/ion.rangeSlider.js"],
outfile: outDir + "ionrangeslider/js/ion.rangeSlider.min.js",
minify: true,
});
build({
...opts,
entryPoints: [outDir + "selectize/accessibility/js/selectize-plugin-a11y.js"],
outfile: outDir + "selectize/accessibility/js/selectize-plugin-a11y.min.js",
minify: true,
});

54
srcts/build/extras.ts Normal file
View File

@@ -0,0 +1,54 @@
// This build script must be executed from the root repo directory via
// ```
// yarn build
// ```
// - TypeScript -----------------------------------------------------------
import { banner, build, outDir } from "./_build";
import babelPlugin from "esbuild-plugin-babel";
build({
bundle: true,
sourcemap: "inline",
minify: true,
plugins: [babelPlugin()],
banner: banner,
entryPoints: [
"srcts/extras/shiny-autoreload.ts",
"srcts/extras/shiny-showcase.ts",
"srcts/extras/shiny-testmode.ts",
],
outdir: outDir,
});
// - Sass -----------------------------------------------------------
import autoprefixer from "autoprefixer";
import postCssPlugin from "@deanc/esbuild-plugin-postcss";
import sassPlugin from "esbuild-plugin-sass";
const sassOpts = {
minify: true,
banner: banner,
plugins: [
sassPlugin(),
postCssPlugin({
plugins: [autoprefixer],
}),
],
};
build({
...sassOpts,
entryPoints: ["srcts/extras/shiny-showcase.scss"],
outfile: outDir + "shiny-showcase.css",
});
build({
...sassOpts,
entryPoints: [
// Must keep shiny.scss within `inst` to be able to use as htmldependency
outDir + "shiny_scss/shiny.scss",
],
outfile: outDir + "shiny.min.css",
});

38
srcts/build/shiny.ts Normal file
View File

@@ -0,0 +1,38 @@
// This build script must be executed from the root repo directory via
// ```
// yarn build
// ```
import { banner, build, outDir, shinyDesc } from "./_build";
import globalsPlugin from "esbuild-plugin-globals";
import babelPlugin from "esbuild-plugin-babel";
import type { BuildOptions } from "esbuild";
const opts: BuildOptions = {
entryPoints: ["srcts/src/index.ts"],
bundle: true,
sourcemap: true,
plugins: [
globalsPlugin({
jquery: "window.jQuery",
//// Loaded dynamically. MUST use `window.strftime` within code
// strftime: "window.strftime",
}),
babelPlugin(),
],
define: {
// eslint-disable-next-line @typescript-eslint/naming-convention
"process.env.SHINY_VERSION": `"${shinyDesc.version}"`,
},
banner: banner,
};
build({
...opts,
outfile: outDir + "shiny.js",
});
build({
...opts,
outfile: outDir + "shiny.min.js",
minify: true,
});

View File

@@ -1,40 +0,0 @@
import esbuild from "esbuild";
import babel from "esbuild-plugin-babel";
import readcontrol from "readcontrol";
import process from "process";
import globalsPlugin from "esbuild-plugin-globals";
let watch = process.argv.length >= 3 && process.argv[2] == "--watch";
let outdir = "../inst/www/shared/";
let opts = {
entryPoints: ["src/index.ts"],
bundle: true,
watch: watch,
plugins: [
globalsPlugin({
jquery: "window.jQuery",
}),
babel(),
],
target: "es5",
sourcemap: true,
define: {
"process.env.SHINY_VERSION": `"${
readcontrol.readSync("../DESCRIPTION").version
}"`,
},
};
console.log("Building shiny.js");
await esbuild.build({
...opts,
outfile: outdir + "shiny.js",
});
console.log("Building shiny.min.js");
await esbuild.build({
...opts,
outfile: outdir + "shiny.min.js",
minify: true,
});

View File

@@ -1,63 +0,0 @@
import {readdirSync, unlinkSync, writeFileSync} from "fs";
import esbuild from "esbuild";
import globalsPlugin from "esbuild-plugin-globals";
// import process from "process";
// let watch = process.argv.length >= 3 && process.argv[2] == "--watch";
let instdir = "../inst/";
let outdir = instdir + "/www/shared/";
let opts = {
bundle: false,
watch: false,
target: "es5",
sourcemap: false,
};
console.log("Building datepicker");
const locale_files = readdirSync(instdir + "www/shared/datepicker/js/locales/")
let require_files = locale_files.map(function(filename) {
return `require("./locales/${ filename }");`;
}).join("\n");
let tmpfile = instdir + "www/shared/datepicker/js/temp.js";
writeFileSync(tmpfile,
`require("./bootstrap-datepicker.js");
${require_files}`)
await esbuild.build({
...opts,
plugins:[
globalsPlugin({
jquery: "window.jQuery",
})
],
bundle: true,
entryPoints: [tmpfile],
outfile: instdir + "www/shared/datepicker/js/bootstrap-datepicker.min.js",
external: ['jquery'],
minify: true,
});
// Clean up
unlinkSync(tmpfile);
console.log("Building ionrangeslider");
await esbuild.build({
...opts,
entryPoints: [
instdir + "www/shared/ionrangeslider/js/ion.rangeSlider.js"
],
outfile: instdir + "www/shared/ionrangeslider/js/ion.rangeSlider.min.js",
minify: true,
});
console.log("Building selectize");
await esbuild.build({
...opts,
entryPoints: [
instdir + "www/shared/selectize/accessibility/js/selectize-plugin-a11y.js"
],
outfile: instdir + "www/shared/selectize/accessibility/js/selectize-plugin-a11y.min.js",
minify: true,
});

View File

@@ -0,0 +1,20 @@
// Type definitions for @types-rstudio/shiny
// Project: Shiny <https://shiny.rstudio.com/>
// Definitions by: RStudio <https://www.rstudio.com/>
import type { Shiny as RStudioShiny } from "../src/shiny/index";
declare global {
// Tell Shiny variable globally exists
// eslint-disable-next-line @typescript-eslint/naming-convention
const Shiny: RStudioShiny;
// Tell window.Shiny exists
interface Window {
// eslint-disable-next-line @typescript-eslint/naming-convention
Shiny: RStudioShiny;
}
// Make `Shiny` a globally available type definition. (No need to import the type)
type Shiny = RStudioShiny;
}

View File

@@ -0,0 +1,19 @@
/* eslint-disable unicorn/filename-case */
let protocol = "ws:";
if (window.location.protocol === "https:") protocol = "wss:";
let defaultPath = window.location.pathname;
if (!/\/$/.test(defaultPath)) defaultPath += "/";
defaultPath += "autoreload/";
const ws = new WebSocket(protocol + "//" + window.location.host + defaultPath);
ws.onmessage = function (event) {
if (event.data === "autoreload") {
window.location.reload();
}
};
export {};

View File

@@ -0,0 +1,86 @@
#showcase-well {
border-radius: 0;
}
.shiny-code {
background-color: white;
margin-bottom: 0;
code {
font-family: Menlo, Consolas, "Courier New", monospace;
}
}
.shiny-code-container {
margin-top: 20px;
clear: both;
h3 {
display: inline;
margin-right: 15px;
}
}
.showcase-header {
font-size: 16px;
font-weight: normal;
}
.showcase-code-link {
text-align: right;
padding: 15px;
}
#showcase-app-container {
vertical-align: top;
}
#showcase-code-tabs {
pre {
border: none;
line-height: 1em;
}
.nav {
margin-bottom: 0px;
}
ul {
margin-bottom: 0px;
}
margin-right: 15px;
.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: white;
}
}

View File

@@ -0,0 +1,315 @@
/* eslint-disable unicorn/filename-case */
import "./globalShiny";
type ShowcaseSrcMessage = {
srcref: number[];
srcfile: string;
};
const animateMs = 400;
// Given a DOM node and a column (count of characters), walk recursively
// through the node's siblings counting characters until the given number
// of characters have been found.
//
// If the given count is bigger than the number of characters contained by
// the node and its siblings, returns a null node and the number of
// characters found.
function findTextColPoint(node: Node, col: number) {
let cols = 0;
if (node.nodeType === 3) {
const nchar = node.nodeValue.replace(/\n/g, "").length;
if (nchar >= col) {
return { element: node, offset: col };
} else {
cols += nchar;
}
} else if (node.nodeType === 1 && node.firstChild) {
const ret = findTextColPoint(node.firstChild, col);
if (ret.element !== null) {
return ret;
} else {
cols += ret.offset;
}
}
if (node.nextSibling) return findTextColPoint(node.nextSibling, col - cols);
else return { element: null, offset: cols };
}
// Returns an object indicating the element containing the given line and
// column of text, and the offset into that element where the text was found.
//
// If the given line and column are not found, returns a null element and
// the number of lines found.
function findTextPoint(el: Node, line: number, col: number) {
let newlines = 0;
for (let childId = 0; childId < el.childNodes.length; childId++) {
const child = el.childNodes[childId];
// If this is a text node, count the number of newlines it contains.
if (child.nodeType === 3) {
// TEXT_NODE
const newlinere = /\n/g;
let match: ReturnType<RegExp["exec"]>;
while ((match = newlinere.exec(child.nodeValue)) !== null) {
newlines++;
// Found the desired line, now find the column.
if (newlines === line) {
return findTextColPoint(child, match.index + col + 1);
}
}
}
// If this is not a text node, descend recursively to see how many
// lines it contains.
else if (child.nodeType === 1) {
// ELEMENT_NODE
const ret = findTextPoint(child, line - newlines, col);
if (ret.element !== null) return ret;
else newlines += ret.offset;
}
}
return { element: null, offset: newlines };
}
// Draw a highlight effect for the given source ref. srcref is assumed to be
// an integer array of length 6, following the standard R format for source
// refs.
function highlightSrcref(
srcref: ShowcaseSrcMessage["srcref"],
srcfile: ShowcaseSrcMessage["srcfile"]
) {
// Check to see if the browser supports text ranges (IE8 doesn't)
if (!document.createRange) return;
// Check to see if we already have a marker for this source ref
let el = document.getElementById("srcref_" + srcref);
if (!el) {
// We don't have a marker, create one
el = document.createElement("span");
el.id = "srcref_" + srcref;
const ref = srcref;
const code = document.getElementById(srcfile.replace(/\./g, "_") + "_code");
// if there is no code file (might be a shiny file), quit early
if (!code) return;
const start = findTextPoint(code, ref[0], ref[4]);
const end = findTextPoint(code, ref[2], ref[5]);
// If the insertion point can't be found, bail out now
if (start.element === null || end.element === null) return;
const range = document.createRange();
// If the text points are inside different <SPAN>s, we may not be able to
// surround them without breaking apart the elements to keep the DOM tree
// intact. Just move the selection points to encompass the contents of
// the SPANs.
if (
start.element.parentNode.nodeName === "SPAN" &&
start.element !== end.element
) {
range.setStartBefore(start.element.parentNode);
} else {
range.setStart(start.element, start.offset);
}
if (
end.element.parentNode.nodeName === "SPAN" &&
start.element !== end.element
) {
range.setEndAfter(end.element.parentNode);
} else {
range.setEnd(end.element, end.offset);
}
range.surroundContents(el);
}
// End any previous highlight before starting this one
$(el).stop(true, true).effect("highlight", null, 1600);
}
// If this is the main Shiny window, wire up our custom message handler.
// TODO-barret, this should work
if (Shiny) {
Shiny.addCustomMessageHandler(
"showcase-src",
function (message: ShowcaseSrcMessage) {
if (message.srcref && message.srcfile) {
highlightSrcref(message.srcref, message.srcfile);
}
}
);
}
let isCodeAbove = false;
const setCodePosition = function (above: boolean, animate: boolean) {
const animateCodeMs = animate ? animateMs : 1;
// set the source and targets for the tab move
const newHostElement = above
? document.getElementById("showcase-sxs-code")
: document.getElementById("showcase-code-inline");
const currentHostElement = above
? document.getElementById("showcase-code-inline")
: document.getElementById("showcase-sxs-code");
const metadataElement = document.getElementById("showcase-app-metadata");
if (metadataElement === null) {
// if there's no app metadata, show and hide the entire well container
// when the code changes position
const wellElement = $("#showcase-well");
if (above) {
wellElement.fadeOut(animateCodeMs);
} else {
wellElement.fadeIn(animateCodeMs);
}
}
// hide the new element before doing anything to it
$(newHostElement).hide();
$(currentHostElement).fadeOut(animateCodeMs, function () {
const tabs = document.getElementById("showcase-code-tabs");
currentHostElement.removeChild(tabs);
newHostElement.appendChild(tabs);
// remove or set the height of the code
if (above) {
setCodeHeightFromDocHeight();
} else {
document.getElementById("showcase-code-content").removeAttribute("style");
}
$(newHostElement).fadeIn(animateCodeMs);
if (!above) {
// remove the applied width and zoom on the app container, and
// scroll smoothly down to the code's new home
document
.getElementById("showcase-app-container")
.removeAttribute("style");
if (animate)
$(document.body).animate({
scrollTop: $(newHostElement).offset().top,
});
}
// if there's a readme, move it either alongside the code or beneath
// the app
const readme = document.getElementById("readme-md");
if (readme !== null) {
readme.parentElement.removeChild(readme);
if (above) {
currentHostElement.appendChild(readme);
$(currentHostElement).fadeIn(animateCodeMs);
} else
document.getElementById("showcase-app-metadata").appendChild(readme);
}
// change the text on the toggle button to reflect the new state
document.getElementById("showcase-code-position-toggle").innerHTML = above
? '<i class="fa fa-level-down"></i> show below'
: '<i class="fa fa-level-up"></i> show with app';
});
if (above) {
$(document.body).animate({ scrollTop: 0 }, animateCodeMs);
}
isCodeAbove = above;
setAppCodeSxsWidths(above && animate);
$(window).trigger("resize");
};
function setAppCodeSxsWidths(animate: boolean) {
const appTargetWidth = 960;
let appWidth = appTargetWidth;
let zoom = 1.0;
const totalWidth = document.getElementById("showcase-app-code").offsetWidth;
if (totalWidth / 2 > appTargetWidth) {
// If the app can use only half the available space and still meet its
// target, take half the available space.
appWidth = totalWidth / 2;
} else if (totalWidth * 0.66 > appTargetWidth) {
// If the app can meet its target by taking up more space (up to 66%
// of its container), take up more space.
appWidth = 960;
} else {
// The space is too narrow for the app and code to live side-by-side
// in a friendly way. Keep the app at 2/3 of the space but scale it.
appWidth = totalWidth * 0.66;
zoom = appWidth / appTargetWidth;
}
const app = document.getElementById("showcase-app-container");
$(app).animate(
{
width: appWidth + "px",
zoom: zoom * 100 + "%",
},
animate ? animateMs : 0
);
}
const toggleCodePosition = function () {
setCodePosition(!isCodeAbove, true);
};
// if the browser is sized to wider than 1350px, show the code next to the
// app by default
const setInitialCodePosition = function () {
if (document.body.offsetWidth > 1350) {
setCodePosition(true, false);
}
};
// make the code scrollable to about the height of the browser, less space
// for the tabs
function setCodeHeightFromDocHeight() {
document.getElementById("showcase-code-content").style.height =
$(window).height() + "px";
}
// if there's a block of markdown content, render it to HTML
function renderMarkdown() {
const mdContent = document.getElementById("showcase-markdown-content");
if (mdContent !== null) {
// IE8 puts the content of <script> tags into innerHTML but
// not innerText
const content = mdContent.innerText || mdContent.innerHTML;
const showdownConverter = (window as any).Showdown
.converter as showdown.ConverterStatic;
document.getElementById("readme-md").innerHTML =
new showdownConverter().makeHtml(content);
}
}
$(window).resize(function () {
if (isCodeAbove) {
setAppCodeSxsWidths(false);
setCodeHeightFromDocHeight();
}
});
declare global {
interface Window {
toggleCodePosition: () => void;
}
}
window.toggleCodePosition = toggleCodePosition;
$(window).on("load", setInitialCodePosition);
$(window).on("load", renderMarkdown);
if (window.hljs) window.hljs.initHighlightingOnLoad();
export {};

View File

@@ -0,0 +1,10 @@
/* eslint-disable unicorn/filename-case */
import { indirectEval } from "../src/utils/eval";
// Listen for messages from parent frame. This file is only added when the
// shiny.testmode option is TRUE.
window.addEventListener("message", function (e: { data: { code: string } }) {
const message = e.data;
if (message.code) indirectEval(message.code);
});

View File

@@ -0,0 +1,17 @@
(function() {
var protocol = 'ws:';
if (window.location.protocol === 'https:')
protocol = 'wss:';
var defaultPath = window.location.pathname;
if (!/\/$/.test(defaultPath))
defaultPath += '/';
defaultPath += 'autoreload/';
var ws = new WebSocket(protocol + '//' + window.location.host + defaultPath);
ws.onmessage = function(event) {
if (event.data === "autoreload") {
window.location.reload()
}
}
})();

View File

@@ -0,0 +1,87 @@
#showcase-well {
border-radius: 0;
-webkit-border-radius: 0;
-moz-border-radius: 0;
}
.shiny-code {
background-color: white;
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: normal;
}
.showcase-code-link {
text-align: right;
padding: 15px;
}
#showcase-app-container {
vertical-align: top;
}
#showcase-code-tabs pre {
border: none;
line-height: 1em;
}
#showcase-code-tabs .nav,
#showcase-code-tabs ul {
margin-bottom: 0px;
}
#showcase-app-code {
width: 100%;
}
#showcase-code-tabs {
margin-right: 15px;
}
#showcase-code-tabs .tab-content {
border-style: solid;
border-color: #e5e5e5;
border-width: 0px 1px 1px 1px;
overflow:auto;
-webkit-border-bottom-right-radius: 4px;
-webkit-border-bottom-left-radius: 4px;
-moz-border-radius-bottomright: 4px;
-moz-border-radius-bottomleft: 4px;
border-bottom-right-radius: 4px;
border-bottom-left-radius: 4px;
}
#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: white;
}

View File

@@ -0,0 +1,272 @@
/*jshint browser:true, jquery:true, strict:false, curly:false, indent:2*/
(function() {
var animateMs = 400;
// Given a DOM node and a column (count of characters), walk recursively
// through the node's siblings counting characters until the given number
// of characters have been found.
//
// If the given count is bigger than the number of characters contained by
// the node and its siblings, returns a null node and the number of
// characters found.
function findTextColPoint(node, col) {
var cols = 0;
if (node.nodeType === 3) {
var nchar = node.nodeValue.replace(/\n/g, "").length;
if (nchar >= col) {
return { element: node, offset: col };
} else {
cols += nchar;
}
} else if (node.nodeType === 1 && node.firstChild) {
var ret = findTextColPoint(node.firstChild, col);
if (ret.element !== null) {
return ret;
} else {
cols += ret.offset;
}
}
if (node.nextSibling)
return findTextColPoint(node.nextSibling, col - cols);
else
return { element: null, offset: cols };
}
// Returns an object indicating the element containing the given line and
// column of text, and the offset into that element where the text was found.
//
// If the given line and column are not found, returns a null element and
// the number of lines found.
function findTextPoint(el, line, col) {
var newlines = 0;
for (var childId = 0; childId < el.childNodes.length; childId++) {
var child = el.childNodes[childId];
// If this is a text node, count the number of newlines it contains.
if (child.nodeType === 3) { // TEXT_NODE
var newlinere = /\n/g;
var match;
while ((match = newlinere.exec(child.nodeValue)) !== null) {
newlines++;
// Found the desired line, now find the column.
if (newlines === line) {
return findTextColPoint(child, match.index + col + 1);
}
}
}
// If this is not a text node, descend recursively to see how many
// lines it contains.
else if (child.nodeType === 1) { // ELEMENT_NODE
var ret = findTextPoint(child, line - newlines, col);
if (ret.element !== null)
return ret;
else
newlines += ret.offset;
}
}
return { element: null, offset: newlines };
}
// Draw a highlight effect for the given source ref. srcref is assumed to be
// an integer array of length 6, following the standard R format for source
// refs.
function highlightSrcref (srcref, srcfile) {
// Check to see if the browser supports text ranges (IE8 doesn't)
if (!document.createRange)
return;
// Check to see if we already have a marker for this source ref
var el = document.getElementById("srcref_" + srcref);
if (!el) {
// We don't have a marker, create one
el = document.createElement("span");
el.id = "srcref_" + srcref;
var ref = srcref;
var code = document.getElementById(srcfile.replace(/\./g, "_") + "_code");
// if there is no code file (might be a shiny file), quit early
if (!code) return;
var start = findTextPoint(code, ref[0], ref[4]);
var end = findTextPoint(code, ref[2], ref[5]);
// If the insertion point can't be found, bail out now
if (start.element === null || end.element === null)
return;
var range = document.createRange();
// If the text points are inside different <SPAN>s, we may not be able to
// surround them without breaking apart the elements to keep the DOM tree
// intact. Just move the selection points to encompass the contents of
// the SPANs.
if (start.element.parentNode.nodeName === "SPAN" &&
start.element !== end.element) {
range.setStartBefore(start.element.parentNode);
} else {
range.setStart(start.element, start.offset);
}
if (end.element.parentNode.nodeName === "SPAN" &&
start.element !== end.element) {
range.setEndAfter(end.element.parentNode);
} else {
range.setEnd(end.element, end.offset);
}
range.surroundContents(el);
}
// End any previous highlight before starting this one
jQuery(el)
.stop(true, true)
.effect("highlight", null, 1600);
}
// If this is the main Shiny window, wire up our custom message handler.
if (window.Shiny) {
Shiny.addCustomMessageHandler('showcase-src', function(message) {
if (message.srcref && message.srcfile) {
highlightSrcref(message.srcref, message.srcfile);
}
});
}
var isCodeAbove = false;
var setCodePosition = function(above, animate) {
var animateCodeMs = animate ? animateMs : 1;
// set the source and targets for the tab move
var newHostElement = above ?
document.getElementById("showcase-sxs-code") :
document.getElementById("showcase-code-inline");
var currentHostElement = above ?
document.getElementById("showcase-code-inline") :
document.getElementById("showcase-sxs-code");
var metadataElement = document.getElementById("showcase-app-metadata");
if (metadataElement === null) {
// if there's no app metadata, show and hide the entire well container
// when the code changes position
var wellElement = $("#showcase-well");
if (above) {
wellElement.fadeOut(animateCodeMs);
} else {
wellElement.fadeIn(animateCodeMs);
}
}
// hide the new element before doing anything to it
$(newHostElement).hide();
$(currentHostElement).fadeOut(animateCodeMs, function() {
var tabs = document.getElementById("showcase-code-tabs");
currentHostElement.removeChild(tabs);
newHostElement.appendChild(tabs);
// remove or set the height of the code
if (above) {
setCodeHeightFromDocHeight();
} else {
document.getElementById("showcase-code-content").removeAttribute("style");
}
$(newHostElement).fadeIn(animateCodeMs);
if (!above) {
// remove the applied width and zoom on the app container, and
// scroll smoothly down to the code's new home
document.getElementById("showcase-app-container").removeAttribute("style");
if (animate)
$(document.body).animate({ scrollTop: $(newHostElement).offset().top });
}
// if there's a readme, move it either alongside the code or beneath
// the app
var readme = document.getElementById("readme-md");
if (readme !== null) {
readme.parentElement.removeChild(readme);
if (above) {
currentHostElement.appendChild(readme);
$(currentHostElement).fadeIn(animateCodeMs);
}
else
document.getElementById("showcase-app-metadata").appendChild(readme);
}
// change the text on the toggle button to reflect the new state
document.getElementById("showcase-code-position-toggle").innerHTML = above ?
'<i class="fa fa-level-down"></i> show below' :
'<i class="fa fa-level-up"></i> show with app';
});
if (above) {
$(document.body).animate({ scrollTop: 0 }, animateCodeMs);
}
isCodeAbove = above;
setAppCodeSxsWidths(above && animate);
$(window).trigger("resize");
};
var setAppCodeSxsWidths = function(animate) {
var appTargetWidth = 960;
var appWidth = appTargetWidth;
var zoom = 1.0;
var totalWidth = document.getElementById("showcase-app-code").offsetWidth;
if (totalWidth / 2 > appTargetWidth) {
// If the app can use only half the available space and still meet its
// target, take half the available space.
appWidth = totalWidth / 2;
} else if (totalWidth * 0.66 > appTargetWidth) {
// If the app can meet its target by taking up more space (up to 66%
// of its container), take up more space.
appWidth = 960;
} else {
// The space is too narrow for the app and code to live side-by-side
// in a friendly way. Keep the app at 2/3 of the space but scale it.
appWidth = totalWidth * 0.66;
zoom = appWidth/appTargetWidth;
}
var app = document.getElementById("showcase-app-container");
$(app).animate({
width: appWidth + "px",
zoom: (zoom*100) + "%"
}, animate ? animateMs : 0);
};
var toggleCodePosition = function() {
setCodePosition(!isCodeAbove, true);
};
// if the browser is sized to wider than 1350px, show the code next to the
// app by default
var setInitialCodePosition = function() {
if (document.body.offsetWidth > 1350) {
setCodePosition(true, false);
}
};
// make the code scrollable to about the height of the browser, less space
// for the tabs
var setCodeHeightFromDocHeight = function() {
document.getElementById("showcase-code-content").style.height =
$(window).height() + "px";
};
// if there's a block of markdown content, render it to HTML
var renderMarkdown = function() {
var mdContent = document.getElementById("showcase-markdown-content");
if (mdContent !== null) {
// IE8 puts the content of <script> tags into innerHTML but
// not innerText
var content = mdContent.innerText || mdContent.innerHTML;
document.getElementById("readme-md").innerHTML =
(new Showdown.converter()).makeHtml(content)
}
}
$(window).resize(function() {
if (isCodeAbove) {
setAppCodeSxsWidths(false);
setCodeHeightFromDocHeight();
}
});
window.toggleCodePosition = toggleCodePosition;
$(window).on("load", setInitialCodePosition);
$(window).on("load", renderMarkdown);
if (window.hljs)
hljs.initHighlightingOnLoad();
})();

View File

@@ -0,0 +1,8 @@
// Listen for messages from parent frame. This file is only added when the
// shiny.testmode option is TRUE.
window.addEventListener("message", function(e) {
var message = e.data;
if (message.code)
eval(message.code);
});

Some files were not shown because too many files have changed in this diff Show More