Compare commits

...

13 Commits

Author SHA1 Message Date
Barret Schloerke
c796ad1694 init some jsdoc on a couple utils methods 2021-06-23 15:41:37 -04: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
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
276 changed files with 4994 additions and 14814 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,14 +123,24 @@ 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 ./src_d && 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"
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
@@ -145,10 +155,16 @@ jobs:
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

View File

@@ -7,6 +7,8 @@ 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
* Bootstrap 5 support. (#3410 and rstudio/bslib#304)

View File

@@ -208,6 +208,15 @@ selectizeIt <- function(inputId, select, options, nonempty = FALSE) {
options$plugins <- c(options$plugins, list('selectize-plugin-a11y'))
}
# 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). Discussion
# of usage here: https://github.com/selectize/selectize.js/issues/192
if (is.null(options$dropdownParent)) {
options$dropdownParent <- "body"
}
res <- checkAsIs(options)
deps <- list(selectizeDependency())

View File

@@ -229,8 +229,11 @@ ionRangeSliderDependencyCSS <- function(theme) {
}
bslib::bs_dependency(
input = sass::sass_file(
system.file(package = "shiny", "www/shared/ionrangeslider/scss/shiny.scss")
input = list(
list(accent = "$component-active-bg"),
sass::sass_file(
system.file(package = "shiny", "www/shared/ionrangeslider/scss/shiny.scss")
)
),
theme = theme,
name = "ionRangeSlider",

View File

@@ -113,7 +113,7 @@
#' }
#' @export
insertTab <- function(inputId, tab, target = NULL,
position = c("before", "after"), select = FALSE,
position = c("after", "before"), select = FALSE,
session = getDefaultReactiveDomain()) {
bslib::nav_insert(
inputId, tab, target,
@@ -137,14 +137,14 @@ insertTab <- function(inputId, tab, target = NULL,
#' @export
prependTab <- function(inputId, tab, select = FALSE, menuName = NULL,
session = getDefaultReactiveDomain()) {
bslib::tab_prepend(inputId, tab, menu_title = menuName, select = select, session = session)
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()) {
bslib::tab_append(inputId, tab, menu_title = menuName, select = select, session = session)
bslib::nav_append(inputId, tab, menu_title = menuName, select = select, session = session)
}
#' @rdname insertTab

View File

@@ -344,7 +344,7 @@ custom_print.ggplot <- function(x) {
# Infer alt text description from renderPlot() value
# (currently just ggplot2 is supported)
getAltText <- function(x, default = "Plot Object") {
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")) {

File diff suppressed because one or more lines are too long

View File

@@ -172,9 +172,9 @@
.irs--shiny .irs-bar {
top: 25px;
height: 8px;
border-top: 1px solid #337ab7;
border-bottom: 1px solid #337ab7;
background: #337ab7;
border-top: 1px solid #428bca;
border-bottom: 1px solid #428bca;
background: #428bca;
}
.irs--shiny .irs-bar--single {
@@ -228,7 +228,7 @@
color: #fff;
text-shadow: none;
padding: 1px 3px;
background-color: #337ab7;
background-color: #428bca;
border-radius: 3px;
font-size: 11px;
line-height: 1.333;

View File

@@ -37,7 +37,7 @@ $font-family: $font-family-base !default;
// "High-level" coloring
$bg: $body-bg !default;
$fg: color-contrast($body-bg) !default;
$accent: $component-active-bg !default;
$accent: #428bca !default;
// "Low-level" coloring, borders, and fonts
$line_bg: linear-gradient(to bottom, mix($bg, $fg, 87%) -50%, $bg 150%) !default;

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,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsiLi4vLi4vLi4vc3JjdHMvZXh0cmFzL3NoaW55LWF1dG9yZWxvYWQudHMiXSwKICAic291cmNlc0NvbnRlbnQiOiBbInZhciBwcm90b2NvbCA9IFwid3M6XCI7XG5pZiAod2luZG93LmxvY2F0aW9uLnByb3RvY29sID09PSBcImh0dHBzOlwiKSBwcm90b2NvbCA9IFwid3NzOlwiO1xudmFyIGRlZmF1bHRQYXRoID0gd2luZG93LmxvY2F0aW9uLnBhdGhuYW1lO1xuaWYgKCEvXFwvJC8udGVzdChkZWZhdWx0UGF0aCkpIGRlZmF1bHRQYXRoICs9IFwiL1wiO1xuZGVmYXVsdFBhdGggKz0gXCJhdXRvcmVsb2FkL1wiO1xudmFyIHdzID0gbmV3IFdlYlNvY2tldChwcm90b2NvbCArIFwiLy9cIiArIHdpbmRvdy5sb2NhdGlvbi5ob3N0ICsgZGVmYXVsdFBhdGgpO1xuXG53cy5vbm1lc3NhZ2UgPSBmdW5jdGlvbiAoZXZlbnQpIHtcbiAgaWYgKGV2ZW50LmRhdGEgPT09IFwiYXV0b3JlbG9hZFwiKSB7XG4gICAgd2luZG93LmxvY2F0aW9uLnJlbG9hZCgpO1xuICB9XG59O1xuXG5leHBvcnQge307Il0sCiAgIm1hcHBpbmdzIjogIjtZQUFBLEdBQUksR0FBVyxNQUNmLEFBQUksT0FBTyxTQUFTLFdBQWEsVUFBVSxHQUFXLFFBQ3RELEdBQUksR0FBYyxPQUFPLFNBQVMsU0FDbEMsQUFBSyxNQUFNLEtBQUssSUFBYyxJQUFlLEtBQzdDLEdBQWUsY0FDZixHQUFJLEdBQUssR0FBSSxXQUFVLEVBQVcsS0FBTyxPQUFPLFNBQVMsS0FBTyxHQUVoRSxFQUFHLFVBQVksU0FBVSxFQUFPLENBQzlCLEFBQUksRUFBTSxPQUFTLGNBQ2pCLE9BQU8sU0FBUyIsCiAgIm5hbWVzIjogW10KfQo=

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,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsiLi4vLi4vLi4vc3JjdHMvc3JjL3V0aWxzL2V2YWwudHMiLCAiLi4vLi4vLi4vc3JjdHMvZXh0cmFzL3NoaW55LXRlc3Rtb2RlLnRzIl0sCiAgInNvdXJjZXNDb250ZW50IjogWyIvL2VzYnVpbGQuZ2l0aHViLmlvL2NvbnRlbnQtdHlwZXMvI2RpcmVjdC1ldmFsXG4vL3RsL2RyO1xuLy8gKiBEaXJlY3QgdXNhZ2Ugb2YgYGV2YWwoXCJ4XCIpYCBpcyBiYWQgd2l0aCBidW5kbGVkIGNvZGUuXG4vLyAqIEluc3RlYWQsIHVzZSBpbmRpcmVjdCBjYWxscyB0byBgZXZhbGAgc3VjaCBhcyBgaW5kaXJlY3RFdmFsKFwieFwiKWBcbi8vICAgKiBFdmVuIGp1c3QgcmVuYW1pbmcgdGhlIGZ1bmN0aW9uIHdvcmtzIHdlbGwgZW5vdWdoLlxuLy8gPiBUaGlzIGlzIGtub3duIGFzIFwiaW5kaXJlY3QgZXZhbFwiIGJlY2F1c2UgZXZhbCBpcyBub3QgYmVpbmcgY2FsbGVkIGRpcmVjdGx5LCBhbmQgc28gZG9lcyBub3QgdHJpZ2dlciB0aGUgZ3JhbW1hdGljYWwgc3BlY2lhbCBjYXNlIGZvciBkaXJlY3QgZXZhbCBpbiB0aGUgSmF2YVNjcmlwdCBWTS4gWW91IGNhbiBjYWxsIGluZGlyZWN0IGV2YWwgdXNpbmcgYW55IHN5bnRheCBhdCBhbGwgZXhjZXB0IGZvciBhbiBleHByZXNzaW9uIG9mIHRoZSBleGFjdCBmb3JtIGV2YWwoJ3gnKS4gRm9yIGV4YW1wbGUsIHZhciBldmFsMiA9IGV2YWw7IGV2YWwyKCd4JykgYW5kIFtldmFsXVswXSgneCcpIGFuZCB3aW5kb3cuZXZhbCgneCcpIGFyZSBhbGwgaW5kaXJlY3QgZXZhbCBjYWxscy5cbi8vID4gV2hlbiB5b3UgdXNlIGluZGlyZWN0IGV2YWwsIHRoZSBjb2RlIGlzIGV2YWx1YXRlZCBpbiB0aGUgZ2xvYmFsIHNjb3BlIGluc3RlYWQgb2YgaW4gdGhlIGlubGluZSBzY29wZSBvZiB0aGUgY2FsbGVyLlxuXG4vKipcbiAqIEV2YWx1YXRlcyBKYXZhU2NyaXB0IGNvZGUgYW5kIGV4ZWN1dGVzIGl0IHZpYSBfSW5kaXJlY3QgRXZhbHVhdGlvbl9cbiAqIEBwYXJhbSB5IEEgU3RyaW5nIHZhbHVlIHRoYXQgY29udGFpbnMgdmFsaWQgSmF2YVNjcmlwdCBjb2RlLlxuICpcbiAqIEZyb20gW2VzYnVpbGRdKGVzYnVpbGQuZ2l0aHViLmlvL2NvbnRlbnQtdHlwZXMvI2RpcmVjdC1ldmFsKTogRGlyZWN0IHVzYWdlIG9mIGBldmFsKFwieFwiKWAgaXMgYmFkIHdpdGggYnVuZGxlZCBjb2RlLiBJbnN0ZWFkLCB1c2UgaW5kaXJlY3QgY2FsbHMgdG8gYGV2YWxgIHN1Y2ggYXMgYGluZGlyZWN0RXZhbChcInhcIilgLiBFdmVuIGp1c3QgcmVuYW1pbmcgdGhlIGZ1bmN0aW9uIHdvcmtzIHdlbGwgZW5vdWdoLlxuICpcbiAqID4gVGhpcyBpcyBrbm93biBhcyBcImluZGlyZWN0IGV2YWxcIiBiZWNhdXNlIGV2YWwgaXMgbm90IGJlaW5nIGNhbGxlZCBkaXJlY3RseSwgYW5kIHNvIGRvZXMgbm90IHRyaWdnZXIgdGhlIGdyYW1tYXRpY2FsIHNwZWNpYWwgY2FzZSBmb3IgZGlyZWN0IGV2YWwgaW4gdGhlIEphdmFTY3JpcHQgVk0uIFlvdSBjYW4gY2FsbCBpbmRpcmVjdCBldmFsIHVzaW5nIGFueSBzeW50YXggYXQgYWxsIGV4Y2VwdCBmb3IgYW4gZXhwcmVzc2lvbiBvZiB0aGUgZXhhY3QgZm9ybSBgZXZhbCgneCcpYC4gRm9yIGV4YW1wbGUsIGB2YXIgZXZhbDIgPSBldmFsOyBldmFsMigneCcpYCBhbmQgYFtldmFsXVswXSgneCcpYCBhbmQgd2luZG93LmBldmFsKCd4JylgIGFyZSBhbGwgaW5kaXJlY3QgZXZhbCBjYWxscy5cbiAqID4gV2hlbiB5b3UgdXNlIGluZGlyZWN0IGV2YWwsIHRoZSBjb2RlIGlzIGV2YWx1YXRlZCBpbiB0aGUgZ2xvYmFsIHNjb3BlIGluc3RlYWQgb2YgaW4gdGhlIGlubGluZSBzY29wZSBvZiB0aGUgY2FsbGVyLlxuICpcbiAqL1xudmFyIGluZGlyZWN0RXZhbCA9IGV2YWw7XG5leHBvcnQgeyBpbmRpcmVjdEV2YWwgfTsiLCAiaW1wb3J0IHsgaW5kaXJlY3RFdmFsIH0gZnJvbSBcIi4uL3NyYy91dGlscy9ldmFsXCI7IC8vIExpc3RlbiBmb3IgbWVzc2FnZXMgZnJvbSBwYXJlbnQgZnJhbWUuIFRoaXMgZmlsZSBpcyBvbmx5IGFkZGVkIHdoZW4gdGhlXG4vLyBzaGlueS50ZXN0bW9kZSBvcHRpb24gaXMgVFJVRS5cblxud2luZG93LmFkZEV2ZW50TGlzdGVuZXIoXCJtZXNzYWdlXCIsIGZ1bmN0aW9uIChlKSB7XG4gIHZhciBtZXNzYWdlID0gZS5kYXRhO1xuICBpZiAobWVzc2FnZS5jb2RlKSBpbmRpcmVjdEV2YWwobWVzc2FnZS5jb2RlKTtcbn0pOyJdLAogICJtYXBwaW5ncyI6ICI7WUFrQkEsR0FBSSxHQUFlLEtDZm5CLE9BQU8saUJBQWlCLFVBQVcsU0FBVSxFQUFHLENBQzlDLEdBQUksR0FBVSxFQUFFLEtBQ2hCLEFBQUksRUFBUSxNQUFNLEVBQWEsRUFBUSIsCiAgIm5hbWVzIjogW10KfQo=

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

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

View File

@@ -1,81 +1,94 @@
{
"name": "@types/shiny",
"private": true,
"version": "1.6.0",
"homepage": "https://shiny.rstudio.com",
"repository": "github:rstudio/shiny",
"name": "@types/shiny",
"version": "1.6.0-alpha.9021",
"license": "GPL-3.0-only",
"main": "",
"types": "src_d/shiny/index.d.ts",
"browser": "",
"types": "srcts/types/shiny/index.d.ts",
"files": [
"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": "^3.5.5",
"@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/bootstrap": "3.4.0",
"@types/bootstrap-datepicker": "0.0.14",
"@types/datatables.net": "^1.10.19",
"@types/ion-rangeslider": "2.3.0",
"@types/highlightjs": "^9.12.1",
"@types/jest": "^26.0.23",
"@types/jquery": "^3.5.5",
"@types/jqueryui": "1.12.15",
"@types/lodash": "^4.14.170",
"@types/node": "^15.6.1",
"@types/selectize": "0.12.34",
"@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": "0.2.3",
"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",
"parcel": "^1.12.4",
"parcel-bundler": "^1.12.5",
"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"
"typescript": "~4.1.5",
"util-inspect": "https://github.com/deecewan/browser-util-inspect#c0b4350df4378ffd743e8c36dd3898ce3992823e"
},
"scripts": {
"watch": "yarn run build_shiny --watch",
"build": "yarn run build_shiny && yarn run bundle_external_libs",
"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": "node esbuild.config.mjs",
"circular_dep_image": "madge --circular --extensions ts --image madge.svg src",
"bundle_external_libs": "node esbuild.external_libs.mjs",
"bundle_shiny_parcel2": "parcel build -d ../inst/www/shared --no-minify -o shiny.js src/index.ts",
"watch_parcel2": "yarn run checks && parcel run -d ../inst/www/shared -o shiny.js srcjs/index.ts",
"replace_shiny_version2": "replace --silent '\"[^\"]+\"; // @VERSION@' \"\\\"`node -e 'console.log(require(\"readcontrol\").readSync(\"../DESCRIPTION\").version)'`\\\"; // @VERSION@\" src/shiny.ts",
"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",
"lint": "eslint --fix --ext .ts src",
"typescript-check": "tsc -p tsconfig.json",
"type-check": "type-coverage -p tsconfig.json --detail --at-least 85",
"import-check": "madge --circular --extensions ts src",
"checks": "yarn run lint && yarn run typescript-check && yarn run type-check && yarn run import-check"
},
"dependencies": {
"core-js": "^3.13.0",
"lodash": "^4.17.21",
"util-inspect": "https://github.com/deecewan/browser-util-inspect#c0b4350df4378ffd743e8c36dd3898ce3992823e"
"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,53 +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
"@typescript-eslint/explicit-module-boundary-types":
- error
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

10
srcts/.gitignore vendored
View File

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

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
@@ -141,13 +194,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 +211,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

@@ -85,18 +85,23 @@
* √ 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
* Maybe not. Might be good to keep for a cycle.
* _Uglify_ js files (like in previous Gruntfile.js)
* datepicker
* ionrangeslider
* selectize
* Documentation
* Check out
* https://www.typescriptlang.org/docs/handbook/jsdoc-supported-types.html#this
* https://github.com/gajus/eslint-plugin-jsdoc
* Looks like it can handle markdown
# 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,67 +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";
async function buildFile(
fileName,
extraOpts = {},
strSize = "shiny.min.js".length
) {
let watch = process.argv.length >= 3 && process.argv[2] == "--watch";
let incremental = false;
let printName = fileName;
while (printName.length < strSize) {
printName = printName + " ";
}
const onRebuild = function (error, result) {
if (error) {
console.error(printName, "watch build failed:\n", error);
} else {
console.log("√ -", printName, "-", new Date().toJSON());
}
return;
};
if (watch) {
incremental = true;
watch = {
onRebuild: onRebuild,
};
}
const outdir = "../inst/www/shared/";
console.log("Building " + fileName);
await esbuild.build({
outfile: outdir + fileName,
entryPoints: ["src/index.ts"],
bundle: true,
incremental: incremental,
watch: watch,
plugins: [
globalsPlugin({
jquery: "window.jQuery",
//// Loaded dynamically. MUST use `window.strftime` within code
// strftime: "window.strftime",
}),
babel(),
],
target: "es5",
sourcemap: true,
define: {
"process.env.SHINY_VERSION": `"${
readcontrol.readSync("../DESCRIPTION").version
}"`,
},
...extraOpts,
});
onRebuild();
}
buildFile("shiny.js");
buildFile("shiny.min.js", { minify: true });

View File

@@ -1,67 +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 opts = {
bundle: false,
watch: false,
target: "es5",
sourcemap: false,
};
console.log("Building datepicker");
const localeFiles = readdirSync(instdir + "www/shared/datepicker/js/locales/");
let requireFiles = localeFiles
.map(function (filename) {
return `require("./locales/${filename}");`;
})
.join("\n");
let tmpfile = instdir + "www/shared/datepicker/js/temp.js";
writeFileSync(
tmpfile,
`require("./bootstrap-datepicker.js");
${requireFiles}`
);
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,18 @@
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,314 @@
import type { Shiny } from "../src/shiny";
type ShowcaseSrcMessage = {
srcref: Array<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 ((window as any).Shiny) {
((window as any).Shiny as 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,9 @@
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);
});

11
srcts/patch/README.md Normal file
View File

@@ -0,0 +1,11 @@
# `yarn` Patch files
* `types-jquery.patch`
* Do not export `$` as a globally available variable. When developing TS code, I like to have full control over the variables. It is good to have a record of where everything comes from. Shiny can not use the latest jQuery loaded onto the page and needs to use a scoped `jQuery` variable. In the end, we can shim the `jquery` import to be `window.jQuery`
* `yarn_pnp.patch`
* This file is currently not used and is outdated.
* This provides a good game plan on how to use PnP with Yarn once esbuild can easily be integrated with Yarn PnP.
* Using PnP removes the `node_modules` folder, but adds a zip of each package. I **do not** like Yarn's suggestion to commit these zip files to support their [Zero Installs](https://next.yarnpkg.com/features/zero-installs) philosophy.
* Reference:
* https://next.yarnpkg.com/features/pnp
* https://yarnpkg.com/api/modules/esbuild_plugin_pnp.html

View File

@@ -1,5 +1,5 @@
import $ from "jquery";
import { InputBinding } from "./InputBinding";
import { InputBinding } from "./inputBinding";
import { hasOwnProperty } from "../../utils";

View File

@@ -1,5 +1,5 @@
import $ from "jquery";
import { InputBinding } from "./InputBinding";
import { InputBinding } from "./inputBinding";
import { hasOwnProperty } from "../../utils";
type CheckedHTMLElement = HTMLInputElement;

View File

@@ -1,8 +1,8 @@
import $ from "jquery";
import { InputBinding } from "./InputBinding";
import { InputBinding } from "./inputBinding";
import { $escape, hasOwnProperty, updateLabel } from "../../utils";
import { CheckedHTMLElement } from "./checkbox";
import type { CheckedHTMLElement } from "./checkbox";
type CheckboxGroupHTMLElement = CheckedHTMLElement;
type ValueLabelObject = {
@@ -17,12 +17,38 @@ type CheckboxGroupReceiveMessageData = {
type CheckboxGroupValue = CheckboxGroupHTMLElement["value"];
// Get the DOM element that contains the top-level label
function getLabelNode(el: CheckboxGroupHTMLElement): JQuery<HTMLElement> {
return $(el).find('label[for="' + $escape(el.id) + '"]');
}
// Given an input DOM object, get the associated label. Handles labels
// that wrap the input as well as labels associated with 'for' attribute.
function getLabel(obj: HTMLElement): string | null {
// If <label><input /><span>label text</span></label>
if ((obj.parentNode as HTMLElement).tagName === "LABEL") {
return $(obj.parentNode).find("span").text().trim();
}
return null;
}
// Given an input DOM object, set the associated label. Handles labels
// that wrap the input as well as labels associated with 'for' attribute.
// eslint-disable-next-line @typescript-eslint/no-unused-vars
function setLabel(obj: HTMLElement, value: string): null {
// If <label><input /><span>label text</span></label>
if ((obj.parentNode as HTMLElement).tagName === "LABEL") {
$(obj.parentNode).find("span").text(value);
}
return null;
}
class CheckboxGroupInputBinding extends InputBinding {
find(scope: HTMLElement): JQuery<HTMLElement> {
return $(scope).find(".shiny-input-checkboxgroup");
}
getValue(el: CheckboxGroupHTMLElement): Array<CheckboxGroupValue> {
getValue(el: CheckboxGroupHTMLElement): CheckboxGroupValue[] {
// Select the checkbox objects that have name equal to the grouping div's id
const $objs = $('input:checkbox[name="' + $escape(el.id) + '"]:checked');
const values = new Array($objs.length);
@@ -32,7 +58,7 @@ class CheckboxGroupInputBinding extends InputBinding {
}
return values;
}
setValue(el: HTMLElement, value: Array<string> | string): void {
setValue(el: HTMLElement, value: string[] | string): void {
// Clear all checkboxes
$('input:checkbox[name="' + $escape(el.id) + '"]').prop("checked", false);
@@ -61,7 +87,7 @@ class CheckboxGroupInputBinding extends InputBinding {
getState(el: CheckboxGroupHTMLElement): {
label: string;
value: ReturnType<CheckboxGroupInputBinding["getValue"]>;
options: Array<ValueLabelObject>;
options: ValueLabelObject[];
} {
const $objs = $(
'input:checkbox[name="' + $escape(el.id) + '"]'
@@ -71,11 +97,11 @@ class CheckboxGroupInputBinding extends InputBinding {
const options = new Array($objs.length);
for (let i = 0; i < options.length; i++) {
options[i] = { value: $objs[i].value, label: this._getLabel($objs[i]) };
options[i] = { value: $objs[i].value, label: getLabel($objs[i]) };
}
return {
label: this._getLabelNode(el).text(),
label: getLabelNode(el).text(),
value: this.getValue(el),
options: options,
};
@@ -97,7 +123,7 @@ class CheckboxGroupInputBinding extends InputBinding {
if (hasOwnProperty(data, "value")) this.setValue(el, data.value);
updateLabel(data.label, this._getLabelNode(el));
updateLabel(data.label, getLabelNode(el));
$(el).trigger("change");
}
@@ -112,31 +138,6 @@ class CheckboxGroupInputBinding extends InputBinding {
unsubscribe(el: CheckboxGroupHTMLElement): void {
$(el).off(".checkboxGroupInputBinding");
}
// Get the DOM element that contains the top-level label
_getLabelNode(el: CheckboxGroupHTMLElement): JQuery<HTMLElement> {
return $(el).find('label[for="' + $escape(el.id) + '"]');
}
// Given an input DOM object, get the associated label. Handles labels
// that wrap the input as well as labels associated with 'for' attribute.
_getLabel(obj: HTMLElement): string | null {
// If <label><input /><span>label text</span></label>
if ((obj.parentNode as HTMLElement).tagName === "LABEL") {
return $(obj.parentNode).find("span").text().trim();
}
return null;
}
// Given an input DOM object, set the associated label. Handles labels
// that wrap the input as well as labels associated with 'for' attribute.
_setLabel(obj: HTMLElement, value: string): null {
// If <label><input /><span>label text</span></label>
if ((obj.parentNode as HTMLElement).tagName === "LABEL") {
$(obj.parentNode).find("span").text(value);
}
return null;
}
}
export { CheckboxGroupInputBinding };

View File

@@ -1,5 +1,5 @@
import $ from "jquery";
import { InputBinding } from "./InputBinding";
import { InputBinding } from "./inputBinding";
import {
formatDateUTC,
updateLabel,
@@ -17,7 +17,7 @@ declare global {
bsDatepicker(methodName: "getStartDate"): Date | -1e9999;
bsDatepicker(methodName: "getEndDate"): Date | 1e9999;
bsDatepicker(methodName: string): void;
bsDatepicker(methodName: string, params: null | Date): void;
bsDatepicker(methodName: string, params: Date | null): void;
}
}
@@ -100,13 +100,13 @@ class DateInputBindingBase extends InputBinding {
this._setMax($input[0], $input.data("max-date"));
}
}
_getLabelNode(el: HTMLElement): JQuery<HTMLElement> {
protected _getLabelNode(el: HTMLElement): JQuery<HTMLElement> {
return $(el).find('label[for="' + $escape(el.id) + '"]');
}
// Given a format object from a date picker, return a string
_formatToString(format: {
parts: Array<string>;
separators: Array<string>;
protected _formatToString(format: {
parts: string[];
separators: string[];
}): string {
// Format object has structure like:
// { parts: ['mm', 'dd', 'yy'], separators: ['', '/', '/' ,''] }
@@ -122,7 +122,7 @@ class DateInputBindingBase extends InputBinding {
}
// Given an unambiguous date string or a Date object, set the min (start) date.
// null will unset. undefined will result in no change,
_setMin(el: HTMLElement, date: Date | undefined | null): void {
protected _setMin(el: HTMLElement, date: Date | null | undefined): void {
if (date === undefined) return;
if (date === null) {
$(el).bsDatepicker("setStartDate", null);
@@ -145,7 +145,7 @@ class DateInputBindingBase extends InputBinding {
// Note that there's no `setUTCStartDate`, so we need to convert this Date.
// It starts at 00:00 UTC, and we convert it to 00:00 in local time, which
// is what's needed for `setStartDate`.
$(el).bsDatepicker("setStartDate", this._UTCDateAsLocal(date));
$(el).bsDatepicker("setStartDate", this._utcDateAsLocal(date));
// If the new min is greater than the current date, unset the current date.
if (date && curValue && date.getTime() > curValue.getTime()) {
@@ -161,7 +161,7 @@ class DateInputBindingBase extends InputBinding {
}
// Given an unambiguous date string or a Date object, set the max (end) date
// null will unset.
_setMax(el: HTMLElement, date: Date): void {
protected _setMax(el: HTMLElement, date: Date): void {
if (date === undefined) return;
if (date === null) {
$(el).bsDatepicker("setEndDate", null);
@@ -180,7 +180,7 @@ class DateInputBindingBase extends InputBinding {
// Workaround for same issue as in _setMin.
const curValue = $(el).bsDatepicker("getUTCDate");
$(el).bsDatepicker("setEndDate", this._UTCDateAsLocal(date));
$(el).bsDatepicker("setEndDate", this._utcDateAsLocal(date));
// If the new min is greater than the current date, unset the current date.
if (date && curValue && date.getTime() < curValue.getTime()) {
@@ -192,7 +192,7 @@ class DateInputBindingBase extends InputBinding {
// Given a date string of format yyyy-mm-dd, return a Date object with
// that date at 12AM UTC.
// If date is a Date object, return it unchanged.
_newDate(date: Date | string | never): Date | null {
protected _newDate(date: Date | never | string): Date | null {
if (date instanceof Date) return date;
if (!date) return null;
@@ -207,7 +207,7 @@ class DateInputBindingBase extends InputBinding {
}
// A Date can have any time during a day; this will return a new Date object
// set to 00:00 in UTC.
_floorDateTime(date: Date): Date {
protected _floorDateTime(date: Date): Date {
date = new Date(date.getTime());
date.setUTCHours(0, 0, 0, 0);
return date;
@@ -216,13 +216,13 @@ class DateInputBindingBase extends InputBinding {
// in UTC. For example, if input date is 2013-02-01 23:00:00 GMT-0600 (CST),
// output will be 2013-02-01 23:00:00 UTC. Note that the JS console may
// print this in local time, as "Sat Feb 02 2013 05:00:00 GMT-0600 (CST)".
_dateAsUTC(date: Date): Date {
protected _dateAsUTC(date: Date): Date {
return new Date(date.getTime() - date.getTimezoneOffset() * 60000);
}
// The inverse of _dateAsUTC. This is needed to adjust time zones because
// some bootstrap-datepicker methods only take local dates as input, and not
// UTC.
_UTCDateAsLocal(date: Date): Date {
protected _utcDateAsLocal(date: Date): Date {
return new Date(date.getTime() + date.getTimezoneOffset() * 60000);
}
}
@@ -257,7 +257,7 @@ class DateInputBinding extends DateInputBindingBase {
getState(el: HTMLElement): {
label: string;
value: string | null;
valueString: string | number | string[];
valueString: string[] | number | string;
min: string | null;
max: string | null;
language: string | null;

View File

@@ -15,6 +15,9 @@ type DateRangeReceiveMessageData = {
value?: { start?: Date; end?: Date };
};
function getLabelNode(el: HTMLElement): JQuery<HTMLElement> {
return $(el).find('label[for="' + $escape(el.id) + '"]');
}
class DateRangeInputBinding extends DateInputBindingBase {
find(scope: HTMLElement): JQuery<HTMLElement> {
return $(scope).find(".shiny-date-range-input");
@@ -92,7 +95,7 @@ class DateRangeInputBinding extends DateInputBindingBase {
else if (startview === 0) startview = "month";
return {
label: this._getLabelNode(el).text(),
label: getLabelNode(el).text(),
value: this.getValue(el),
valueString: [$startinput.val() as string, $endinput.val() as string],
min: minStr,
@@ -109,7 +112,7 @@ class DateRangeInputBinding extends DateInputBindingBase {
const $startinput = $inputs.eq(0);
const $endinput = $inputs.eq(1);
updateLabel(data.label, this._getLabelNode(el));
updateLabel(data.label, getLabelNode(el));
if (hasOwnProperty(data, "min")) {
this._setMin($startinput[0], data.min);
@@ -176,9 +179,6 @@ class DateRangeInputBinding extends DateInputBindingBase {
unsubscribe(el: HTMLElement): void {
$(el).off(".dateRangeInputBinding");
}
_getLabelNode(el: HTMLElement): JQuery<HTMLElement> {
return $(el).find('label[for="' + $escape(el.id) + '"]');
}
}
export { DateRangeInputBinding };

View File

@@ -1,12 +1,122 @@
import $ from "jquery";
import { InputBinding } from "./InputBinding";
import { FileUploader } from "../../file/FileProcessor";
import { InputBinding } from "./inputBinding";
import { FileUploader } from "../../file/fileProcessor";
import { shinyShinyApp } from "../../shiny/initedMethods";
const _ZoneClass = {
ACTIVE: "shiny-file-input-active",
OVER: "shiny-file-input-over",
};
const zoneActive = "shiny-file-input-active";
const zoneOver = "shiny-file-input-over";
function zoneOf(el: HTMLElement | JQuery<HTMLElement>): JQuery<HTMLElement> {
return $(el).closest("div.input-group");
}
// This function makes it possible to attach listeners to the dragenter,
// dragleave, and drop events of a single element with children. It's not
// intuitive to do directly because outer elements fire "dragleave" events
// both when the drag leaves the element and when the drag enters a child. To
// make it easier, we maintain a count of the elements being dragged across
// and trigger 3 new types of event:
//
// 1. draghover:enter - When a drag enters el and any of its children.
// 2. draghover:leave - When the drag leaves el and all of its children.
// 3. draghover:drop - When an item is dropped on el or any of its children.
function enableDraghover(el: JQuery<HTMLElement>): JQuery<HTMLElement> {
const $el = $(el);
let childCounter = 0;
$el.on({
"dragenter.draghover": (e) => {
if (childCounter++ === 0) {
$el.trigger("draghover:enter", e);
}
},
"dragleave.draghover": (e) => {
if (--childCounter === 0) {
$el.trigger("draghover:leave", e);
}
if (childCounter < 0) {
console.error("draghover childCounter is negative somehow");
}
},
"dragover.draghover": (e) => {
e.preventDefault();
},
"drop.draghover": (e) => {
childCounter = 0;
$el.trigger("draghover:drop", e);
e.preventDefault();
},
});
return $el;
}
function disableDraghover(el: JQuery<HTMLElement>): JQuery<HTMLElement> {
return $(el).off(".draghover");
}
function enableDocumentEvents(): void {
const $doc = $("html");
enableDraghover($doc).on({
"draghover:enter.draghover":
// e: Event
() => {
zoneOf($fileInputs).addClass(zoneActive);
},
"draghover:leave.draghover":
// e: Event
() => {
zoneOf($fileInputs).removeClass(zoneActive);
},
"draghover:drop.draghover":
// e: Event
() => {
zoneOf($fileInputs).removeClass(zoneOver).removeClass(zoneActive);
},
});
}
function disableDocumentEvents(): void {
const $doc = $("html");
$doc.off(".draghover");
disableDraghover($doc);
}
function canSetFiles(fileList: FileList): boolean {
const testEl = document.createElement("input");
testEl.type = "file";
try {
testEl.files = fileList;
} catch (e) {
return false;
}
return true;
}
function handleDrop(e: JQuery.DragEventBase, el: HTMLInputElement): void {
const files = e.originalEvent.dataTransfer.files,
$el = $(el);
if (files === undefined || files === null) {
// 1. The FileList object isn't supported by this browser, and
// there's nothing else we can try. (< IE 10)
console.log(
"Dropping files is not supported on this browser. (no FileList)"
);
} else if (!canSetFiles(files)) {
// 2. The browser doesn't support assigning a type=file input's .files
// property, but we do have a FileList to work with. (IE10+/Edge)
$el.val("");
uploadDroppedFilesIE10Plus(el, files);
} else {
// 3. The browser supports FileList and input.files assignment.
// (Chrome, Safari)
$el.val("");
el.files = e.originalEvent.dataTransfer.files;
// Recent versions of Firefox (57+, or "Quantum" and beyond) don't seem to
// automatically trigger a change event, so we trigger one manually here.
// On browsers that do trigger change, this operation appears to be
// idempotent, as el.files doesn't change between events.
$el.trigger("change");
}
}
// NOTE On Safari, at least version 10.1.2, *if the developer console is open*,
// setting the input's value will behave strangely because of a Safari bug. The
@@ -133,158 +243,47 @@ class FileInputBinding extends InputBinding {
return "shiny.file";
el;
}
_zoneOf(el: HTMLElement | JQuery<HTMLElement>): JQuery<HTMLElement> {
return $(el).closest("div.input-group");
}
// This function makes it possible to attach listeners to the dragenter,
// dragleave, and drop events of a single element with children. It's not
// intuitive to do directly because outer elements fire "dragleave" events
// both when the drag leaves the element and when the drag enters a child. To
// make it easier, we maintain a count of the elements being dragged across
// and trigger 3 new types of event:
//
// 1. draghover:enter - When a drag enters el and any of its children.
// 2. draghover:leave - When the drag leaves el and all of its children.
// 3. draghover:drop - When an item is dropped on el or any of its children.
_enableDraghover(el: JQuery<HTMLElement>): JQuery<HTMLElement> {
const $el = $(el);
let childCounter = 0;
$el.on({
"dragenter.draghover": (e) => {
if (childCounter++ === 0) {
$el.trigger("draghover:enter", e);
}
},
"dragleave.draghover": (e) => {
if (--childCounter === 0) {
$el.trigger("draghover:leave", e);
}
if (childCounter < 0) {
console.error("draghover childCounter is negative somehow");
}
},
"dragover.draghover": (e) => {
e.preventDefault();
},
"drop.draghover": (e) => {
childCounter = 0;
$el.trigger("draghover:drop", e);
e.preventDefault();
},
});
return $el;
}
_disableDraghover(el: JQuery<HTMLElement>): JQuery<HTMLElement> {
return $(el).off(".draghover");
}
_enableDocumentEvents(): void {
const $doc = $("html"),
{ ACTIVE, OVER } = _ZoneClass;
this._enableDraghover($doc).on({
"draghover:enter.draghover":
// e: Event
() => {
this._zoneOf($fileInputs).addClass(ACTIVE);
},
"draghover:leave.draghover":
// e: Event
() => {
this._zoneOf($fileInputs).removeClass(ACTIVE);
},
"draghover:drop.draghover":
// e: Event
() => {
this._zoneOf($fileInputs).removeClass(OVER).removeClass(ACTIVE);
},
});
}
_disableDocumentEvents(): void {
const $doc = $("html");
$doc.off(".draghover");
this._disableDraghover($doc);
}
_canSetFiles(fileList: FileList): boolean {
const testEl = document.createElement("input");
testEl.type = "file";
try {
testEl.files = fileList;
} catch (e) {
return false;
}
return true;
}
_handleDrop(e: JQuery.DragEventBase, el: HTMLInputElement): void {
const files = e.originalEvent.dataTransfer.files,
$el = $(el);
if (files === undefined || files === null) {
// 1. The FileList object isn't supported by this browser, and
// there's nothing else we can try. (< IE 10)
console.log(
"Dropping files is not supported on this browser. (no FileList)"
);
} else if (!this._canSetFiles(files)) {
// 2. The browser doesn't support assigning a type=file input's .files
// property, but we do have a FileList to work with. (IE10+/Edge)
$el.val("");
uploadDroppedFilesIE10Plus(el, files);
} else {
// 3. The browser supports FileList and input.files assignment.
// (Chrome, Safari)
$el.val("");
el.files = e.originalEvent.dataTransfer.files;
// Recent versions of Firefox (57+, or "Quantum" and beyond) don't seem to
// automatically trigger a change event, so we trigger one manually here.
// On browsers that do trigger change, this operation appears to be
// idempotent, as el.files doesn't change between events.
$el.trigger("change");
}
}
subscribe(el: HTMLInputElement, callback: (x: boolean) => void): void {
callback;
$(el).on("change.fileInputBinding", uploadFiles);
// Here we try to set up the necessary events for Drag and Drop ("DnD").
if ($fileInputs.length === 0) this._enableDocumentEvents();
if ($fileInputs.length === 0) enableDocumentEvents();
$fileInputs = $fileInputs.add(el);
const $zone = this._zoneOf(el),
{ OVER } = _ZoneClass;
const $zone = zoneOf(el);
this._enableDraghover($zone).on({
enableDraghover($zone).on({
"draghover:enter.draghover": (e) => {
e;
$zone.addClass(OVER);
$zone.addClass(zoneOver);
},
"draghover:leave.draghover": (e) => {
$zone.removeClass(OVER);
$zone.removeClass(zoneOver);
// Prevent this event from bubbling to the document handler,
// which would deactivate all zones.
e.stopPropagation();
},
"draghover:drop.draghover": (e, dropEvent) => {
e;
this._handleDrop(dropEvent, el);
handleDrop(dropEvent, el);
},
});
}
unsubscribe(el: HTMLElement): void {
const $el = $(el),
$zone = this._zoneOf(el);
$zone = zoneOf(el);
$zone.removeClass(_ZoneClass.OVER).removeClass(_ZoneClass.ACTIVE);
$zone.removeClass(zoneOver).removeClass(zoneActive);
this._disableDraghover($zone);
disableDraghover($zone);
$el.off(".fileInputBinding");
$zone.off(".draghover");
// Remove el from list of inputs and (maybe) clean up global event handlers.
$fileInputs = $fileInputs.not(el);
if ($fileInputs.length === 0) this._disableDocumentEvents();
if ($fileInputs.length === 0) disableDocumentEvents();
}
}

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