mirror of
https://github.com/rstudio/shiny.git
synced 2026-01-10 23:48:01 -05:00
Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c796ad1694 | ||
|
|
244fdc72bc | ||
|
|
b9d163a71d | ||
|
|
61ee467dee | ||
|
|
7c0829d553 | ||
|
|
68eb4c6965 | ||
|
|
6d4015f61b | ||
|
|
d89513b7e0 | ||
|
|
a159594a45 | ||
|
|
78c62ad819 |
@@ -20,6 +20,7 @@ plugins:
|
||||
- '@typescript-eslint'
|
||||
- prettier
|
||||
- jest-dom
|
||||
- unicorn
|
||||
rules:
|
||||
"@typescript-eslint/explicit-function-return-type":
|
||||
- off
|
||||
@@ -27,8 +28,7 @@ rules:
|
||||
- off
|
||||
"@typescript-eslint/explicit-module-boundary-types":
|
||||
- error
|
||||
camelcase:
|
||||
- error
|
||||
|
||||
default-case:
|
||||
- error
|
||||
indent:
|
||||
@@ -51,3 +51,58 @@ rules:
|
||||
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
2
.gitattributes
vendored
@@ -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
|
||||
|
||||
24
.github/workflows/rituals.yaml
vendored
24
.github/workflows/rituals.yaml
vendored
@@ -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 ./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"
|
||||
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
NEWS.md
2
NEWS.md
@@ -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)
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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")) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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=
|
||||
|
||||
@@ -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
@@ -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
3
inst/www/shared/shiny.min.css
vendored
3
inst/www/shared/shiny.min.css
vendored
File diff suppressed because one or more lines are too long
3
inst/www/shared/shiny.min.js
vendored
3
inst/www/shared/shiny.min.js
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -11,7 +11,7 @@ insertTab(
|
||||
inputId,
|
||||
tab,
|
||||
target = NULL,
|
||||
position = c("before", "after"),
|
||||
position = c("after", "before"),
|
||||
select = FALSE,
|
||||
session = getDefaultReactiveDomain()
|
||||
)
|
||||
|
||||
66
package.json
66
package.json
@@ -1,76 +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": "",
|
||||
"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",
|
||||
"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 srcts/build/shiny.mjs",
|
||||
"circular_dep_image": "madge --circular --extensions ts --image madge.svg srcts/src",
|
||||
"bundle_external_libs": "node srcts/build/external_libs.mjs",
|
||||
"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 srcts/src",
|
||||
"typescript-check": "tsc -p tsconfig.json",
|
||||
"type-check": "type-coverage -p tsconfig.json --detail --at-least 85",
|
||||
"import-check": "madge --circular --extensions ts srcts/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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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`
|
||||
|
||||
@@ -92,11 +92,16 @@
|
||||
* √ 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
80
srcts/build/_build.ts
Normal 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 };
|
||||
@@ -1,72 +0,0 @@
|
||||
// This build script must be executed from the root repo directory via
|
||||
// ```
|
||||
// yarn build
|
||||
// ```
|
||||
|
||||
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,
|
||||
});
|
||||
57
srcts/build/external_libs.ts
Normal file
57
srcts/build/external_libs.ts
Normal 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
54
srcts/build/extras.ts
Normal 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",
|
||||
});
|
||||
@@ -1,72 +0,0 @@
|
||||
// This build script must be executed from the root repo directory via
|
||||
// ```
|
||||
// yarn build
|
||||
// ```
|
||||
|
||||
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: ["srcts/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 });
|
||||
38
srcts/build/shiny.ts
Normal file
38
srcts/build/shiny.ts
Normal 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,
|
||||
});
|
||||
18
srcts/extras/shiny-autoreload.ts
Normal file
18
srcts/extras/shiny-autoreload.ts
Normal 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 {};
|
||||
86
srcts/extras/shiny-showcase.scss
Normal file
86
srcts/extras/shiny-showcase.scss
Normal 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;
|
||||
}
|
||||
}
|
||||
314
srcts/extras/shiny-showcase.ts
Normal file
314
srcts/extras/shiny-showcase.ts
Normal 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 {};
|
||||
9
srcts/extras/shiny-testmode.ts
Normal file
9
srcts/extras/shiny-testmode.ts
Normal 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);
|
||||
});
|
||||
17
srcts/old/extras/shiny-autoreload.js
Normal file
17
srcts/old/extras/shiny-autoreload.js
Normal 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()
|
||||
}
|
||||
}
|
||||
})();
|
||||
87
srcts/old/extras/shiny-showcase.css
Normal file
87
srcts/old/extras/shiny-showcase.css
Normal 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;
|
||||
}
|
||||
272
srcts/old/extras/shiny-showcase.js
Normal file
272
srcts/old/extras/shiny-showcase.js
Normal 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();
|
||||
})();
|
||||
8
srcts/old/extras/shiny-testmode.js
Normal file
8
srcts/old/extras/shiny-testmode.js
Normal 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
11
srcts/patch/README.md
Normal 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
|
||||
@@ -1,5 +1,5 @@
|
||||
import $ from "jquery";
|
||||
import { InputBinding } from "./InputBinding";
|
||||
import { InputBinding } from "./inputBinding";
|
||||
|
||||
import { hasOwnProperty } from "../../utils";
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import $ from "jquery";
|
||||
import { InputBinding } from "./InputBinding";
|
||||
import { InputBinding } from "./inputBinding";
|
||||
import { hasOwnProperty } from "../../utils";
|
||||
|
||||
type CheckedHTMLElement = HTMLInputElement;
|
||||
|
||||
@@ -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 };
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 };
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { BindingRegistry } from "../registry";
|
||||
|
||||
import { InputBinding } from "./InputBinding";
|
||||
import { InputBinding } from "./inputBinding";
|
||||
|
||||
import { CheckboxInputBinding } from "./checkbox";
|
||||
import { CheckboxGroupInputBinding } from "./checkboxgroup";
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import { RatePolicyModes } from "../../inputPolicies/inputRateDecorator";
|
||||
import { bindScope } from "../../shiny/bind";
|
||||
import type { RatePolicyModes } from "../../inputPolicies/inputRateDecorator";
|
||||
import type { BindScope } from "../../shiny/bind";
|
||||
|
||||
class InputBinding {
|
||||
name: string;
|
||||
|
||||
// Returns a jQuery object or element array that contains the
|
||||
// descendants of scope that match this binding
|
||||
find(scope: bindScope): JQuery<HTMLElement> {
|
||||
find(scope: BindScope): JQuery<HTMLElement> {
|
||||
throw "Not implemented";
|
||||
// add so that typescript isn't mad about an unused var
|
||||
scope;
|
||||
@@ -12,12 +12,18 @@ type NumberReceiveMessageData = {
|
||||
step?: string | null;
|
||||
};
|
||||
|
||||
function getLabelNode(el: NumberHTMLElement): JQuery<HTMLElement> {
|
||||
return $(el)
|
||||
.parent()
|
||||
.find('label[for="' + $escape(el.id) + '"]');
|
||||
}
|
||||
|
||||
class NumberInputBinding extends TextInputBindingBase {
|
||||
find(scope: HTMLElement): JQuery<HTMLElement> {
|
||||
return $(scope).find('input[type="number"]');
|
||||
}
|
||||
|
||||
getValue(el: NumberHTMLElement): string | number | string[] {
|
||||
getValue(el: NumberHTMLElement): string[] | number | string {
|
||||
const numberVal = $(el).val();
|
||||
|
||||
if (typeof numberVal == "string") {
|
||||
@@ -48,7 +54,7 @@ class NumberInputBinding extends TextInputBindingBase {
|
||||
if (hasOwnProperty(data, "max")) el.max = data.max;
|
||||
if (hasOwnProperty(data, "step")) el.step = data.step;
|
||||
|
||||
updateLabel(data.label, this._getLabelNode(el));
|
||||
updateLabel(data.label, getLabelNode(el));
|
||||
|
||||
$(el).trigger("change");
|
||||
}
|
||||
@@ -61,19 +67,13 @@ class NumberInputBinding extends TextInputBindingBase {
|
||||
step: number;
|
||||
} {
|
||||
return {
|
||||
label: this._getLabelNode(el).text(),
|
||||
label: getLabelNode(el).text(),
|
||||
value: this.getValue(el),
|
||||
min: Number(el.min),
|
||||
max: Number(el.max),
|
||||
step: Number(el.step),
|
||||
};
|
||||
}
|
||||
|
||||
_getLabelNode(el: NumberHTMLElement): JQuery<HTMLElement> {
|
||||
return $(el)
|
||||
.parent()
|
||||
.find('label[for="' + $escape(el.id) + '"]');
|
||||
}
|
||||
}
|
||||
|
||||
export { NumberInputBinding };
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import $ from "jquery";
|
||||
import { InputBinding } from "./InputBinding";
|
||||
import { InputBinding } from "./inputBinding";
|
||||
import { $escape, hasOwnProperty, updateLabel } from "../../utils";
|
||||
|
||||
type RadioHTMLElement = HTMLInputElement;
|
||||
@@ -11,15 +11,43 @@ type ValueLabelObject = {
|
||||
|
||||
type RadioReceiveMessageData = {
|
||||
value?: string;
|
||||
options?: Array<ValueLabelObject>;
|
||||
options?: ValueLabelObject[];
|
||||
label: string;
|
||||
};
|
||||
|
||||
// Get the DOM element that contains the top-level label
|
||||
function getLabelNode(el: RadioHTMLElement): JQuery<HTMLElement> {
|
||||
return $(el)
|
||||
.parent()
|
||||
.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 RadioInputBinding extends InputBinding {
|
||||
find(scope: HTMLElement): JQuery<HTMLElement> {
|
||||
return $(scope).find(".shiny-input-radiogroup");
|
||||
}
|
||||
getValue(el: RadioHTMLElement): string | number | string[] | null {
|
||||
getValue(el: RadioHTMLElement): string[] | number | string | null {
|
||||
// Select the radio objects that have name equal to the grouping div's id
|
||||
const checkedItems = $(
|
||||
'input:radio[name="' + $escape(el.id) + '"]:checked'
|
||||
@@ -49,8 +77,8 @@ class RadioInputBinding extends InputBinding {
|
||||
}
|
||||
getState(el: RadioHTMLElement): {
|
||||
label: string;
|
||||
value: string | number | string[];
|
||||
options: Array<ValueLabelObject>;
|
||||
value: string[] | number | string;
|
||||
options: ValueLabelObject[];
|
||||
} {
|
||||
const $objs = $(
|
||||
'input:radio[name="' + $escape(el.id) + '"]'
|
||||
@@ -60,11 +88,11 @@ class RadioInputBinding 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,
|
||||
};
|
||||
@@ -84,7 +112,7 @@ class RadioInputBinding 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");
|
||||
}
|
||||
@@ -96,32 +124,6 @@ class RadioInputBinding extends InputBinding {
|
||||
unsubscribe(el: RadioHTMLElement): void {
|
||||
$(el).off(".radioInputBinding");
|
||||
}
|
||||
// Get the DOM element that contains the top-level label
|
||||
_getLabelNode(el: RadioHTMLElement): JQuery<HTMLElement> {
|
||||
return $(el)
|
||||
.parent()
|
||||
.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 { RadioInputBinding };
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import $ from "jquery";
|
||||
import { InputBinding } from "./InputBinding";
|
||||
import { InputBinding } from "./inputBinding";
|
||||
import { $escape, hasOwnProperty, updateLabel } from "../../utils";
|
||||
import { indirectEval } from "../../utils/eval";
|
||||
|
||||
@@ -17,6 +17,27 @@ type SelectizeInfo = Selectize.IApi<string, unknown> & {
|
||||
settings: Selectize.IOptions<string, unknown>;
|
||||
};
|
||||
|
||||
function getLabelNode(el: SelectHTMLElement): JQuery<HTMLElement> {
|
||||
let escapedId = $escape(el.id);
|
||||
|
||||
if (isSelectize(el)) {
|
||||
escapedId += "-selectized";
|
||||
}
|
||||
return $(el)
|
||||
.parent()
|
||||
.parent()
|
||||
.find('label[for="' + escapedId + '"]');
|
||||
}
|
||||
// Return true if it's a selectize input, false if it's a regular select input.
|
||||
// eslint-disable-next-line camelcase
|
||||
function isSelectize(el: HTMLElement): boolean {
|
||||
const config = $(el)
|
||||
.parent()
|
||||
.find('script[data-for="' + $escape(el.id) + '"]');
|
||||
|
||||
return config.length > 0;
|
||||
}
|
||||
|
||||
class SelectInputBinding extends InputBinding {
|
||||
find(scope: HTMLElement): JQuery<HTMLElement> {
|
||||
return $(scope).find("select");
|
||||
@@ -37,11 +58,11 @@ class SelectInputBinding extends InputBinding {
|
||||
getId(el: SelectHTMLElement): string {
|
||||
return InputBinding.prototype.getId.call(this, el) || el.name;
|
||||
}
|
||||
getValue(el: HTMLElement): string | number | string[] {
|
||||
getValue(el: HTMLElement): string[] | number | string {
|
||||
return $(el).val();
|
||||
}
|
||||
setValue(el: SelectHTMLElement, value: string): void {
|
||||
if (!this._is_selectize(el)) {
|
||||
if (!isSelectize(el)) {
|
||||
$(el).val(value);
|
||||
} else {
|
||||
const selectize = this._selectize(el);
|
||||
@@ -53,7 +74,7 @@ class SelectInputBinding extends InputBinding {
|
||||
}
|
||||
getState(el: SelectHTMLElement): {
|
||||
label: JQuery<HTMLElement>;
|
||||
value: string | number | string[];
|
||||
value: string[] | number | string;
|
||||
options: Array<{ value: string; label: string }>;
|
||||
} {
|
||||
// Store options in an array of objects, each with with value and label
|
||||
@@ -70,7 +91,7 @@ class SelectInputBinding extends InputBinding {
|
||||
}
|
||||
|
||||
return {
|
||||
label: this._getLabelNode(el),
|
||||
label: getLabelNode(el),
|
||||
value: this.getValue(el),
|
||||
options: options,
|
||||
};
|
||||
@@ -161,7 +182,7 @@ class SelectInputBinding extends InputBinding {
|
||||
this.setValue(el, data.value);
|
||||
}
|
||||
|
||||
updateLabel(data.label, this._getLabelNode(el));
|
||||
updateLabel(data.label, getLabelNode(el));
|
||||
|
||||
$(el).trigger("change");
|
||||
}
|
||||
@@ -186,27 +207,7 @@ class SelectInputBinding extends InputBinding {
|
||||
initialize(el: SelectHTMLElement): void {
|
||||
this._selectize(el);
|
||||
}
|
||||
_getLabelNode(el: SelectHTMLElement): JQuery<HTMLElement> {
|
||||
let escapedId = $escape(el.id);
|
||||
|
||||
if (this._is_selectize(el)) {
|
||||
escapedId += "-selectized";
|
||||
}
|
||||
return $(el)
|
||||
.parent()
|
||||
.parent()
|
||||
.find('label[for="' + escapedId + '"]');
|
||||
}
|
||||
// Return true if it's a selectize input, false if it's a regular select input.
|
||||
// eslint-disable-next-line camelcase
|
||||
_is_selectize(el: HTMLElement): boolean {
|
||||
const config = $(el)
|
||||
.parent()
|
||||
.find('script[data-for="' + $escape(el.id) + '"]');
|
||||
|
||||
return config.length > 0;
|
||||
}
|
||||
_selectize(el: SelectHTMLElement, update = false): SelectizeInfo {
|
||||
protected _selectize(el: SelectHTMLElement, update = false): SelectizeInfo {
|
||||
if (!$.fn.selectize) return undefined;
|
||||
const $el = $(el);
|
||||
const config = $el
|
||||
@@ -221,7 +222,7 @@ class SelectInputBinding extends InputBinding {
|
||||
searchField: ["label"];
|
||||
onItemRemove?: (value: string) => void;
|
||||
onDropdownClose?: () => void;
|
||||
} & Record<string, unknown> = $.extend(
|
||||
} & { [key: string]: unknown } = $.extend(
|
||||
{
|
||||
labelField: "label",
|
||||
valueField: "value",
|
||||
|
||||
@@ -7,14 +7,16 @@ import {
|
||||
hasOwnProperty,
|
||||
} from "../../utils";
|
||||
|
||||
import { TextHTMLElement, TextInputBindingBase } from "./text";
|
||||
import type { TextHTMLElement } from "./text";
|
||||
import { TextInputBindingBase } from "./text";
|
||||
|
||||
// interface SliderHTMLElement extends NameValueHTMLElement {
|
||||
// checked?: any;
|
||||
// }
|
||||
|
||||
type TimeFormatter = (fmt: string, dt: Date) => string;
|
||||
type legacySliderType = {
|
||||
// Backward compatible code for old-style jsliders (Shiny <= 0.10.2.2),
|
||||
type LegacySlider = {
|
||||
canStepNext: () => boolean;
|
||||
stepNext: () => void;
|
||||
resetToStart: () => void;
|
||||
@@ -22,7 +24,7 @@ type legacySliderType = {
|
||||
|
||||
type SliderReceiveMessageData = {
|
||||
label: string;
|
||||
value?: Array<string | number> | string | number;
|
||||
value?: Array<number | string> | number | string;
|
||||
min?: number;
|
||||
max?: number;
|
||||
step?: number;
|
||||
@@ -32,14 +34,10 @@ type SliderReceiveMessageData = {
|
||||
// and could be needed after shiny has initialized.
|
||||
declare global {
|
||||
interface Window {
|
||||
strftime: {
|
||||
strftime: TimeFormatter & {
|
||||
utc: () => TimeFormatter;
|
||||
timezone: (timezone: string) => TimeFormatter;
|
||||
} & TimeFormatter;
|
||||
}
|
||||
interface JQuery {
|
||||
// Backward compatible code for old-style jsliders (Shiny <= 0.10.2.2),
|
||||
slider: () => legacySliderType;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,14 +48,14 @@ function forceIonSliderUpdate(slider) {
|
||||
else console.log("Couldn't force ion slider to update");
|
||||
}
|
||||
|
||||
type prettifyType = (num: number) => string;
|
||||
type Prettify = (num: number) => string;
|
||||
function getTypePrettifyer(
|
||||
dataType: string,
|
||||
timeFormat: string,
|
||||
timezone: string
|
||||
) {
|
||||
let timeFormatter: TimeFormatter;
|
||||
let prettify: prettifyType;
|
||||
let prettify: Prettify;
|
||||
|
||||
if (dataType === "date") {
|
||||
timeFormatter = window.strftime.utc();
|
||||
@@ -84,6 +82,17 @@ function getTypePrettifyer(
|
||||
return prettify;
|
||||
}
|
||||
|
||||
function getLabelNode(el: HTMLElement): JQuery<HTMLElement> {
|
||||
return $(el)
|
||||
.parent()
|
||||
.find('label[for="' + $escape(el.id) + '"]');
|
||||
}
|
||||
// Number of values; 1 for single slider, 2 for range slider
|
||||
function numValues(el: HTMLElement): 1 | 2 {
|
||||
if ($(el).data("ionRangeSlider").options.type === "double") return 2;
|
||||
else return 1;
|
||||
}
|
||||
|
||||
class SliderInputBinding extends TextInputBindingBase {
|
||||
find(scope: HTMLElement): JQuery<HTMLElement> {
|
||||
// Check if ionRangeSlider plugin is loaded
|
||||
@@ -127,7 +136,7 @@ class SliderInputBinding extends TextInputBindingBase {
|
||||
};
|
||||
}
|
||||
|
||||
if (this._numValues(el) === 2) {
|
||||
if (numValues(el) === 2) {
|
||||
return [convert(result.from), convert(result.to)];
|
||||
} else {
|
||||
return convert(result.from);
|
||||
@@ -142,7 +151,7 @@ class SliderInputBinding extends TextInputBindingBase {
|
||||
|
||||
$el.data("immediate", true);
|
||||
try {
|
||||
if (this._numValues(el) === 2 && value instanceof Array) {
|
||||
if (numValues(el) === 2 && value instanceof Array) {
|
||||
slider.update({ from: value[0], to: value[1] });
|
||||
} else {
|
||||
slider.update({ from: value });
|
||||
@@ -165,20 +174,20 @@ class SliderInputBinding extends TextInputBindingBase {
|
||||
const $el = $(el);
|
||||
const slider = $el.data("ionRangeSlider");
|
||||
const msg: {
|
||||
from?: string | number;
|
||||
to?: string | number;
|
||||
from?: number | string;
|
||||
to?: number | string;
|
||||
min?: number;
|
||||
max?: number;
|
||||
step?: number;
|
||||
prettify?: prettifyType;
|
||||
prettify?: Prettify;
|
||||
} = {};
|
||||
|
||||
if (hasOwnProperty(data, "value")) {
|
||||
if (this._numValues(el) === 2 && data.value instanceof Array) {
|
||||
if (numValues(el) === 2 && data.value instanceof Array) {
|
||||
msg.from = data.value[0];
|
||||
msg.to = data.value[1];
|
||||
} else {
|
||||
msg.from = data.value as string | number;
|
||||
msg.from = data.value as number | string;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -192,7 +201,7 @@ class SliderInputBinding extends TextInputBindingBase {
|
||||
}
|
||||
}
|
||||
|
||||
updateLabel(data.label, this._getLabelNode(el));
|
||||
updateLabel(data.label, getLabelNode(el));
|
||||
|
||||
// (maybe) update data elements
|
||||
const domElements = ["data-type", "time-format", "timezone"];
|
||||
@@ -245,16 +254,6 @@ class SliderInputBinding extends TextInputBindingBase {
|
||||
|
||||
$el.ionRangeSlider(opts);
|
||||
}
|
||||
_getLabelNode(el: HTMLElement): JQuery<HTMLElement> {
|
||||
return $(el)
|
||||
.parent()
|
||||
.find('label[for="' + $escape(el.id) + '"]');
|
||||
}
|
||||
// Number of values; 1 for single slider, 2 for range slider
|
||||
_numValues(el: HTMLElement): 1 | 2 {
|
||||
if ($(el).data("ionRangeSlider").options.type === "double") return 2;
|
||||
else return 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Format numbers for nicer output.
|
||||
@@ -303,7 +302,7 @@ $(document).on("click", ".slider-animate-button", function (evt: Event) {
|
||||
// Backward compatible code for old-style jsliders (Shiny <= 0.10.2.2),
|
||||
// and new-style ionsliders.
|
||||
if (target.hasClass("jslider")) {
|
||||
const slider = target.slider();
|
||||
const slider = target.slider() as unknown as LegacySlider;
|
||||
|
||||
// If we're currently at the end, restart
|
||||
if (!slider.canStepNext()) slider.resetToStart();
|
||||
|
||||
@@ -1,8 +1,13 @@
|
||||
import $ from "jquery";
|
||||
import { InputBinding } from "./InputBinding";
|
||||
import { InputBinding } from "./inputBinding";
|
||||
import { hasOwnProperty, isBS3 } from "../../utils";
|
||||
|
||||
type TabInputReceiveMessageData = { value?: string };
|
||||
|
||||
function getTabName(anchor: JQuery<HTMLElement>): string {
|
||||
return anchor.attr("data-value") || anchor.text();
|
||||
}
|
||||
|
||||
class BootstrapTabInputBinding extends InputBinding {
|
||||
find(scope: HTMLElement): JQuery<HTMLElement> {
|
||||
return $(scope).find("ul.nav.shiny-tab-input");
|
||||
@@ -16,14 +21,11 @@ class BootstrapTabInputBinding extends InputBinding {
|
||||
".nav-link:not(.dropdown-toggle).active, .dropdown-menu .dropdown-item.active"
|
||||
);
|
||||
|
||||
if (anchor.length === 1) return this._getTabName(anchor);
|
||||
if (anchor.length === 1) return getTabName(anchor);
|
||||
|
||||
return null;
|
||||
}
|
||||
setValue(el: HTMLElement, value: string): void {
|
||||
// this is required as an arrow function will not fix the usage
|
||||
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
||||
const self = this;
|
||||
let success = false;
|
||||
|
||||
if (value) {
|
||||
@@ -36,7 +38,7 @@ class BootstrapTabInputBinding extends InputBinding {
|
||||
);
|
||||
|
||||
anchors.each(function () {
|
||||
if (self._getTabName($(this)) === value) {
|
||||
if (getTabName($(this)) === value) {
|
||||
$(this).tab("show");
|
||||
success = true;
|
||||
return false; // Break out of each()
|
||||
@@ -69,9 +71,6 @@ class BootstrapTabInputBinding extends InputBinding {
|
||||
unsubscribe(el: HTMLElement): void {
|
||||
$(el).off(".bootstrapTabInputBinding");
|
||||
}
|
||||
_getTabName(anchor: JQuery<HTMLElement>): string {
|
||||
return anchor.attr("data-value") || anchor.text();
|
||||
}
|
||||
}
|
||||
|
||||
export { BootstrapTabInputBinding };
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import $ from "jquery";
|
||||
import { $escape, updateLabel, hasOwnProperty } from "../../utils";
|
||||
|
||||
import { InputBinding } from "./InputBinding";
|
||||
import { InputBinding } from "./inputBinding";
|
||||
|
||||
// interface TextHTMLElement extends NameValueHTMLElement {
|
||||
// placeholder: any;
|
||||
@@ -14,6 +14,12 @@ type TextReceiveMessageData = {
|
||||
placeholder?: TextHTMLElement["placeholder"];
|
||||
};
|
||||
|
||||
function getLabelNode(el: HTMLElement): JQuery<HTMLElement> {
|
||||
return $(el)
|
||||
.parent()
|
||||
.find('label[for="' + $escape(el.id) + '"]');
|
||||
}
|
||||
|
||||
class TextInputBindingBase extends InputBinding {
|
||||
find(scope: HTMLElement): JQuery<HTMLElement> {
|
||||
const $inputs = $(scope).find(
|
||||
@@ -80,12 +86,6 @@ class TextInputBindingBase extends InputBinding {
|
||||
};
|
||||
el;
|
||||
}
|
||||
|
||||
_getLabelNode(el: HTMLElement): JQuery<HTMLElement> {
|
||||
return $(el)
|
||||
.parent()
|
||||
.find('label[for="' + $escape(el.id) + '"]');
|
||||
}
|
||||
}
|
||||
|
||||
class TextInputBinding extends TextInputBindingBase {
|
||||
@@ -103,7 +103,7 @@ class TextInputBinding extends TextInputBindingBase {
|
||||
placeholder: string;
|
||||
} {
|
||||
return {
|
||||
label: this._getLabelNode(el).text(),
|
||||
label: getLabelNode(el).text(),
|
||||
value: el.value,
|
||||
placeholder: el.placeholder,
|
||||
};
|
||||
@@ -111,7 +111,7 @@ class TextInputBinding extends TextInputBindingBase {
|
||||
receiveMessage(el: TextHTMLElement, data: TextReceiveMessageData): void {
|
||||
if (hasOwnProperty(data, "value")) this.setValue(el, data.value);
|
||||
|
||||
updateLabel(data.label, this._getLabelNode(el));
|
||||
updateLabel(data.label, getLabelNode(el));
|
||||
|
||||
if (hasOwnProperty(data, "placeholder")) el.placeholder = data.placeholder;
|
||||
|
||||
|
||||
@@ -1,34 +1,34 @@
|
||||
import $ from "jquery";
|
||||
|
||||
import { OutputBinding } from "./OutputBinding";
|
||||
import { OutputBinding } from "./outputBinding";
|
||||
import { shinyUnbindAll } from "../../shiny/initedMethods";
|
||||
import { debounce } from "../../time";
|
||||
import { escapeHTML } from "../../utils";
|
||||
import { indirectEval } from "../../utils/eval";
|
||||
import type { errorsMessageValue } from "../../shiny/shinyapp";
|
||||
import type { ErrorsMessageValue } from "../../shiny/shinyapp";
|
||||
|
||||
class DatatableOutputBinding extends OutputBinding {
|
||||
find(scope: HTMLElement): JQuery<HTMLElement> {
|
||||
return $(scope).find(".shiny-datatable-output");
|
||||
}
|
||||
onValueError(el: HTMLElement, err: errorsMessageValue): void {
|
||||
onValueError(el: HTMLElement, err: ErrorsMessageValue): void {
|
||||
shinyUnbindAll(el);
|
||||
this.renderError(el, err);
|
||||
}
|
||||
renderValue(
|
||||
el: HTMLElement,
|
||||
data: null | {
|
||||
colnames?: Array<string>;
|
||||
options?: null | {
|
||||
data: {
|
||||
colnames?: string[];
|
||||
options?: {
|
||||
searching?: boolean;
|
||||
search?: { caseInsensitive?: boolean };
|
||||
};
|
||||
} | null;
|
||||
action?: string;
|
||||
escape?: string;
|
||||
evalOptions?: Array<string>;
|
||||
evalOptions?: string[];
|
||||
callback?: string;
|
||||
searchDelay?: number;
|
||||
}
|
||||
} | null
|
||||
): void {
|
||||
const $el = $(el).empty();
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import $ from "jquery";
|
||||
|
||||
import { OutputBinding } from "./OutputBinding";
|
||||
import { OutputBinding } from "./outputBinding";
|
||||
|
||||
class DownloadLinkOutputBinding extends OutputBinding {
|
||||
find(scope: HTMLElement): JQuery<HTMLElement> {
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
import $ from "jquery";
|
||||
|
||||
import { OutputBinding } from "./OutputBinding";
|
||||
import { OutputBinding } from "./outputBinding";
|
||||
import { shinyUnbindAll } from "../../shiny/initedMethods";
|
||||
import { renderContent } from "../../shiny/render";
|
||||
import type { errorsMessageValue } from "../../shiny/shinyapp";
|
||||
import type { ErrorsMessageValue } from "../../shiny/shinyapp";
|
||||
|
||||
class HtmlOutputBinding extends OutputBinding {
|
||||
find(scope: HTMLElement): JQuery<HTMLElement> {
|
||||
return $(scope).find(".shiny-html-output");
|
||||
}
|
||||
onValueError(el: HTMLElement, err: errorsMessageValue): void {
|
||||
onValueError(el: HTMLElement, err: ErrorsMessageValue): void {
|
||||
shinyUnbindAll(el);
|
||||
this.renderError(el, err);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import $ from "jquery";
|
||||
import { OutputBinding } from "./OutputBinding";
|
||||
import { OutputBinding } from "./outputBinding";
|
||||
import {
|
||||
createBrushHandler,
|
||||
createClickHandler,
|
||||
@@ -15,8 +15,8 @@ import {
|
||||
hasOwnProperty,
|
||||
} from "../../utils";
|
||||
import { isIE, IEVersion } from "../../utils/browser";
|
||||
import type { CoordmapInitType } from "../../imageutils/initCoordmap";
|
||||
import type { errorsMessageValue } from "../../shiny/shinyapp";
|
||||
import type { CoordmapInit } from "../../imageutils/initCoordmap";
|
||||
import type { ErrorsMessageValue } from "../../shiny/shinyapp";
|
||||
|
||||
class ImageOutputBinding extends OutputBinding {
|
||||
find(scope: HTMLElement): JQuery<HTMLElement> {
|
||||
@@ -26,9 +26,9 @@ class ImageOutputBinding extends OutputBinding {
|
||||
renderValue(
|
||||
el: HTMLElement,
|
||||
data: {
|
||||
coordmap: CoordmapInitType;
|
||||
coordmap: CoordmapInit;
|
||||
error?: string;
|
||||
} & Record<string, string>
|
||||
} & { [key: string]: string }
|
||||
): void {
|
||||
// The overall strategy:
|
||||
// * Clear out existing image and event handlers.
|
||||
@@ -65,34 +65,40 @@ class ImageOutputBinding extends OutputBinding {
|
||||
|
||||
// If value is undefined, return alternate. Sort of like ||, except it won't
|
||||
// return alternate for other falsy values (0, false, null).
|
||||
function OR(value, alternate) {
|
||||
function ifUndefined(value, alternate) {
|
||||
if (value === undefined) return alternate;
|
||||
return value;
|
||||
}
|
||||
|
||||
const opts = {
|
||||
clickId: $el.data("click-id"),
|
||||
clickClip: OR(strToBool($el.data("click-clip")), true),
|
||||
clickClip: ifUndefined(strToBool($el.data("click-clip")), true),
|
||||
|
||||
dblclickId: $el.data("dblclick-id"),
|
||||
dblclickClip: OR(strToBool($el.data("dblclick-clip")), true),
|
||||
dblclickDelay: OR($el.data("dblclick-delay"), 400),
|
||||
dblclickClip: ifUndefined(strToBool($el.data("dblclick-clip")), true),
|
||||
dblclickDelay: ifUndefined($el.data("dblclick-delay"), 400),
|
||||
|
||||
hoverId: $el.data("hover-id"),
|
||||
hoverClip: OR(strToBool($el.data("hover-clip")), true),
|
||||
hoverDelayType: OR($el.data("hover-delay-type"), "debounce"),
|
||||
hoverDelay: OR($el.data("hover-delay"), 300),
|
||||
hoverNullOutside: OR(strToBool($el.data("hover-null-outside")), false),
|
||||
hoverClip: ifUndefined(strToBool($el.data("hover-clip")), true),
|
||||
hoverDelayType: ifUndefined($el.data("hover-delay-type"), "debounce"),
|
||||
hoverDelay: ifUndefined($el.data("hover-delay"), 300),
|
||||
hoverNullOutside: ifUndefined(
|
||||
strToBool($el.data("hover-null-outside")),
|
||||
false
|
||||
),
|
||||
|
||||
brushId: $el.data("brush-id"),
|
||||
brushClip: OR(strToBool($el.data("brush-clip")), true),
|
||||
brushDelayType: OR($el.data("brush-delay-type"), "debounce"),
|
||||
brushDelay: OR($el.data("brush-delay"), 300),
|
||||
brushFill: OR($el.data("brush-fill"), "#666"),
|
||||
brushStroke: OR($el.data("brush-stroke"), "#000"),
|
||||
brushOpacity: OR($el.data("brush-opacity"), 0.3),
|
||||
brushDirection: OR($el.data("brush-direction"), "xy"),
|
||||
brushResetOnNew: OR(strToBool($el.data("brush-reset-on-new")), false),
|
||||
brushClip: ifUndefined(strToBool($el.data("brush-clip")), true),
|
||||
brushDelayType: ifUndefined($el.data("brush-delay-type"), "debounce"),
|
||||
brushDelay: ifUndefined($el.data("brush-delay"), 300),
|
||||
brushFill: ifUndefined($el.data("brush-fill"), "#666"),
|
||||
brushStroke: ifUndefined($el.data("brush-stroke"), "#000"),
|
||||
brushOpacity: ifUndefined($el.data("brush-opacity"), 0.3),
|
||||
brushDirection: ifUndefined($el.data("brush-direction"), "xy"),
|
||||
brushResetOnNew: ifUndefined(
|
||||
strToBool($el.data("brush-reset-on-new")),
|
||||
false
|
||||
),
|
||||
|
||||
coordmap: data.coordmap,
|
||||
};
|
||||
@@ -105,7 +111,7 @@ class ImageOutputBinding extends OutputBinding {
|
||||
}
|
||||
|
||||
// Copy items from data to img. Don't set the coordmap as an attribute.
|
||||
$.each(data, function (key, value) {
|
||||
$.each(data, function (key: string, value) {
|
||||
if (value === null || key === "coordmap") {
|
||||
return;
|
||||
}
|
||||
@@ -257,7 +263,7 @@ class ImageOutputBinding extends OutputBinding {
|
||||
});
|
||||
}
|
||||
|
||||
renderError(el: HTMLElement, err: errorsMessageValue): void {
|
||||
renderError(el: HTMLElement, err: ErrorsMessageValue): void {
|
||||
$(el).find("img").trigger("reset");
|
||||
OutputBinding.prototype.renderError.call(this, el, err);
|
||||
}
|
||||
@@ -281,8 +287,8 @@ class ImageOutputBinding extends OutputBinding {
|
||||
|
||||
resize(
|
||||
el: HTMLElement,
|
||||
width: string | number,
|
||||
height: string | number
|
||||
width: number | string,
|
||||
height: number | string
|
||||
): void {
|
||||
$(el).find("img").trigger("resize");
|
||||
return;
|
||||
|
||||
@@ -5,7 +5,7 @@ import { DatatableOutputBinding } from "./datatable";
|
||||
import { HtmlOutputBinding } from "./html";
|
||||
import { imageOutputBinding } from "./image";
|
||||
|
||||
import { OutputBinding } from "./OutputBinding";
|
||||
import { OutputBinding } from "./outputBinding";
|
||||
|
||||
type InitOutputBindings = {
|
||||
outputBindings: BindingRegistry<OutputBinding>;
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import $ from "jquery";
|
||||
import { asArray } from "../../utils";
|
||||
import type { errorsMessageValue } from "../../shiny/shinyapp";
|
||||
import type { ErrorsMessageValue } from "../../shiny/shinyapp";
|
||||
|
||||
class OutputBinding {
|
||||
name: string;
|
||||
|
||||
// Returns a jQuery object or element array that contains the
|
||||
// descendants of scope that match this binding
|
||||
find(scope: JQuery<HTMLElement> | HTMLElement): JQuery<HTMLElement> {
|
||||
find(scope: HTMLElement | JQuery<HTMLElement>): JQuery<HTMLElement> {
|
||||
throw "Not implemented";
|
||||
scope;
|
||||
}
|
||||
@@ -25,10 +25,10 @@ class OutputBinding {
|
||||
this.clearError(el);
|
||||
this.renderValue(el, data);
|
||||
}
|
||||
onValueError(el: HTMLElement, err: errorsMessageValue): void {
|
||||
onValueError(el: HTMLElement, err: ErrorsMessageValue): void {
|
||||
this.renderError(el, err);
|
||||
}
|
||||
renderError(el: HTMLElement, err: errorsMessageValue): void {
|
||||
renderError(el: HTMLElement, err: ErrorsMessageValue): void {
|
||||
this.clearError(el);
|
||||
if (err.message === "") {
|
||||
// not really error, but we just need to wait (e.g. action buttons)
|
||||
@@ -54,10 +54,10 @@ class OutputBinding {
|
||||
});
|
||||
}
|
||||
showProgress(el: HTMLElement, show: boolean): void {
|
||||
const RECALC_CLASS = "recalculating";
|
||||
const recalcClass = "recalculating";
|
||||
|
||||
if (show) $(el).addClass(RECALC_CLASS);
|
||||
else $(el).removeClass(RECALC_CLASS);
|
||||
if (show) $(el).addClass(recalcClass);
|
||||
else $(el).removeClass(recalcClass);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import $ from "jquery";
|
||||
import { OutputBinding } from "./OutputBinding";
|
||||
import { OutputBinding } from "./outputBinding";
|
||||
|
||||
class TextOutputBinding extends OutputBinding {
|
||||
find(scope: HTMLElement): JQuery<HTMLElement> {
|
||||
return $(scope).find(".shiny-text-output");
|
||||
}
|
||||
renderValue(el: HTMLElement, data: string | number | boolean): void {
|
||||
renderValue(el: HTMLElement, data: boolean | number | string): void {
|
||||
$(el).text(data);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import type { errorsMessageValue } from "../shiny/shinyapp";
|
||||
import type { ErrorsMessageValue } from "../shiny/shinyapp";
|
||||
import { makeResizeFilter } from "../utils";
|
||||
import { OutputBinding } from "./output";
|
||||
import type { OutputBinding } from "./output";
|
||||
|
||||
interface OutpuBindingWithResize extends OutputBinding {
|
||||
resize?: (
|
||||
el: HTMLElement,
|
||||
width: string | number,
|
||||
height: string | number
|
||||
width: number | string,
|
||||
height: number | string
|
||||
) => void;
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ class OutputBindingAdapter {
|
||||
onValueChange(data: unknown): void {
|
||||
this.binding.onValueChange(this.el, data);
|
||||
}
|
||||
onValueError(err: errorsMessageValue): void {
|
||||
onValueError(err: ErrorsMessageValue): void {
|
||||
this.binding.onValueError(this.el, err);
|
||||
}
|
||||
showProgress(show: boolean): void {
|
||||
@@ -1,20 +1,21 @@
|
||||
import { mergeSort } from "../utils";
|
||||
|
||||
interface BindingInterface {
|
||||
interface BindingBase {
|
||||
name: string;
|
||||
}
|
||||
|
||||
interface BindingObjType<BindingType> {
|
||||
binding: BindingType;
|
||||
interface BindingObj<Binding> {
|
||||
binding: Binding;
|
||||
priority: number;
|
||||
name?: string;
|
||||
}
|
||||
|
||||
class BindingRegistry<BindingType extends BindingInterface> {
|
||||
bindings: Array<BindingObjType<BindingType>> = [];
|
||||
bindingNames: Record<string, BindingObjType<BindingType>> = {};
|
||||
class BindingRegistry<Binding extends BindingBase> {
|
||||
name: string;
|
||||
bindings: Array<BindingObj<Binding>> = [];
|
||||
bindingNames: { [key: string]: BindingObj<Binding> } = {};
|
||||
|
||||
register(binding: BindingType, bindingName: string, priority = 0): void {
|
||||
register(binding: Binding, bindingName: string, priority = 0): void {
|
||||
const bindingObj = { binding, priority };
|
||||
|
||||
this.bindings.unshift(bindingObj);
|
||||
@@ -39,7 +40,7 @@ class BindingRegistry<BindingType extends BindingInterface> {
|
||||
return bindingObj.priority;
|
||||
}
|
||||
|
||||
getBindings(): Array<BindingObjType<BindingType>> {
|
||||
getBindings(): Array<BindingObj<Binding>> {
|
||||
// Sort the bindings. The ones with higher priority are consulted
|
||||
// first; ties are broken by most-recently-registered.
|
||||
return mergeSort(this.bindings, function (a, b) {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { InputBinding } from "../bindings/input/InputBinding";
|
||||
import type { OutputBindingAdapter } from "../bindings/output_adapter";
|
||||
import type { priorityType } from "../inputPolicies/InputPolicy";
|
||||
import type { errorsMessageValue } from "../shiny/shinyapp";
|
||||
import type { InputBinding } from "../bindings/input/inputBinding";
|
||||
import type { OutputBindingAdapter } from "../bindings/outputAdapter";
|
||||
import type { EventPriority } from "../inputPolicies/inputPolicy";
|
||||
import type { ErrorsMessageValue } from "../shiny/shinyapp";
|
||||
|
||||
interface ShinyEventCommon extends JQuery.Event {
|
||||
name: string;
|
||||
@@ -12,7 +12,7 @@ interface ShinyEventInputChanged extends ShinyEventCommon {
|
||||
value: unknown;
|
||||
binding: InputBinding;
|
||||
inputType: string;
|
||||
priority: priorityType;
|
||||
priority: EventPriority;
|
||||
}
|
||||
interface ShinyEventUpdateInput extends ShinyEventCommon {
|
||||
message: unknown;
|
||||
@@ -25,10 +25,10 @@ interface ShinyEventValue extends ShinyEventCommon {
|
||||
|
||||
interface ShinyEventError extends ShinyEventCommon {
|
||||
binding: OutputBindingAdapter;
|
||||
error: errorsMessageValue;
|
||||
error: ErrorsMessageValue;
|
||||
}
|
||||
interface ShinyEventMessage extends JQuery.Event {
|
||||
message: Record<string, unknown>;
|
||||
message: { [key: string]: unknown };
|
||||
}
|
||||
|
||||
export type {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import $ from "jquery";
|
||||
import { triggerFileInputChanged } from "../events/shiny_inputchanged";
|
||||
import { triggerFileInputChanged } from "../events/inputChanged";
|
||||
import { $escape } from "../utils";
|
||||
import { ShinyApp } from "../shiny/shinyapp";
|
||||
import type { ShinyApp } from "../shiny/shinyapp";
|
||||
import { getFileInputBinding } from "../shiny/initedMethods";
|
||||
|
||||
type JobId = string;
|
||||
@@ -135,7 +135,7 @@ class FileUploader extends FileProcessor {
|
||||
): void;
|
||||
makeRequest(
|
||||
method: string,
|
||||
args: Array<unknown>,
|
||||
args: unknown[],
|
||||
onSuccess: Parameters<ShinyApp["makeRequest"]>[2],
|
||||
onFailure: Parameters<ShinyApp["makeRequest"]>[3],
|
||||
blobs: Parameters<ShinyApp["makeRequest"]>[4]
|
||||
@@ -1,21 +1,21 @@
|
||||
import $ from "jquery";
|
||||
import type { CoordmapType } from "./initCoordmap";
|
||||
import type { Coordmap } from "./initCoordmap";
|
||||
import { findOrigin } from "./initCoordmap";
|
||||
import { equal, isnan, mapValues, roundSignif } from "../utils";
|
||||
import type { PanelType } from "./initPanelScales";
|
||||
import type { Panel } from "./initPanelScales";
|
||||
|
||||
import type { OffsetType } from "./findbox";
|
||||
import type { Offset } from "./findbox";
|
||||
import { findBox } from "./findbox";
|
||||
import { shiftToRange } from "./shiftToRange";
|
||||
|
||||
type BoundsType = {
|
||||
type Bounds = {
|
||||
xmin: number;
|
||||
xmax: number;
|
||||
ymin: number;
|
||||
ymax: number;
|
||||
};
|
||||
type BoundsCss = BoundsType;
|
||||
type BoundsData = BoundsType;
|
||||
type BoundsCss = Bounds;
|
||||
type BoundsData = Bounds;
|
||||
|
||||
type ImageState = {
|
||||
brushing?: boolean;
|
||||
@@ -23,8 +23,8 @@ type ImageState = {
|
||||
resizing?: boolean;
|
||||
|
||||
// Offset of last mouse down and up events (in CSS pixels)
|
||||
down?: OffsetType;
|
||||
up?: OffsetType;
|
||||
down?: Offset;
|
||||
up?: Offset;
|
||||
|
||||
// Which side(s) we're currently resizing
|
||||
resizeSides?: {
|
||||
@@ -38,30 +38,30 @@ type ImageState = {
|
||||
boundsData?: BoundsData;
|
||||
|
||||
// Panel object that the brush is in
|
||||
panel?: PanelType;
|
||||
panel?: Panel;
|
||||
|
||||
// The bounds at the start of a drag/resize (in CSS pixels)
|
||||
changeStartBounds?: BoundsType;
|
||||
changeStartBounds?: Bounds;
|
||||
};
|
||||
|
||||
type BrushOptsType = {
|
||||
brushDirection: "x" | "y" | "xy";
|
||||
type BrushOpts = {
|
||||
brushDirection: "x" | "xy" | "y";
|
||||
brushClip: boolean;
|
||||
brushFill: string;
|
||||
brushOpacity: string;
|
||||
brushStroke: string;
|
||||
brushDelayType?: "throttle" | "debounce";
|
||||
brushDelayType?: "debounce" | "throttle";
|
||||
brushDelay?: number;
|
||||
brushResetOnNew?: boolean;
|
||||
};
|
||||
|
||||
type BrushType = {
|
||||
type Brush = {
|
||||
reset: () => void;
|
||||
|
||||
importOldBrush: () => void;
|
||||
isInsideBrush: (offsetCss: OffsetType) => boolean;
|
||||
isInResizeArea: (offsetCss: OffsetType) => boolean;
|
||||
whichResizeSides: (offsetCss: OffsetType) => ImageState["resizeSides"];
|
||||
isInsideBrush: (offsetCss: Offset) => boolean;
|
||||
isInResizeArea: (offsetCss: Offset) => boolean;
|
||||
whichResizeSides: (offsetCss: Offset) => ImageState["resizeSides"];
|
||||
|
||||
// A callback when the wrapper div or img is resized.
|
||||
onResize: () => void;
|
||||
@@ -91,17 +91,17 @@ type BrushType = {
|
||||
|
||||
isBrushing: () => ImageState["brushing"];
|
||||
startBrushing: () => void;
|
||||
brushTo: (offsetCss: OffsetType) => void;
|
||||
brushTo: (offsetCss: Offset) => void;
|
||||
stopBrushing: () => void;
|
||||
|
||||
isDragging: () => ImageState["dragging"];
|
||||
startDragging: () => void;
|
||||
dragTo: (offsetCss: OffsetType) => void;
|
||||
dragTo: (offsetCss: Offset) => void;
|
||||
stopDragging: () => void;
|
||||
|
||||
isResizing: () => ImageState["resizing"];
|
||||
startResizing: () => void;
|
||||
resizeTo: (offsetCss: OffsetType) => void;
|
||||
resizeTo: (offsetCss: Offset) => void;
|
||||
stopResizing: () => void;
|
||||
};
|
||||
|
||||
@@ -109,10 +109,10 @@ type BrushType = {
|
||||
// in a brushHandler, which provides various event listeners.
|
||||
function createBrush(
|
||||
$el: JQuery<HTMLElement>,
|
||||
opts: BrushOptsType,
|
||||
coordmap: CoordmapType,
|
||||
opts: BrushOpts,
|
||||
coordmap: Coordmap,
|
||||
expandPixels: number
|
||||
): BrushType {
|
||||
): Brush {
|
||||
// Number of pixels outside of brush to allow start resizing
|
||||
const resizeExpand = 10;
|
||||
|
||||
@@ -354,10 +354,8 @@ function createBrush(
|
||||
|
||||
// Get or set the bounds of the brush using coordinates in the data space.
|
||||
function boundsData(): ImageState["boundsData"];
|
||||
function boundsData(
|
||||
boxData: Parameters<PanelType["scaleDataToImg"]>[0]
|
||||
): void;
|
||||
function boundsData(boxData?: Parameters<PanelType["scaleDataToImg"]>[0]) {
|
||||
function boundsData(boxData: Parameters<Panel["scaleDataToImg"]>[0]): void;
|
||||
function boundsData(boxData?: Parameters<Panel["scaleDataToImg"]>[0]) {
|
||||
if (boxData === undefined) {
|
||||
return $.extend({}, state.boundsData);
|
||||
}
|
||||
@@ -436,14 +434,14 @@ function createBrush(
|
||||
.outerHeight(b.ymax - b.ymin + 1);
|
||||
}
|
||||
|
||||
function down(offsetCss?: OffsetType) {
|
||||
function down(offsetCss?: Offset) {
|
||||
if (offsetCss === undefined) return state.down;
|
||||
|
||||
state.down = offsetCss;
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function up(offsetCss?: OffsetType) {
|
||||
function up(offsetCss?: Offset) {
|
||||
if (offsetCss === undefined) return state.up;
|
||||
|
||||
state.up = offsetCss;
|
||||
@@ -463,7 +461,7 @@ function createBrush(
|
||||
updateDiv();
|
||||
}
|
||||
|
||||
function brushTo(offsetCss: OffsetType) {
|
||||
function brushTo(offsetCss: Offset) {
|
||||
boundsCss(findBox(state.down, offsetCss));
|
||||
$div.show();
|
||||
updateDiv();
|
||||
@@ -484,7 +482,7 @@ function createBrush(
|
||||
state.changeStartBounds = $.extend({}, state.boundsCss);
|
||||
}
|
||||
|
||||
function dragTo(offsetCss: OffsetType) {
|
||||
function dragTo(offsetCss: Offset) {
|
||||
// How far the brush was dragged
|
||||
const dx = offsetCss.x - state.down.x;
|
||||
const dy = offsetCss.y - state.down.y;
|
||||
@@ -545,7 +543,7 @@ function createBrush(
|
||||
state.resizeSides = whichResizeSides(state.down);
|
||||
}
|
||||
|
||||
function resizeTo(offsetCss: OffsetType) {
|
||||
function resizeTo(offsetCss: Offset) {
|
||||
// How far the brush was dragged
|
||||
const dCss = {
|
||||
x: offsetCss.x - state.down.x,
|
||||
@@ -638,4 +636,4 @@ function createBrush(
|
||||
|
||||
export { createBrush };
|
||||
|
||||
export type { BoundsType, BrushOptsType, BoundsCss };
|
||||
export type { Bounds, BrushOpts, BoundsCss };
|
||||
|
||||
@@ -21,7 +21,7 @@ function createClickInfo(
|
||||
// it with the information stored in this.e.
|
||||
function triggerEvent(
|
||||
newEventType: string,
|
||||
e: JQuery.MouseDownEvent | JQuery.DoubleClickEvent
|
||||
e: JQuery.DoubleClickEvent | JQuery.MouseDownEvent
|
||||
) {
|
||||
// Extract important info from e and construct a new event with type
|
||||
// eventType.
|
||||
|
||||
@@ -3,10 +3,10 @@ import { imageOutputBinding } from "../bindings/output/image";
|
||||
import { shinySetInputValue } from "../shiny/initedMethods";
|
||||
import { Debouncer, Throttler } from "../time";
|
||||
import { createBrush } from "./createBrush";
|
||||
import type { BoundsCss, BoundsType, BrushOptsType } from "./createBrush";
|
||||
import type { OffsetType } from "./findbox";
|
||||
import type { CoordmapType } from "./initCoordmap";
|
||||
import type { PanelType } from "./initPanelScales";
|
||||
import type { BoundsCss, Bounds, BrushOpts } from "./createBrush";
|
||||
import type { Offset } from "./findbox";
|
||||
import type { Coordmap } from "./initCoordmap";
|
||||
import type { Panel } from "./initPanelScales";
|
||||
|
||||
// ----------------------------------------------------------
|
||||
// Handler creators for click, hover, brush.
|
||||
@@ -15,7 +15,7 @@ import type { PanelType } from "./initPanelScales";
|
||||
// the same name (like 'mousedown').
|
||||
// ----------------------------------------------------------
|
||||
|
||||
type CreateHandlerType = {
|
||||
type CreateHandler = {
|
||||
mousemove?: (e: JQuery.MouseMoveEvent) => void;
|
||||
mouseout?: (e: JQuery.MouseOutEvent) => void;
|
||||
mousedown?: (e: JQuery.MouseDownEvent) => void;
|
||||
@@ -28,32 +28,32 @@ type BrushInfo = {
|
||||
xmax: number;
|
||||
ymin: number;
|
||||
ymax: number;
|
||||
// eslint-disable-next-line camelcase
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
coords_css?: BoundsCss;
|
||||
// eslint-disable-next-line camelcase
|
||||
coords_img?: BoundsType;
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
coords_img?: Bounds;
|
||||
x?: number;
|
||||
y?: number;
|
||||
// eslint-disable-next-line camelcase
|
||||
img_css_ratio?: OffsetType;
|
||||
mapping?: PanelType["mapping"];
|
||||
domain?: PanelType["domain"];
|
||||
range?: PanelType["range"];
|
||||
log?: PanelType["log"];
|
||||
direction?: BrushOptsType["brushDirection"];
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
img_css_ratio?: Offset;
|
||||
mapping?: Panel["mapping"];
|
||||
domain?: Panel["domain"];
|
||||
range?: Panel["range"];
|
||||
log?: Panel["log"];
|
||||
direction?: BrushOpts["brushDirection"];
|
||||
brushId?: string;
|
||||
outputId?: string;
|
||||
};
|
||||
|
||||
type InputIdType = Parameters<CoordmapType["mouseCoordinateSender"]>[0];
|
||||
type ClipType = Parameters<CoordmapType["mouseCoordinateSender"]>[1];
|
||||
type NullOutsideType = Parameters<CoordmapType["mouseCoordinateSender"]>[2];
|
||||
type InputId = Parameters<Coordmap["mouseCoordinateSender"]>[0];
|
||||
type Clip = Parameters<Coordmap["mouseCoordinateSender"]>[1];
|
||||
type NullOutside = Parameters<Coordmap["mouseCoordinateSender"]>[2];
|
||||
|
||||
function createClickHandler(
|
||||
inputId: InputIdType,
|
||||
clip: ClipType,
|
||||
coordmap: CoordmapType
|
||||
): CreateHandlerType {
|
||||
inputId: InputId,
|
||||
clip: Clip,
|
||||
coordmap: Coordmap
|
||||
): CreateHandler {
|
||||
const clickInfoSender = coordmap.mouseCoordinateSender(inputId, clip);
|
||||
|
||||
return {
|
||||
@@ -70,20 +70,20 @@ function createClickHandler(
|
||||
}
|
||||
|
||||
function createHoverHandler(
|
||||
inputId: InputIdType,
|
||||
inputId: InputId,
|
||||
delay: number,
|
||||
delayType: "throttle" | string,
|
||||
clip: ClipType,
|
||||
nullOutside: NullOutsideType,
|
||||
coordmap: CoordmapType
|
||||
): CreateHandlerType {
|
||||
delayType: string | "throttle",
|
||||
clip: Clip,
|
||||
nullOutside: NullOutside,
|
||||
coordmap: Coordmap
|
||||
): CreateHandler {
|
||||
const sendHoverInfo = coordmap.mouseCoordinateSender(
|
||||
inputId,
|
||||
clip,
|
||||
nullOutside
|
||||
);
|
||||
|
||||
let hoverInfoSender: Throttler | Debouncer;
|
||||
let hoverInfoSender: Debouncer | Throttler;
|
||||
|
||||
if (delayType === "throttle")
|
||||
hoverInfoSender = new Throttler(null, sendHoverInfo, delay);
|
||||
@@ -116,12 +116,12 @@ function createHoverHandler(
|
||||
// Returns a brush handler object. This has three public functions:
|
||||
// mousedown, mousemove, and onResetImg.
|
||||
function createBrushHandler(
|
||||
inputId: InputIdType,
|
||||
inputId: InputId,
|
||||
$el: JQuery<HTMLElement>,
|
||||
opts: BrushOptsType,
|
||||
coordmap: CoordmapType,
|
||||
opts: BrushOpts,
|
||||
coordmap: Coordmap,
|
||||
outputId: BrushInfo["outputId"]
|
||||
): CreateHandlerType {
|
||||
): CreateHandler {
|
||||
// Parameter: expand the area in which a brush can be started, by this
|
||||
// many pixels in all directions. (This should probably be a brush option)
|
||||
const expandPixels = 20;
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
// Given two sets of x/y coordinates, return an object representing the min
|
||||
// and max x and y values. (This could be generalized to any number of
|
||||
|
||||
import type { BoundsType } from "./createBrush";
|
||||
import type { Bounds } from "./createBrush";
|
||||
|
||||
type OffsetType = {
|
||||
type Offset = {
|
||||
x: number;
|
||||
y: number;
|
||||
};
|
||||
|
||||
// points).
|
||||
function findBox(offset1: OffsetType, offset2: OffsetType): BoundsType {
|
||||
function findBox(offset1: Offset, offset2: Offset): Bounds {
|
||||
return {
|
||||
xmin: Math.min(offset1.x, offset2.x),
|
||||
xmax: Math.max(offset1.x, offset2.x),
|
||||
@@ -18,5 +18,5 @@ function findBox(offset1: OffsetType, offset2: OffsetType): BoundsType {
|
||||
};
|
||||
}
|
||||
|
||||
export type { OffsetType };
|
||||
export type { Offset };
|
||||
export { findBox };
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import $ from "jquery";
|
||||
import { shinySetInputValue } from "../shiny/initedMethods";
|
||||
import { mapValues } from "../utils";
|
||||
import type { OffsetType } from "./findbox";
|
||||
import type { BoundsType } from "./createBrush";
|
||||
import type { PanelType } from "./initPanelScales";
|
||||
import type { Offset } from "./findbox";
|
||||
import type { Bounds } from "./createBrush";
|
||||
import type { Panel } from "./initPanelScales";
|
||||
import { initPanelScales } from "./initPanelScales";
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
@@ -21,7 +21,7 @@ function findScalingRatio($el: JQuery<HTMLElement>) {
|
||||
};
|
||||
}
|
||||
|
||||
function findOrigin($el: JQuery<HTMLElement>): OffsetType {
|
||||
function findOrigin($el: JQuery<HTMLElement>): Offset {
|
||||
const offset = $el.offset();
|
||||
const scalingRatio = findScalingRatio($el);
|
||||
|
||||
@@ -66,53 +66,53 @@ function findDims($el: JQuery<HTMLElement>) {
|
||||
};
|
||||
}
|
||||
|
||||
type OffsetCssType = Record<string, number>;
|
||||
type OffsetImgType = Record<string, number>;
|
||||
type OffsetCss = { [key: string]: number };
|
||||
type OffsetImg = { [key: string]: number };
|
||||
|
||||
type Coords = {
|
||||
// eslint-disable-next-line camelcase
|
||||
coords_css: OffsetType;
|
||||
// eslint-disable-next-line camelcase
|
||||
coords_img: OffsetType;
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
coords_css: Offset;
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
coords_img: Offset;
|
||||
x?: number;
|
||||
y?: number;
|
||||
// eslint-disable-next-line camelcase
|
||||
img_css_ratio?: OffsetType;
|
||||
mapping?: PanelType["mapping"];
|
||||
domain?: PanelType["domain"];
|
||||
range?: PanelType["range"];
|
||||
log?: PanelType["log"];
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
img_css_ratio?: Offset;
|
||||
mapping?: Panel["mapping"];
|
||||
domain?: Panel["domain"];
|
||||
range?: Panel["range"];
|
||||
log?: Panel["log"];
|
||||
};
|
||||
|
||||
type CoordmapInitType = {
|
||||
panels: Array<PanelType>;
|
||||
type CoordmapInit = {
|
||||
panels: Panel[];
|
||||
dims: {
|
||||
height: number;
|
||||
width: number;
|
||||
};
|
||||
};
|
||||
type CoordmapType = {
|
||||
panels: Array<PanelType>;
|
||||
type Coordmap = {
|
||||
panels: Panel[];
|
||||
dims: {
|
||||
height: number;
|
||||
width: number;
|
||||
};
|
||||
mouseOffsetCss: (evt: JQuery.MouseEventBase) => OffsetType;
|
||||
mouseOffsetCss: (evt: JQuery.MouseEventBase) => Offset;
|
||||
scaleCssToImg: {
|
||||
(offsetCss: BoundsType): BoundsType;
|
||||
(offsetCss: OffsetType): OffsetType;
|
||||
(offsetCss: OffsetCssType): OffsetImgType;
|
||||
(offsetCss: Bounds): Bounds;
|
||||
(offsetCss: Offset): Offset;
|
||||
(offsetCss: OffsetCss): OffsetImg;
|
||||
};
|
||||
scaleImgToCss: {
|
||||
(offsetImg: BoundsType): BoundsType;
|
||||
(offsetImg: OffsetType): OffsetType;
|
||||
(offsetImg: OffsetImgType): OffsetCssType;
|
||||
(offsetImg: Bounds): Bounds;
|
||||
(offsetImg: Offset): Offset;
|
||||
(offsetImg: OffsetImg): OffsetCss;
|
||||
};
|
||||
imgToCssScalingRatio: () => OffsetType;
|
||||
cssToImgScalingRatio: () => OffsetType;
|
||||
imgToCssScalingRatio: () => Offset;
|
||||
cssToImgScalingRatio: () => Offset;
|
||||
|
||||
getPanelCss: (offsetCss: OffsetCssType, expand?: number) => PanelType;
|
||||
isInPanelCss: (offsetCss: OffsetCssType, expand?: number) => boolean;
|
||||
getPanelCss: (offsetCss: OffsetCss, expand?: number) => Panel;
|
||||
isInPanelCss: (offsetCss: OffsetCss, expand?: number) => boolean;
|
||||
|
||||
mouseCoordinateSender: (
|
||||
inputId: string,
|
||||
@@ -142,9 +142,9 @@ type CoordmapType = {
|
||||
// than the other two, because there can be multiple panels (as in facets).
|
||||
function initCoordmap(
|
||||
$el: JQuery<HTMLElement>,
|
||||
coordmap_: CoordmapInitType
|
||||
): CoordmapType {
|
||||
const coordmap = coordmap_ as CoordmapType;
|
||||
coordmap_: CoordmapInit
|
||||
): Coordmap {
|
||||
const coordmap = coordmap_ as Coordmap;
|
||||
const $img = $el.find("img");
|
||||
const img = $img[0];
|
||||
|
||||
@@ -192,9 +192,9 @@ function initCoordmap(
|
||||
// "xmin", "y", and "ymax" -- anything that starts with "x" and "y". If the
|
||||
// img content is 1000 pixels wide, but is scaled to 400 pixels on screen,
|
||||
// and the input is x:400, then this will return x:1000.
|
||||
function scaleCssToImg(offsetCss: BoundsType): BoundsType;
|
||||
function scaleCssToImg(offsetCss: OffsetType): OffsetType;
|
||||
function scaleCssToImg(offsetCss: OffsetCssType): OffsetImgType;
|
||||
function scaleCssToImg(offsetCss: Bounds): Bounds;
|
||||
function scaleCssToImg(offsetCss: Offset): Offset;
|
||||
function scaleCssToImg(offsetCss: OffsetCss): OffsetImg;
|
||||
function scaleCssToImg(offsetCss) {
|
||||
const pixelScaling = coordmap.imgToCssScalingRatio();
|
||||
|
||||
@@ -217,12 +217,12 @@ function initCoordmap(
|
||||
// corresponding offset in CSS pixels. If the img content is 1000 pixels
|
||||
// wide, but is scaled to 400 pixels on screen, and the input is x:1000,
|
||||
// then this will return x:400.
|
||||
function scaleImgToCss(offsetImg: BoundsType): BoundsType;
|
||||
function scaleImgToCss(offsetImg: OffsetType): OffsetType;
|
||||
function scaleImgToCss(offsetImg: OffsetImgType): OffsetCssType;
|
||||
function scaleImgToCss(
|
||||
offsetImg: Record<string, number>
|
||||
): Record<string, number> {
|
||||
function scaleImgToCss(offsetImg: Bounds): Bounds;
|
||||
function scaleImgToCss(offsetImg: Offset): Offset;
|
||||
function scaleImgToCss(offsetImg: OffsetImg): OffsetCss;
|
||||
function scaleImgToCss(offsetImg: { [key: string]: number }): {
|
||||
[key: string]: number;
|
||||
} {
|
||||
const pixelScaling = coordmap.imgToCssScalingRatio();
|
||||
|
||||
const result = mapValues(offsetImg, (value, key) => {
|
||||
@@ -359,9 +359,9 @@ function initCoordmap(
|
||||
if (clip) return;
|
||||
|
||||
const coords: Coords = {
|
||||
// eslint-disable-next-line camelcase
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
coords_css: coordsCss,
|
||||
// eslint-disable-next-line camelcase
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
coords_img: coordmap.scaleCssToImg(coordsCss),
|
||||
};
|
||||
|
||||
@@ -376,11 +376,11 @@ function initCoordmap(
|
||||
const coords: Coords = {
|
||||
x: coordsData.x,
|
||||
y: coordsData.y,
|
||||
// eslint-disable-next-line camelcase
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
coords_css: coordsCss,
|
||||
// eslint-disable-next-line camelcase
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
coords_img: coordsImg,
|
||||
// eslint-disable-next-line camelcase
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
img_css_ratio: coordmap.cssToImgScalingRatio(),
|
||||
};
|
||||
|
||||
@@ -402,5 +402,5 @@ function initCoordmap(
|
||||
return coordmap;
|
||||
}
|
||||
|
||||
export type { CoordmapType, CoordmapInitType };
|
||||
export type { Coordmap, CoordmapInit };
|
||||
export { initCoordmap, findOrigin };
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// Map a value x from a domain to a range. If clip is true, clip it to the
|
||||
|
||||
import { OffsetType } from "./findbox";
|
||||
import type { Offset } from "./findbox";
|
||||
import { mapValues } from "../utils";
|
||||
|
||||
// range.
|
||||
@@ -52,7 +52,7 @@ function scaler1D(
|
||||
};
|
||||
}
|
||||
|
||||
type PanelType = {
|
||||
type Panel = {
|
||||
domain: {
|
||||
top: number;
|
||||
bottom: number;
|
||||
@@ -69,17 +69,17 @@ type PanelType = {
|
||||
x?: number;
|
||||
y?: number;
|
||||
};
|
||||
mapping: Record<string, string>;
|
||||
// eslint-disable-next-line camelcase
|
||||
panel_vars?: Record<string, number | string>;
|
||||
mapping: { [key: string]: string };
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
panel_vars?: { [key: string]: number | string };
|
||||
|
||||
scaleDataToImg?: (
|
||||
val: Record<string, number>,
|
||||
val: { [key: string]: number },
|
||||
clip?: boolean
|
||||
) => Record<string, number>;
|
||||
) => { [key: string]: number };
|
||||
scaleImgToData?: {
|
||||
(val: OffsetType, clip?: boolean): OffsetType;
|
||||
(val: Record<string, number>, clip?: boolean): Record<string, number>;
|
||||
(val: Offset, clip?: boolean): Offset;
|
||||
(val: { [key: string]: number }, clip?: boolean): { [key: string]: number };
|
||||
};
|
||||
|
||||
clipImg?: (offsetImg: { x: number; y: number }) => { x: number; y: number };
|
||||
@@ -87,7 +87,7 @@ type PanelType = {
|
||||
|
||||
// Modify panel, adding scale and inverse-scale functions that take objects
|
||||
// like {x:1, y:3}, and also add clip function.
|
||||
function addScaleFuns(panel: PanelType) {
|
||||
function addScaleFuns(panel: Panel) {
|
||||
const d = panel.domain;
|
||||
const r = panel.range;
|
||||
const xlog = panel.log && panel.log.x ? panel.log.x : null;
|
||||
@@ -111,8 +111,8 @@ function addScaleFuns(panel: PanelType) {
|
||||
});
|
||||
};
|
||||
|
||||
function scaleImgToData(val: OffsetType, clip?: boolean);
|
||||
function scaleImgToData(val: Record<string, number>, clip?: boolean) {
|
||||
function scaleImgToData(val: Offset, clip?: boolean);
|
||||
function scaleImgToData(val: { [key: string]: number }, clip?: boolean) {
|
||||
return mapValues(val, (value, key) => {
|
||||
const prefix = key.substring(0, 1);
|
||||
|
||||
@@ -149,7 +149,7 @@ function addScaleFuns(panel: PanelType) {
|
||||
// scaleDataToImg(), and clipImg() functions to each one. The panel objects
|
||||
// use img and data coordinates only; they do not use css coordinates. The
|
||||
// domain is in data coordinates; the range is in img coordinates.
|
||||
function initPanelScales(panels: Array<PanelType>): void {
|
||||
function initPanelScales(panels: Panel[]): void {
|
||||
// Add the functions to each panel object.
|
||||
for (let i = 0; i < panels.length; i++) {
|
||||
const panel = panels[i];
|
||||
@@ -158,5 +158,5 @@ function initPanelScales(panels: Array<PanelType>): void {
|
||||
}
|
||||
}
|
||||
|
||||
export type { PanelType };
|
||||
export type { Panel };
|
||||
export { initPanelScales };
|
||||
|
||||
@@ -3,10 +3,10 @@
|
||||
// range in vals is larger than the range of min and max, the result might not
|
||||
// make sense.
|
||||
function shiftToRange(
|
||||
vals: number | Array<number>,
|
||||
vals: number[] | number,
|
||||
min: number,
|
||||
max: number
|
||||
): Array<number> {
|
||||
): number[] {
|
||||
if (!(vals instanceof Array)) vals = [vals];
|
||||
|
||||
const maxval = Math.max.apply(null, vals);
|
||||
|
||||
@@ -5,7 +5,8 @@ import { InputRateDecorator } from "./inputRateDecorator";
|
||||
import { InputDeferDecorator } from "./inputDeferDecorator";
|
||||
import { InputValidateDecorator } from "./inputValidateDecorator";
|
||||
|
||||
import { priorityType, InputPolicy } from "./InputPolicy";
|
||||
import { InputPolicy } from "./inputPolicy";
|
||||
import type { EventPriority } from "./inputPolicy";
|
||||
|
||||
export {
|
||||
InputBatchSender,
|
||||
@@ -17,4 +18,4 @@ export {
|
||||
InputPolicy,
|
||||
};
|
||||
|
||||
export type { priorityType };
|
||||
export type { EventPriority };
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
import $ from "jquery";
|
||||
import { priorityType, InputPolicy } from "./InputPolicy";
|
||||
import { ShinyApp } from "../shiny/shinyapp";
|
||||
import type { EventPriority } from "./inputPolicy";
|
||||
import { InputPolicy } from "./inputPolicy";
|
||||
import type { ShinyApp } from "../shiny/shinyapp";
|
||||
|
||||
// Schedules data to be sent to shinyapp at the next setTimeout(0).
|
||||
// Batches multiple input calls into one websocket message.
|
||||
class InputBatchSender extends InputPolicy {
|
||||
shinyapp: ShinyApp;
|
||||
timerId: NodeJS.Timeout = null;
|
||||
pendingData: Record<string, unknown> = {};
|
||||
pendingData: { [key: string]: unknown } = {};
|
||||
reentrant = false;
|
||||
lastChanceCallback: Array<() => void> = [];
|
||||
|
||||
@@ -19,20 +20,20 @@ class InputBatchSender extends InputPolicy {
|
||||
setInput(
|
||||
nameType: string,
|
||||
value: unknown,
|
||||
opts: { priority: priorityType }
|
||||
opts: { priority: EventPriority }
|
||||
): void {
|
||||
this.pendingData[nameType] = value;
|
||||
|
||||
if (!this.reentrant) {
|
||||
if (opts.priority === "event") {
|
||||
this.$sendNow();
|
||||
this._sendNow();
|
||||
} else if (!this.timerId) {
|
||||
this.timerId = setTimeout(this.$sendNow.bind(this), 0);
|
||||
this.timerId = setTimeout(this._sendNow.bind(this), 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private $sendNow(): void {
|
||||
private _sendNow(): void {
|
||||
if (this.reentrant) {
|
||||
console.trace("Unexpected reentrancy in InputBatchSender!");
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { priorityType, InputPolicy } from "./InputPolicy";
|
||||
import type { EventPriority } from "./inputPolicy";
|
||||
import { InputPolicy } from "./inputPolicy";
|
||||
import { hasOwnProperty } from "../utils";
|
||||
|
||||
class InputDeferDecorator extends InputPolicy {
|
||||
pendingInput: Record<
|
||||
string,
|
||||
{ value: unknown; opts: { priority: priorityType } }
|
||||
> = {};
|
||||
pendingInput: {
|
||||
[key: string]: { value: unknown; opts: { priority: EventPriority } };
|
||||
} = {};
|
||||
constructor(target: InputPolicy) {
|
||||
super();
|
||||
this.target = target;
|
||||
@@ -14,7 +14,7 @@ class InputDeferDecorator extends InputPolicy {
|
||||
setInput(
|
||||
nameType: string,
|
||||
value: unknown,
|
||||
opts: { priority: priorityType }
|
||||
opts: { priority: EventPriority }
|
||||
): void {
|
||||
if (/^\./.test(nameType)) this.target.setInput(nameType, value, opts);
|
||||
else this.pendingInput[nameType] = { value, opts };
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import $ from "jquery";
|
||||
import type { priorityType } from "./InputPolicy";
|
||||
import { InputPolicy } from "./InputPolicy";
|
||||
import type { EventPriority } from "./inputPolicy";
|
||||
import { InputPolicy } from "./inputPolicy";
|
||||
import type { InputBinding } from "../bindings";
|
||||
import type { ShinyEventInputChanged } from "../events/shinyEvents";
|
||||
import { splitInputNameType } from "./splitInputNameType";
|
||||
@@ -16,7 +16,7 @@ class InputEventDecorator extends InputPolicy {
|
||||
value: unknown,
|
||||
opts: {
|
||||
el: HTMLElement;
|
||||
priority: priorityType;
|
||||
priority: EventPriority;
|
||||
binding: InputBinding;
|
||||
}
|
||||
): void {
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
import { priorityType, InputPolicy } from "./InputPolicy";
|
||||
import type { EventPriority } from "./inputPolicy";
|
||||
import { InputPolicy } from "./inputPolicy";
|
||||
import { hasOwnProperty } from "../utils";
|
||||
import { splitInputNameType } from "./splitInputNameType";
|
||||
|
||||
type lastSentValuesType = Record<string, Record<string, string>>;
|
||||
type LastSentValues = { [key: string]: { [key: string]: string } };
|
||||
|
||||
class InputNoResendDecorator extends InputPolicy {
|
||||
lastSentValues: lastSentValuesType;
|
||||
lastSentValues: LastSentValues;
|
||||
|
||||
constructor(target: InputPolicy, initialValues: lastSentValuesType = {}) {
|
||||
constructor(target: InputPolicy, initialValues: LastSentValues = {}) {
|
||||
super();
|
||||
this.target = target;
|
||||
this.reset(initialValues);
|
||||
@@ -16,7 +17,7 @@ class InputNoResendDecorator extends InputPolicy {
|
||||
setInput(
|
||||
nameType: string,
|
||||
value: unknown,
|
||||
opts: { priority: priorityType }
|
||||
opts: { priority: EventPriority }
|
||||
): void {
|
||||
const { name: inputName, inputType: inputType } =
|
||||
splitInputNameType(nameType);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
type priorityType = "immediate" | "deferred" | "event";
|
||||
type EventPriority = "deferred" | "event" | "immediate";
|
||||
|
||||
// Schedules data to be sent to shinyapp at the next setTimeout(0).
|
||||
// Batches multiple input calls into one websocket message.
|
||||
@@ -8,7 +8,7 @@ class InputPolicy {
|
||||
setInput(
|
||||
name: string,
|
||||
value: unknown,
|
||||
opts: { priority: priorityType }
|
||||
opts: { priority: EventPriority }
|
||||
): void {
|
||||
throw "not implemented";
|
||||
name;
|
||||
@@ -18,4 +18,4 @@ class InputPolicy {
|
||||
}
|
||||
|
||||
export { InputPolicy };
|
||||
export type { priorityType };
|
||||
export type { EventPriority };
|
||||
@@ -1,8 +1,9 @@
|
||||
import { priorityType, InputPolicy } from "./InputPolicy";
|
||||
import type { EventPriority } from "./inputPolicy";
|
||||
import { InputPolicy } from "./inputPolicy";
|
||||
import { Debouncer, Invoker, Throttler } from "../time";
|
||||
import { splitInputNameType } from "./splitInputNameType";
|
||||
|
||||
type RatePolicyModes = "direct" | "debounce" | "throttle";
|
||||
type RatePolicyModes = "debounce" | "direct" | "throttle";
|
||||
class InputRateDecorator extends InputPolicy {
|
||||
inputRatePolicies = {};
|
||||
|
||||
@@ -20,11 +21,11 @@ class InputRateDecorator extends InputPolicy {
|
||||
setInput(
|
||||
nameType: string,
|
||||
value: unknown,
|
||||
opts: { priority: priorityType }
|
||||
opts: { priority: EventPriority }
|
||||
): void {
|
||||
const { name: inputName } = splitInputNameType(nameType);
|
||||
|
||||
this.$ensureInit(inputName);
|
||||
this._ensureInit(inputName);
|
||||
|
||||
if (opts.priority !== "deferred")
|
||||
this.inputRatePolicies[inputName].immediateCall(nameType, value, opts);
|
||||
@@ -38,28 +39,28 @@ class InputRateDecorator extends InputPolicy {
|
||||
const { name: inputName } = splitInputNameType(nameType);
|
||||
|
||||
if (mode === "direct") {
|
||||
this.inputRatePolicies[inputName] = new Invoker(this, this.$doSetInput);
|
||||
this.inputRatePolicies[inputName] = new Invoker(this, this._doSetInput);
|
||||
} else if (mode === "debounce") {
|
||||
this.inputRatePolicies[inputName] = new Debouncer(
|
||||
this,
|
||||
this.$doSetInput,
|
||||
this._doSetInput,
|
||||
millis
|
||||
);
|
||||
} else if (mode === "throttle") {
|
||||
this.inputRatePolicies[inputName] = new Throttler(
|
||||
this,
|
||||
this.$doSetInput,
|
||||
this._doSetInput,
|
||||
millis
|
||||
);
|
||||
}
|
||||
}
|
||||
private $ensureInit(name: string): void {
|
||||
private _ensureInit(name: string): void {
|
||||
if (!(name in this.inputRatePolicies)) this.setRatePolicy(name, "direct");
|
||||
}
|
||||
private $doSetInput(
|
||||
private _doSetInput(
|
||||
nameType: string,
|
||||
value: unknown,
|
||||
opts: { priority: priorityType }
|
||||
opts: { priority: EventPriority }
|
||||
): void {
|
||||
this.target.setInput(nameType, value, opts);
|
||||
}
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
import $ from "jquery";
|
||||
import { priorityType, InputPolicy } from "./InputPolicy";
|
||||
import type { EventPriority, InputPolicy } from "./inputPolicy";
|
||||
|
||||
type MaybeInputOpts = {
|
||||
priority?: priorityType;
|
||||
priority?: EventPriority;
|
||||
binding?: unknown;
|
||||
el?: HTMLElement;
|
||||
};
|
||||
|
||||
// Merge opts with defaults, and return a new object.
|
||||
function addDefaultInputOpts<T>(opts?: T & MaybeInputOpts): T & {
|
||||
priority: priorityType;
|
||||
function addDefaultInputOpts<T>(opts?: MaybeInputOpts & T): T & {
|
||||
priority: EventPriority;
|
||||
binding: unknown;
|
||||
el?: HTMLElement;
|
||||
} {
|
||||
@@ -48,7 +48,7 @@ class InputValidateDecorator {
|
||||
setInput = function <T>(
|
||||
nameType: string,
|
||||
value: unknown,
|
||||
opts?: T & MaybeInputOpts
|
||||
opts?: MaybeInputOpts & T
|
||||
): void {
|
||||
if (!nameType) throw "Can't set input with empty name.";
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import $ from "jquery";
|
||||
import type { InputBinding, OutputBinding } from "../bindings";
|
||||
import { OutputBindingAdapter } from "../bindings/output_adapter";
|
||||
import { OutputBindingAdapter } from "../bindings/outputAdapter";
|
||||
import type { BindingRegistry } from "../bindings/registry";
|
||||
import type {
|
||||
InputRateDecorator,
|
||||
@@ -11,7 +11,7 @@ import { sendImageSizeFns } from "./sendImageSize";
|
||||
|
||||
const boundInputs = {};
|
||||
|
||||
type bindScope = HTMLElement | JQuery<HTMLElement>;
|
||||
type BindScope = HTMLElement | JQuery<HTMLElement>;
|
||||
|
||||
// todo make sure allowDeferred can NOT be supplied and still work
|
||||
function valueChangeCallback(inputs, binding, el, allowDeferred) {
|
||||
@@ -33,7 +33,7 @@ function valueChangeCallback(inputs, binding, el, allowDeferred) {
|
||||
}
|
||||
}
|
||||
|
||||
type bindInputsCtx = {
|
||||
type BindInputsCtx = {
|
||||
inputs: InputValidateDecorator;
|
||||
inputsRate: InputRateDecorator;
|
||||
inputBindings: BindingRegistry<InputBinding>;
|
||||
@@ -43,15 +43,14 @@ type bindInputsCtx = {
|
||||
initDeferredIframes: () => void;
|
||||
};
|
||||
function bindInputs(
|
||||
shinyCtx: bindInputsCtx,
|
||||
scope: bindScope = document.documentElement
|
||||
): Record<
|
||||
string,
|
||||
{
|
||||
shinyCtx: BindInputsCtx,
|
||||
scope: BindScope = document.documentElement
|
||||
): {
|
||||
[key: string]: {
|
||||
value: unknown;
|
||||
opts: { immediate: boolean; binding: InputBinding; el: HTMLElement };
|
||||
}
|
||||
> {
|
||||
};
|
||||
} {
|
||||
const { inputs, inputsRate, inputBindings } = shinyCtx;
|
||||
const bindings = inputBindings.getBindings();
|
||||
|
||||
@@ -125,8 +124,8 @@ function bindOutputs(
|
||||
sendOutputHiddenState,
|
||||
maybeAddThemeObserver,
|
||||
outputBindings,
|
||||
}: bindInputsCtx,
|
||||
scope: bindScope = document.documentElement
|
||||
}: BindInputsCtx,
|
||||
scope: BindScope = document.documentElement
|
||||
): void {
|
||||
const $scope = $(scope);
|
||||
|
||||
@@ -183,7 +182,7 @@ function bindOutputs(
|
||||
}
|
||||
|
||||
function unbindInputs(
|
||||
scope: bindScope = document.documentElement,
|
||||
scope: BindScope = document.documentElement,
|
||||
includeSelf = false
|
||||
) {
|
||||
const inputs: Array<HTMLElement | JQuery<HTMLElement>> = $(scope)
|
||||
@@ -213,8 +212,8 @@ function unbindInputs(
|
||||
}
|
||||
}
|
||||
function unbindOutputs(
|
||||
{ sendOutputHiddenState }: bindInputsCtx,
|
||||
scope: bindScope = document.documentElement,
|
||||
{ sendOutputHiddenState }: BindInputsCtx,
|
||||
scope: BindScope = document.documentElement,
|
||||
includeSelf = false
|
||||
) {
|
||||
const outputs: Array<HTMLElement | JQuery<HTMLElement>> = $(scope)
|
||||
@@ -248,29 +247,31 @@ function unbindOutputs(
|
||||
setTimeout(sendOutputHiddenState, 0);
|
||||
}
|
||||
|
||||
// (Named used before TS conversion)
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
function _bindAll(
|
||||
shinyCtx: bindInputsCtx,
|
||||
scope: bindScope
|
||||
shinyCtx: BindInputsCtx,
|
||||
scope: BindScope
|
||||
): ReturnType<typeof bindInputs> {
|
||||
bindOutputs(shinyCtx, scope);
|
||||
return bindInputs(shinyCtx, scope);
|
||||
}
|
||||
function unbindAll(
|
||||
shinyCtx: bindInputsCtx,
|
||||
scope: bindScope,
|
||||
shinyCtx: BindInputsCtx,
|
||||
scope: BindScope,
|
||||
includeSelf = false
|
||||
): void {
|
||||
unbindInputs(scope, includeSelf);
|
||||
unbindOutputs(shinyCtx, scope, includeSelf);
|
||||
}
|
||||
function bindAll(shinyCtx: bindInputsCtx, scope: bindScope): void {
|
||||
function bindAll(shinyCtx: BindInputsCtx, scope: BindScope): void {
|
||||
// _bindAll returns input values; it doesn't send them to the server.
|
||||
// Shiny.bindAll needs to send the values to the server.
|
||||
const currentInputItems = _bindAll(shinyCtx, scope);
|
||||
|
||||
const inputs = shinyCtx.inputs;
|
||||
|
||||
$.each(currentInputItems, function (name, item) {
|
||||
$.each(currentInputItems, function (name: string, item) {
|
||||
inputs.setInput(name, item.value, item.opts);
|
||||
});
|
||||
|
||||
@@ -283,4 +284,4 @@ function bindAll(shinyCtx: bindInputsCtx, scope: bindScope): void {
|
||||
|
||||
export { unbindAll, bindAll, _bindAll };
|
||||
|
||||
export type { bindScope, bindInputsCtx };
|
||||
export type { BindScope, BindInputsCtx };
|
||||
|
||||
@@ -8,25 +8,28 @@ import { showModal, removeModal } from "./modal";
|
||||
import { showReconnectDialog, hideReconnectDialog } from "./reconnectDialog";
|
||||
import { renderContent, renderDependencies, renderHtml } from "./render";
|
||||
import { initShiny } from "./init";
|
||||
import {
|
||||
import type {
|
||||
shinyBindAll,
|
||||
shinyForgetLastInputValue,
|
||||
shinySetInputValue,
|
||||
shinyInitializeInputs,
|
||||
shinyUnbindAll,
|
||||
setFileInputBinding,
|
||||
} from "./initedMethods";
|
||||
import { addCustomMessageHandler, HandlerType, ShinyApp } from "./shinyapp";
|
||||
import { setFileInputBinding } from "./initedMethods";
|
||||
import type { Handler, ShinyApp } from "./shinyapp";
|
||||
import { addCustomMessageHandler } from "./shinyapp";
|
||||
import { initInputBindings } from "../bindings/input";
|
||||
import { initOutputBindings } from "../bindings/output";
|
||||
|
||||
interface ShinyType {
|
||||
interface Shiny {
|
||||
version: string;
|
||||
$escape: typeof $escape;
|
||||
compareVersion: typeof compareVersion;
|
||||
inputBindings: ReturnType<typeof initInputBindings>["inputBindings"];
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
InputBinding: typeof InputBinding;
|
||||
outputBindings: ReturnType<typeof initOutputBindings>["outputBindings"];
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
OutputBinding: typeof OutputBinding;
|
||||
resetBrush: typeof resetBrush;
|
||||
notifications: {
|
||||
@@ -53,17 +56,17 @@ interface ShinyType {
|
||||
|
||||
// Eventually deprecate
|
||||
// For old-style custom messages - should deprecate and migrate to new
|
||||
oncustommessage?: HandlerType;
|
||||
oncustommessage?: Handler;
|
||||
}
|
||||
|
||||
let Shiny: ShinyType;
|
||||
let windowShiny: Shiny;
|
||||
|
||||
function setShiny(Shiny_: ShinyType): void {
|
||||
Shiny = Shiny_;
|
||||
function setShiny(windowShiny_: Shiny): void {
|
||||
windowShiny = windowShiny_;
|
||||
|
||||
// `process.env.SHINY_VERSION` is overwritten to the Shiny version at build time.
|
||||
// During testing, the `Shiny.version` will be `"development"`
|
||||
Shiny.version = process.env.SHINY_VERSION || "development";
|
||||
windowShiny.version = process.env.SHINY_VERSION || "development";
|
||||
|
||||
const { inputBindings, fileInputBinding } = initInputBindings();
|
||||
const { outputBindings } = initOutputBindings();
|
||||
@@ -71,32 +74,35 @@ function setShiny(Shiny_: ShinyType): void {
|
||||
// set variable to be retrieved later
|
||||
setFileInputBinding(fileInputBinding);
|
||||
|
||||
Shiny.$escape = $escape;
|
||||
Shiny.compareVersion = compareVersion;
|
||||
Shiny.inputBindings = inputBindings;
|
||||
Shiny.InputBinding = InputBinding;
|
||||
Shiny.outputBindings = outputBindings;
|
||||
Shiny.OutputBinding = OutputBinding;
|
||||
Shiny.resetBrush = resetBrush;
|
||||
Shiny.notifications = { show: showNotification, remove: removeNotification };
|
||||
Shiny.modal = { show: showModal, remove: removeModal };
|
||||
windowShiny.$escape = $escape;
|
||||
windowShiny.compareVersion = compareVersion;
|
||||
windowShiny.inputBindings = inputBindings;
|
||||
windowShiny.InputBinding = InputBinding;
|
||||
windowShiny.outputBindings = outputBindings;
|
||||
windowShiny.OutputBinding = OutputBinding;
|
||||
windowShiny.resetBrush = resetBrush;
|
||||
windowShiny.notifications = {
|
||||
show: showNotification,
|
||||
remove: removeNotification,
|
||||
};
|
||||
windowShiny.modal = { show: showModal, remove: removeModal };
|
||||
|
||||
Shiny.addCustomMessageHandler = addCustomMessageHandler;
|
||||
Shiny.showReconnectDialog = showReconnectDialog;
|
||||
Shiny.hideReconnectDialog = hideReconnectDialog;
|
||||
Shiny.renderDependencies = renderDependencies;
|
||||
Shiny.renderContent = renderContent;
|
||||
Shiny.renderHtml = renderHtml;
|
||||
windowShiny.addCustomMessageHandler = addCustomMessageHandler;
|
||||
windowShiny.showReconnectDialog = showReconnectDialog;
|
||||
windowShiny.hideReconnectDialog = hideReconnectDialog;
|
||||
windowShiny.renderDependencies = renderDependencies;
|
||||
windowShiny.renderContent = renderContent;
|
||||
windowShiny.renderHtml = renderHtml;
|
||||
|
||||
$(function () {
|
||||
// Init Shiny a little later than document ready, so user code can
|
||||
// run first (i.e. to register bindings)
|
||||
setTimeout(function () {
|
||||
initShiny(Shiny);
|
||||
initShiny(windowShiny);
|
||||
}, 1);
|
||||
});
|
||||
}
|
||||
|
||||
export { Shiny, setShiny };
|
||||
export { windowShiny, setShiny };
|
||||
|
||||
export type { ShinyType };
|
||||
export type { Shiny };
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import $ from "jquery";
|
||||
import { ShinyType } from ".";
|
||||
import type { Shiny } from ".";
|
||||
import {
|
||||
InputBatchSender,
|
||||
InputDeferDecorator,
|
||||
@@ -7,8 +7,8 @@ import {
|
||||
InputNoResendDecorator,
|
||||
InputRateDecorator,
|
||||
InputValidateDecorator,
|
||||
priorityType,
|
||||
} from "../inputPolicies";
|
||||
import type { EventPriority } from "../inputPolicies";
|
||||
import { addDefaultInputOpts } from "../inputPolicies/inputValidateDecorator";
|
||||
import { debounce, Debouncer } from "../time";
|
||||
import {
|
||||
@@ -18,7 +18,8 @@ import {
|
||||
mapValues,
|
||||
pixelRatio,
|
||||
} from "../utils";
|
||||
import { bindAll, bindInputsCtx, bindScope, unbindAll, _bindAll } from "./bind";
|
||||
import { bindAll, unbindAll, _bindAll } from "./bind";
|
||||
import type { BindInputsCtx, BindScope } from "./bind";
|
||||
import { setShinyObj } from "./initedMethods";
|
||||
import { registerDependency } from "./render";
|
||||
import { sendImageSizeFns } from "./sendImageSize";
|
||||
@@ -26,11 +27,11 @@ import { ShinyApp } from "./shinyapp";
|
||||
import { registerNames as singletonsRegisterNames } from "./singletons";
|
||||
|
||||
// "init_shiny.js"
|
||||
function initShiny(Shiny: ShinyType): void {
|
||||
setShinyObj(Shiny);
|
||||
const shinyapp = (Shiny.shinyapp = new ShinyApp());
|
||||
function initShiny(windowShiny: Shiny): void {
|
||||
setShinyObj(windowShiny);
|
||||
const shinyapp = (windowShiny.shinyapp = new ShinyApp());
|
||||
|
||||
Shiny.progressHandlers = shinyapp.progressHandlers;
|
||||
windowShiny.progressHandlers = shinyapp.progressHandlers;
|
||||
|
||||
const inputBatchSender = new InputBatchSender(shinyapp);
|
||||
const inputsNoResend = new InputNoResendDecorator(inputBatchSender);
|
||||
@@ -57,10 +58,10 @@ function initShiny(Shiny: ShinyType): void {
|
||||
|
||||
const inputs = new InputValidateDecorator(target);
|
||||
|
||||
Shiny.setInputValue = Shiny.onInputChange = function (
|
||||
windowShiny.setInputValue = windowShiny.onInputChange = function (
|
||||
name: string,
|
||||
value: unknown,
|
||||
opts?: { priority?: priorityType }
|
||||
opts?: { priority?: EventPriority }
|
||||
): void {
|
||||
const newOpts = addDefaultInputOpts(opts);
|
||||
|
||||
@@ -73,15 +74,15 @@ function initShiny(Shiny: ShinyType): void {
|
||||
// `forgetLastInputValue` tells Shiny that the very next call to
|
||||
// `setInputValue` for this input id shouldn't be ignored, even if it
|
||||
// is a dupe of the existing value.
|
||||
Shiny.forgetLastInputValue = function (name) {
|
||||
windowShiny.forgetLastInputValue = function (name) {
|
||||
inputsNoResend.forget(name);
|
||||
};
|
||||
|
||||
// MUST be called after `setShiny()`
|
||||
const inputBindings = Shiny.inputBindings;
|
||||
const outputBindings = Shiny.outputBindings;
|
||||
const inputBindings = windowShiny.inputBindings;
|
||||
const outputBindings = windowShiny.outputBindings;
|
||||
|
||||
function shinyBindCtx(): bindInputsCtx {
|
||||
function shinyBindCtx(): BindInputsCtx {
|
||||
return {
|
||||
inputs,
|
||||
inputsRate,
|
||||
@@ -93,16 +94,16 @@ function initShiny(Shiny: ShinyType): void {
|
||||
};
|
||||
}
|
||||
|
||||
Shiny.bindAll = function (scope: bindScope) {
|
||||
windowShiny.bindAll = function (scope: BindScope) {
|
||||
bindAll(shinyBindCtx(), scope);
|
||||
};
|
||||
Shiny.unbindAll = function (scope: bindScope, includeSelf = false) {
|
||||
windowShiny.unbindAll = function (scope: BindScope, includeSelf = false) {
|
||||
unbindAll(shinyBindCtx(), scope, includeSelf);
|
||||
};
|
||||
|
||||
// Calls .initialize() for all of the input objects in all input bindings,
|
||||
// in the given scope.
|
||||
function initializeInputs(scope: bindScope = document.documentElement) {
|
||||
function initializeInputs(scope: BindScope = document.documentElement) {
|
||||
const bindings = inputBindings.getBindings();
|
||||
|
||||
// Iterate over all bindings
|
||||
@@ -123,7 +124,7 @@ function initShiny(Shiny: ShinyType): void {
|
||||
}
|
||||
}
|
||||
}
|
||||
Shiny.initializeInputs = initializeInputs;
|
||||
windowShiny.initializeInputs = initializeInputs;
|
||||
|
||||
function getIdFromEl(el: HTMLElement) {
|
||||
const $el = $(el);
|
||||
@@ -516,7 +517,7 @@ function initShiny(Shiny: ShinyType): void {
|
||||
initDeferredIframes();
|
||||
});
|
||||
|
||||
window.console.log("Shiny version: ", Shiny.version);
|
||||
window.console.log("Shiny version: ", windowShiny.version);
|
||||
} // function initShiny()
|
||||
|
||||
// Give any deferred iframes a chance to load.
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import type { ShinyType } from ".";
|
||||
import type { Shiny } from ".";
|
||||
import type { FileInputBinding } from "../bindings/input/fileinput";
|
||||
import type { OutputBindingAdapter } from "../bindings/output_adapter";
|
||||
import type { priorityType } from "../inputPolicies";
|
||||
import type { bindScope } from "./bind";
|
||||
import type { HandlerType, ShinyApp } from "./shinyapp";
|
||||
import type { OutputBindingAdapter } from "../bindings/outputAdapter";
|
||||
import type { EventPriority } from "../inputPolicies";
|
||||
import type { BindScope } from "./bind";
|
||||
import type { Handler, ShinyApp } from "./shinyapp";
|
||||
|
||||
let fullShinyObj_: ShinyType = null;
|
||||
let fullShinyObj: Shiny = null;
|
||||
|
||||
function setShinyObj(shiny: ShinyType): void {
|
||||
fullShinyObj_ = shiny;
|
||||
function setShinyObj(shiny: Shiny): void {
|
||||
fullShinyObj = shiny;
|
||||
}
|
||||
|
||||
//// 2021/03: TypeScript Conversion note
|
||||
@@ -19,55 +19,55 @@ function setShinyObj(shiny: ShinyType): void {
|
||||
function shinySetInputValue(
|
||||
name: string,
|
||||
value: unknown,
|
||||
opts?: { priority?: priorityType }
|
||||
opts?: { priority?: EventPriority }
|
||||
): void {
|
||||
fullShinyObj_.setInputValue(name, value, opts);
|
||||
fullShinyObj.setInputValue(name, value, opts);
|
||||
}
|
||||
function shinyShinyApp(): ShinyApp {
|
||||
return fullShinyObj_.shinyapp;
|
||||
return fullShinyObj.shinyapp;
|
||||
}
|
||||
function setShinyUser(user: string): void {
|
||||
fullShinyObj_.user = user;
|
||||
fullShinyObj.user = user;
|
||||
}
|
||||
function shinyForgetLastInputValue(name: string): void {
|
||||
fullShinyObj_.forgetLastInputValue(name);
|
||||
fullShinyObj.forgetLastInputValue(name);
|
||||
}
|
||||
function shinyBindAll(scope: bindScope): void {
|
||||
fullShinyObj_.bindAll(scope);
|
||||
function shinyBindAll(scope: BindScope): void {
|
||||
fullShinyObj.bindAll(scope);
|
||||
}
|
||||
function shinyUnbindAll(scope: bindScope, includeSelf = false): void {
|
||||
fullShinyObj_.unbindAll(scope, includeSelf);
|
||||
function shinyUnbindAll(scope: BindScope, includeSelf = false): void {
|
||||
fullShinyObj.unbindAll(scope, includeSelf);
|
||||
}
|
||||
function shinyInitializeInputs(scope: bindScope): void {
|
||||
fullShinyObj_.initializeInputs(scope);
|
||||
function shinyInitializeInputs(scope: BindScope): void {
|
||||
fullShinyObj.initializeInputs(scope);
|
||||
}
|
||||
|
||||
function shinyAppBindOutput(id: string, binding: OutputBindingAdapter): void {
|
||||
fullShinyObj_.shinyapp.bindOutput(id, binding);
|
||||
fullShinyObj.shinyapp.bindOutput(id, binding);
|
||||
}
|
||||
|
||||
function shinyAppUnbindOutput(
|
||||
id: string,
|
||||
binding: OutputBindingAdapter
|
||||
): boolean {
|
||||
return fullShinyObj_.shinyapp.unbindOutput(id, binding);
|
||||
return fullShinyObj.shinyapp.unbindOutput(id, binding);
|
||||
}
|
||||
|
||||
function getShinyOnCustomMessage(): null | HandlerType {
|
||||
return fullShinyObj_.oncustommessage;
|
||||
function getShinyOnCustomMessage(): Handler | null {
|
||||
return fullShinyObj.oncustommessage;
|
||||
}
|
||||
|
||||
let fileInputBinding_: FileInputBinding;
|
||||
let fileInputBinding: FileInputBinding;
|
||||
|
||||
function getFileInputBinding(): FileInputBinding {
|
||||
return fileInputBinding_;
|
||||
return fileInputBinding;
|
||||
}
|
||||
function setFileInputBinding(fileInputBinding: FileInputBinding): void {
|
||||
fileInputBinding_ = fileInputBinding;
|
||||
function setFileInputBinding(fileInputBinding_: FileInputBinding): void {
|
||||
fileInputBinding = fileInputBinding_;
|
||||
}
|
||||
|
||||
function getShinyCreateWebsocket(): (() => WebSocket) | void {
|
||||
return fullShinyObj_.createSocket;
|
||||
return fullShinyObj.createSocket;
|
||||
}
|
||||
|
||||
export {
|
||||
|
||||
@@ -19,12 +19,12 @@ function show({
|
||||
if (!id) id = randomId();
|
||||
|
||||
// Create panel if necessary
|
||||
_createPanel();
|
||||
createPanel();
|
||||
|
||||
// Get existing DOM element for this ID, or create if needed.
|
||||
let $notification = _get(id);
|
||||
let $notification = get(id);
|
||||
|
||||
if ($notification.length === 0) $notification = _create(id);
|
||||
if ($notification.length === 0) $notification = create(id);
|
||||
|
||||
// Render html and dependencies
|
||||
const newHtml =
|
||||
@@ -61,34 +61,34 @@ function show({
|
||||
// If duration was provided, schedule removal. If not, clear existing
|
||||
// removal callback (this happens if a message was first added with
|
||||
// a duration, and then updated with no duration).
|
||||
if (duration) _addRemovalCallback(id, duration);
|
||||
else _clearRemovalCallback(id);
|
||||
if (duration) addRemovalCallback(id, duration);
|
||||
else clearRemovalCallback(id);
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
// TODO-barret - Should `id` be required? (some places do not supply one)
|
||||
function remove(id?: string): void {
|
||||
_get(id).fadeOut(fadeDuration, function () {
|
||||
get(id).fadeOut(fadeDuration, function () {
|
||||
shinyUnbindAll(this);
|
||||
$(this).remove();
|
||||
|
||||
// If no more notifications, remove the panel from the DOM.
|
||||
if (_ids().length === 0) {
|
||||
_getPanel().remove();
|
||||
if (ids().length === 0) {
|
||||
getPanel().remove();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Returns an individual notification DOM object (wrapped in jQuery).
|
||||
function _get(id?: string) {
|
||||
function get(id?: string) {
|
||||
if (!id) return null;
|
||||
return _getPanel().find("#shiny-notification-" + $escape(id));
|
||||
return getPanel().find("#shiny-notification-" + $escape(id));
|
||||
}
|
||||
|
||||
// Return array of all notification IDs
|
||||
function _ids() {
|
||||
return _getPanel()
|
||||
function ids() {
|
||||
return getPanel()
|
||||
.find(".shiny-notification")
|
||||
.map(function () {
|
||||
return this.id.replace(/shiny-notification-/, "");
|
||||
@@ -97,14 +97,14 @@ function _ids() {
|
||||
}
|
||||
|
||||
// Returns the notification panel DOM object (wrapped in jQuery).
|
||||
function _getPanel() {
|
||||
function getPanel() {
|
||||
return $("#shiny-notification-panel");
|
||||
}
|
||||
|
||||
// Create notifications panel and return the jQuery object. If the DOM
|
||||
// element already exists, just return it.
|
||||
function _createPanel() {
|
||||
const $panel = _getPanel();
|
||||
function createPanel() {
|
||||
const $panel = getPanel();
|
||||
|
||||
if ($panel.length > 0) return $panel;
|
||||
|
||||
@@ -115,8 +115,8 @@ function _createPanel() {
|
||||
|
||||
// Create a notification DOM element and return the jQuery object. If the
|
||||
// DOM element already exists for the ID, just return it without creating.
|
||||
function _create(id) {
|
||||
let $notification = _get(id);
|
||||
function create(id) {
|
||||
let $notification = get(id);
|
||||
|
||||
if ($notification.length === 0) {
|
||||
$notification = $(
|
||||
@@ -132,29 +132,29 @@ function _create(id) {
|
||||
remove(id);
|
||||
});
|
||||
|
||||
_getPanel().append($notification);
|
||||
getPanel().append($notification);
|
||||
}
|
||||
|
||||
return $notification;
|
||||
}
|
||||
|
||||
// Add a callback to remove a notification after a delay in ms.
|
||||
function _addRemovalCallback(id, delay) {
|
||||
function addRemovalCallback(id, delay) {
|
||||
// If there's an existing removalCallback, clear it before adding the new
|
||||
// one.
|
||||
_clearRemovalCallback(id);
|
||||
clearRemovalCallback(id);
|
||||
|
||||
// Attach new removal callback
|
||||
const removalCallback = setTimeout(function () {
|
||||
remove(id);
|
||||
}, delay);
|
||||
|
||||
_get(id).data("removalCallback", removalCallback);
|
||||
get(id).data("removalCallback", removalCallback);
|
||||
}
|
||||
|
||||
// Clear a removal callback from a notification, if present.
|
||||
function _clearRemovalCallback(id) {
|
||||
const $notification = _get(id);
|
||||
function clearRemovalCallback(id) {
|
||||
const $notification = get(id);
|
||||
const oldRemovalCallback = $notification.data("removalCallback");
|
||||
|
||||
if (oldRemovalCallback) {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import $ from "jquery";
|
||||
import { asArray, hasOwnProperty } from "../utils";
|
||||
import { isIE } from "../utils/browser";
|
||||
import { bindScope } from "./bind";
|
||||
import type { BindScope } from "./bind";
|
||||
import {
|
||||
shinyBindAll,
|
||||
shinyInitializeInputs,
|
||||
@@ -10,9 +10,9 @@ import {
|
||||
import { sendImageSizeFns } from "./sendImageSize";
|
||||
|
||||
import { renderHtml as singletonsRenderHtml } from "./singletons";
|
||||
import type { WherePosition, RenderHtmlWherePosition } from "./singletons";
|
||||
import type { WherePosition } from "./singletons";
|
||||
|
||||
function renderDependencies(dependencies: null | Array<HtmlDep>): void {
|
||||
function renderDependencies(dependencies: HtmlDep[] | null): void {
|
||||
if (dependencies) {
|
||||
$.each(dependencies, function (i, dep) {
|
||||
renderDependency(dep);
|
||||
@@ -24,8 +24,8 @@ function renderDependencies(dependencies: null | Array<HtmlDep>): void {
|
||||
// inputs/outputs. `content` can be null, a string, or an object with
|
||||
// properties 'html' and 'deps'.
|
||||
function renderContent(
|
||||
el: bindScope,
|
||||
content: null | string | { html: string; deps?: Array<HtmlDep> },
|
||||
el: BindScope,
|
||||
content: string | { html: string; deps?: HtmlDep[] } | null,
|
||||
where: WherePosition = "replace"
|
||||
): void {
|
||||
if (where === "replace") {
|
||||
@@ -44,12 +44,9 @@ function renderContent(
|
||||
dependencies = content.deps || [];
|
||||
}
|
||||
|
||||
// @ts-expect-error; TODO-barret; The `where` values do not match.
|
||||
// The type definition is to have `InsertPosition` which does not align with `WherePosition` from above
|
||||
// type InsertPosition = "beforebegin" | "afterbegin" | "beforeend" | "afterend"
|
||||
renderHtml(html, el, dependencies, where);
|
||||
|
||||
let scope: bindScope = el;
|
||||
let scope: BindScope = el;
|
||||
|
||||
if (where === "replace") {
|
||||
shinyInitializeInputs(el);
|
||||
@@ -73,34 +70,33 @@ function renderContent(
|
||||
// Render HTML in a DOM element, inserting singletons into head as needed
|
||||
function renderHtml(
|
||||
html: string,
|
||||
el: bindScope,
|
||||
dependencies: Array<HtmlDep>,
|
||||
where: RenderHtmlWherePosition = "replace"
|
||||
el: BindScope,
|
||||
dependencies: HtmlDep[],
|
||||
where: WherePosition = "replace"
|
||||
): ReturnType<typeof singletonsRenderHtml> {
|
||||
renderDependencies(dependencies);
|
||||
return singletonsRenderHtml(html, el, where);
|
||||
}
|
||||
|
||||
type HtmlDepName = string;
|
||||
type HtmlDepVersion = string;
|
||||
type HtmlDep = {
|
||||
name: HtmlDepName;
|
||||
name: string;
|
||||
version: HtmlDepVersion;
|
||||
restyle?: boolean;
|
||||
src?: { href: string };
|
||||
meta?: string | Array<string>;
|
||||
stylesheet?: string | Array<string>;
|
||||
meta?: string[] | string;
|
||||
stylesheet?: string[] | string;
|
||||
script?:
|
||||
| Array<{ [key: string]: string }>
|
||||
| string[]
|
||||
| string
|
||||
| Array<string>
|
||||
| Record<string, string>
|
||||
| Array<Record<string, string>>;
|
||||
attachment?: string | Array<string> | Record<string, string>;
|
||||
| { [key: string]: string };
|
||||
attachment?: string[] | string | { [key: string]: string };
|
||||
head?: string;
|
||||
};
|
||||
const htmlDependencies: Record<HtmlDepName, HtmlDepVersion> = {};
|
||||
const htmlDependencies: { [key: string]: HtmlDepVersion } = {};
|
||||
|
||||
function registerDependency(name: HtmlDepName, version: HtmlDepVersion): void {
|
||||
function registerDependency(name: string, version: HtmlDepVersion): void {
|
||||
htmlDependencies[name] = version;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { InputBatchSender } from "../inputPolicies";
|
||||
import type { InputBatchSender } from "../inputPolicies";
|
||||
import { debounce, Debouncer } from "../time";
|
||||
|
||||
class SendImageSize {
|
||||
|
||||
@@ -20,36 +20,36 @@ import { renderContent, renderHtml } from "./render";
|
||||
import type { HtmlDep } from "./render";
|
||||
import { hideReconnectDialog, showReconnectDialog } from "./reconnectDialog";
|
||||
import { resetBrush } from "../imageutils/resetBrush";
|
||||
import { OutputBindingAdapter } from "../bindings/output_adapter";
|
||||
import type { OutputBindingAdapter } from "../bindings/outputAdapter";
|
||||
import type {
|
||||
ShinyEventError,
|
||||
ShinyEventMessage,
|
||||
ShinyEventValue,
|
||||
ShinyEventUpdateInput,
|
||||
} from "../events/shinyEvents";
|
||||
import { InputBinding } from "../bindings";
|
||||
import type { InputBinding } from "../bindings";
|
||||
import { indirectEval } from "../utils/eval";
|
||||
import type { WherePosition } from "./singletons";
|
||||
import type { UploadInitValue, UploadEndValue } from "../file/FileProcessor";
|
||||
import type { UploadInitValue, UploadEndValue } from "../file/fileProcessor";
|
||||
|
||||
type ResponseValue = UploadInitValue | UploadEndValue;
|
||||
type HandlerType = (
|
||||
msg: Record<string, unknown> | Array<unknown> | boolean | string
|
||||
type ResponseValue = UploadEndValue | UploadInitValue;
|
||||
type Handler = (
|
||||
msg: unknown[] | boolean | string | { [key: string]: unknown }
|
||||
) => void;
|
||||
|
||||
type ShinyWebSocket = WebSocket & {
|
||||
allowReconnect?: boolean;
|
||||
};
|
||||
|
||||
type errorsMessageValue = {
|
||||
type ErrorsMessageValue = {
|
||||
message: string;
|
||||
call: Array<string>;
|
||||
type?: Array<string>;
|
||||
call: string[];
|
||||
type?: string[];
|
||||
};
|
||||
|
||||
type OnSuccessRequest = (value: ResponseValue) => void;
|
||||
type OnErrorRequest = (err: string) => void;
|
||||
type InputValuesType = Record<string, unknown>;
|
||||
type InputValues = { [key: string]: unknown };
|
||||
|
||||
//// 2021/03 - TypeScript conversion note:
|
||||
// These four variables were moved from being internally defined to being defined globally within the file.
|
||||
@@ -71,8 +71,23 @@ const messageHandlers = {};
|
||||
const customMessageHandlerOrder = [];
|
||||
const customMessageHandlers = {};
|
||||
|
||||
// Adds Shiny (internal) message handler
|
||||
function addMessageHandler(type: string, handler: Handler) {
|
||||
if (messageHandlers[type]) {
|
||||
throw 'handler for message of type "' + type + '" already added.';
|
||||
}
|
||||
if (typeof handler !== "function") {
|
||||
throw "handler must be a function.";
|
||||
}
|
||||
if (handler.length !== 1) {
|
||||
throw "handler must be a function that takes one argument.";
|
||||
}
|
||||
messageHandlerOrder.push(type);
|
||||
messageHandlers[type] = handler;
|
||||
}
|
||||
|
||||
// Adds custom message handler - this one is exposed to the user
|
||||
function addCustomMessageHandler(type: string, handler: HandlerType): void {
|
||||
function addCustomMessageHandler(type: string, handler: Handler): void {
|
||||
// Remove any previously defined handlers so that only the most recent one
|
||||
// will be called
|
||||
if (customMessageHandlers[type]) {
|
||||
@@ -105,35 +120,34 @@ class ShinyApp {
|
||||
} = null;
|
||||
|
||||
// Cached input values
|
||||
$inputValues: InputValuesType = {};
|
||||
$inputValues: InputValues = {};
|
||||
|
||||
// Input values at initialization (and reconnect)
|
||||
$initialInput: InputValuesType;
|
||||
$initialInput: InputValues;
|
||||
|
||||
// Output bindings
|
||||
$bindings: Record<string, OutputBindingAdapter> = {};
|
||||
$bindings: { [key: string]: OutputBindingAdapter } = {};
|
||||
|
||||
// Cached values/errors
|
||||
$values = {};
|
||||
$errors: Record<string, errorsMessageValue> = {};
|
||||
$errors: { [key: string]: ErrorsMessageValue } = {};
|
||||
|
||||
// Conditional bindings (show/hide element based on expression)
|
||||
$conditionals = {};
|
||||
|
||||
$pendingMessages: Array<string> = [];
|
||||
$activeRequests: Record<
|
||||
number,
|
||||
{ onSuccess: OnSuccessRequest; onError: OnErrorRequest }
|
||||
> = {};
|
||||
$pendingMessages: string[] = [];
|
||||
$activeRequests: {
|
||||
[key: number]: { onSuccess: OnSuccessRequest; onError: OnErrorRequest };
|
||||
} = {};
|
||||
$nextRequestId = 0;
|
||||
|
||||
$allowReconnect: boolean | "force" = false;
|
||||
|
||||
constructor() {
|
||||
this.init();
|
||||
this._init();
|
||||
}
|
||||
|
||||
connect(initialInput: InputValuesType): void {
|
||||
connect(initialInput: InputValues): void {
|
||||
if (this.$socket)
|
||||
throw "Connect was already called on this application object";
|
||||
|
||||
@@ -245,7 +259,7 @@ class ShinyApp {
|
||||
return socket;
|
||||
}
|
||||
|
||||
sendInput(values: InputValuesType): void {
|
||||
sendInput(values: InputValues): void {
|
||||
const msg = JSON.stringify({
|
||||
method: "update",
|
||||
data: values,
|
||||
@@ -356,10 +370,10 @@ class ShinyApp {
|
||||
// request. Strings will be encoded using UTF-8.
|
||||
makeRequest(
|
||||
method: string,
|
||||
args: Array<unknown>,
|
||||
args: unknown[],
|
||||
onSuccess: OnSuccessRequest,
|
||||
onError: OnErrorRequest,
|
||||
blobs: Array<Blob | ArrayBuffer | string>
|
||||
blobs: Array<ArrayBuffer | Blob | string>
|
||||
): void {
|
||||
let requestId = this.$nextRequestId;
|
||||
|
||||
@@ -429,7 +443,7 @@ class ShinyApp {
|
||||
}
|
||||
}
|
||||
|
||||
receiveError(name: string, error: errorsMessageValue): void {
|
||||
receiveError(name: string, error: ErrorsMessageValue): void {
|
||||
if (this.$errors[name] === error) return;
|
||||
|
||||
this.$errors[name] = error;
|
||||
@@ -496,8 +510,8 @@ class ShinyApp {
|
||||
// Narrows a scopeComponent -- an input or output object -- to one constrained
|
||||
// by nsPrefix. Returns a new object with keys removed and renamed as
|
||||
// necessary.
|
||||
private narrowScopeComponent<T>(
|
||||
scopeComponent: Record<string, T>,
|
||||
private _narrowScopeComponent<T>(
|
||||
scopeComponent: { [key: string]: T },
|
||||
nsPrefix: string | null
|
||||
) {
|
||||
return Object.keys(scopeComponent)
|
||||
@@ -513,11 +527,11 @@ class ShinyApp {
|
||||
//
|
||||
// Otherwise, returns a new object with keys in subComponents removed and
|
||||
// renamed as necessary.
|
||||
private narrowScope(scope, nsPrefix: string) {
|
||||
private _narrowScope(scope, nsPrefix: string) {
|
||||
if (nsPrefix) {
|
||||
return {
|
||||
input: this.narrowScopeComponent(scope.input, nsPrefix),
|
||||
output: this.narrowScopeComponent(scope.output, nsPrefix),
|
||||
input: this._narrowScopeComponent(scope.input, nsPrefix),
|
||||
output: this._narrowScopeComponent(scope.output, nsPrefix),
|
||||
};
|
||||
}
|
||||
return scope;
|
||||
@@ -557,7 +571,7 @@ class ShinyApp {
|
||||
}
|
||||
|
||||
const nsPrefix = el.attr("data-ns-prefix");
|
||||
const nsScope = this.narrowScope(scope, nsPrefix);
|
||||
const nsScope = this._narrowScope(scope, nsPrefix);
|
||||
const show = condFunc(nsScope);
|
||||
const showing = el.css("display") !== "none";
|
||||
|
||||
@@ -577,25 +591,10 @@ class ShinyApp {
|
||||
|
||||
// Message handler management functions =================================
|
||||
|
||||
// Adds Shiny (internal) message handler
|
||||
private addMessageHandler(type: string, handler: HandlerType) {
|
||||
if (messageHandlers[type]) {
|
||||
throw 'handler for message of type "' + type + '" already added.';
|
||||
}
|
||||
if (typeof handler !== "function") {
|
||||
throw "handler must be a function.";
|
||||
}
|
||||
if (handler.length !== 1) {
|
||||
throw "handler must be a function that takes one argument.";
|
||||
}
|
||||
messageHandlerOrder.push(type);
|
||||
messageHandlers[type] = handler;
|
||||
}
|
||||
|
||||
// // Added in shiny init method
|
||||
// Shiny.addCustomMessageHandler = addCustomMessageHandler;
|
||||
|
||||
dispatchMessage(data: string | ArrayBufferLike): void {
|
||||
dispatchMessage(data: ArrayBufferLike | string): void {
|
||||
let msgObj: ShinyEventMessage["message"] = {};
|
||||
|
||||
if (typeof data === "string") {
|
||||
@@ -632,12 +631,14 @@ class ShinyApp {
|
||||
this.$updateConditionals();
|
||||
}
|
||||
|
||||
// Message handlers =====================================================
|
||||
|
||||
// A function for sending messages to the appropriate handlers.
|
||||
// - msgObj: the object containing messages, with format {msgObj.foo, msObj.bar
|
||||
_sendMessagesToHandlers(
|
||||
msgObj: Record<string, unknown>,
|
||||
handlers: Record<string, HandlerType>,
|
||||
handlerOrder: Array<string>
|
||||
private _sendMessagesToHandlers(
|
||||
msgObj: { [key: string]: unknown },
|
||||
handlers: { [key: string]: Handler },
|
||||
handlerOrder: string[]
|
||||
): void {
|
||||
// Dispatch messages to handlers, if handler is present
|
||||
for (let i = 0; i < handlerOrder.length; i++) {
|
||||
@@ -651,27 +652,27 @@ class ShinyApp {
|
||||
}
|
||||
}
|
||||
|
||||
// Message handlers =====================================================
|
||||
private _init() {
|
||||
// Dev note:
|
||||
// * Use arrow functions to allow the Types to propagate.
|
||||
// * However, `_sendMessagesToHandlers()` will adjust the `this` context to the same _`this`_.
|
||||
|
||||
private init() {
|
||||
this.addMessageHandler(
|
||||
"values",
|
||||
function (message: Record<string, unknown>) {
|
||||
for (const name in this.$bindings) {
|
||||
if (hasOwnProperty(this.$bindings, name))
|
||||
this.$bindings[name].showProgress(false);
|
||||
}
|
||||
addMessageHandler("values", (message: { [key: string]: unknown }) => {
|
||||
for (const name in this.$bindings) {
|
||||
if (hasOwnProperty(this.$bindings, name))
|
||||
this.$bindings[name].showProgress(false);
|
||||
}
|
||||
|
||||
for (const key in message) {
|
||||
if (hasOwnProperty(message, key))
|
||||
this.receiveOutput(key, message[key]);
|
||||
for (const key in message) {
|
||||
if (hasOwnProperty(message, key)) {
|
||||
this.receiveOutput(key, message[key]);
|
||||
}
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
this.addMessageHandler(
|
||||
addMessageHandler(
|
||||
"errors",
|
||||
function (message: Record<string, errorsMessageValue>) {
|
||||
function (message: { [key: string]: ErrorsMessageValue }) {
|
||||
for (const key in message) {
|
||||
if (hasOwnProperty(message, key))
|
||||
this.receiveError(key, message[key]);
|
||||
@@ -679,9 +680,9 @@ class ShinyApp {
|
||||
}
|
||||
);
|
||||
|
||||
this.addMessageHandler(
|
||||
addMessageHandler(
|
||||
"inputMessages",
|
||||
function (message: Array<{ id: string; message: unknown }>) {
|
||||
(message: Array<{ id: string; message: unknown }>) => {
|
||||
// inputMessages should be an array
|
||||
for (let i = 0; i < message.length; i++) {
|
||||
const $obj = $(".shiny-bound-input#" + $escape(message[i].id));
|
||||
@@ -704,20 +705,20 @@ class ShinyApp {
|
||||
}
|
||||
);
|
||||
|
||||
this.addMessageHandler("javascript", function (message: string) {
|
||||
addMessageHandler("javascript", (message: string) => {
|
||||
/*jshint evil: true */
|
||||
indirectEval(message);
|
||||
});
|
||||
|
||||
this.addMessageHandler("console", function (message: Array<unknown>) {
|
||||
addMessageHandler("console", (message: unknown[]) => {
|
||||
for (let i = 0; i < message.length; i++) {
|
||||
if (console.log) console.log(message[i]);
|
||||
}
|
||||
});
|
||||
|
||||
this.addMessageHandler(
|
||||
addMessageHandler(
|
||||
"progress",
|
||||
function (message: { type: string; message: { id: string } }) {
|
||||
(message: { type: string; message: { id: string } }) => {
|
||||
if (message.type && message.message) {
|
||||
const handler = this.progressHandlers[message.type];
|
||||
|
||||
@@ -726,28 +727,28 @@ class ShinyApp {
|
||||
}
|
||||
);
|
||||
|
||||
this.addMessageHandler(
|
||||
addMessageHandler(
|
||||
"notification",
|
||||
function (
|
||||
(
|
||||
message:
|
||||
| { type: "show"; message: Parameters<typeof showNotification>[0] }
|
||||
| { type: "remove"; message: string }
|
||||
| { type: "show"; message: Parameters<typeof showNotification>[0] }
|
||||
| { type: void }
|
||||
) {
|
||||
) => {
|
||||
if (message.type === "show") showNotification(message.message);
|
||||
else if (message.type === "remove") removeNotification(message.message);
|
||||
else throw "Unkown notification type: " + message.type;
|
||||
}
|
||||
);
|
||||
|
||||
this.addMessageHandler(
|
||||
addMessageHandler(
|
||||
"modal",
|
||||
function (
|
||||
(
|
||||
message:
|
||||
| { type: "show"; message: Parameters<typeof showModal>[0] }
|
||||
| { type: "remove"; message: string }
|
||||
| { type: "show"; message: Parameters<typeof showModal>[0] }
|
||||
| { type: void }
|
||||
) {
|
||||
) => {
|
||||
if (message.type === "show") showModal(message.message);
|
||||
// For 'remove', message content isn't used
|
||||
else if (message.type === "remove") removeModal();
|
||||
@@ -755,13 +756,9 @@ class ShinyApp {
|
||||
}
|
||||
);
|
||||
|
||||
this.addMessageHandler(
|
||||
addMessageHandler(
|
||||
"response",
|
||||
function (message: {
|
||||
tag: string;
|
||||
value?: ResponseValue;
|
||||
error?: string;
|
||||
}) {
|
||||
(message: { tag: string; value?: ResponseValue; error?: string }) => {
|
||||
const requestId = message.tag;
|
||||
const request = this.$activeRequests[requestId];
|
||||
|
||||
@@ -773,22 +770,19 @@ class ShinyApp {
|
||||
}
|
||||
);
|
||||
|
||||
this.addMessageHandler(
|
||||
"allowReconnect",
|
||||
function (message: true | false | "force") {
|
||||
switch (message) {
|
||||
case true:
|
||||
case false:
|
||||
case "force":
|
||||
this.$allowReconnect = message;
|
||||
break;
|
||||
default:
|
||||
throw "Invalid value for allowReconnect: " + message;
|
||||
}
|
||||
addMessageHandler("allowReconnect", (message: "force" | false | true) => {
|
||||
switch (message) {
|
||||
case true:
|
||||
case false:
|
||||
case "force":
|
||||
this.$allowReconnect = message;
|
||||
break;
|
||||
default:
|
||||
throw "Invalid value for allowReconnect: " + message;
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
this.addMessageHandler("custom", function (message) {
|
||||
addMessageHandler("custom", (message: { [key: string]: unknown }) => {
|
||||
// For old-style custom messages - should deprecate and migrate to new
|
||||
// method
|
||||
const shinyOnCustomMessage = getShinyOnCustomMessage();
|
||||
@@ -803,13 +797,9 @@ class ShinyApp {
|
||||
);
|
||||
});
|
||||
|
||||
this.addMessageHandler(
|
||||
addMessageHandler(
|
||||
"config",
|
||||
function (message: {
|
||||
workerId: string;
|
||||
sessionId: string;
|
||||
user?: string;
|
||||
}) {
|
||||
(message: { workerId: string; sessionId: string; user?: string }) => {
|
||||
this.config = {
|
||||
workerId: message.workerId,
|
||||
sessionId: message.sessionId,
|
||||
@@ -819,7 +809,7 @@ class ShinyApp {
|
||||
}
|
||||
);
|
||||
|
||||
this.addMessageHandler("busy", function (message: "busy" | "idle") {
|
||||
addMessageHandler("busy", (message: "busy" | "idle") => {
|
||||
if (message === "busy") {
|
||||
$(document.documentElement).addClass("shiny-busy");
|
||||
$(document).trigger("shiny:busy");
|
||||
@@ -829,12 +819,12 @@ class ShinyApp {
|
||||
}
|
||||
});
|
||||
|
||||
this.addMessageHandler(
|
||||
addMessageHandler(
|
||||
"recalculating",
|
||||
function (message: {
|
||||
(message: {
|
||||
name?: string;
|
||||
status?: "recalculating" | "recalculated";
|
||||
}) {
|
||||
status?: "recalculated" | "recalculating";
|
||||
}) => {
|
||||
if (
|
||||
hasOwnProperty(message, "name") &&
|
||||
hasOwnProperty(message, "status")
|
||||
@@ -849,20 +839,20 @@ class ShinyApp {
|
||||
}
|
||||
);
|
||||
|
||||
this.addMessageHandler("reload", function (message: true) {
|
||||
addMessageHandler("reload", (message: true) => {
|
||||
window.location.reload();
|
||||
return;
|
||||
message;
|
||||
});
|
||||
|
||||
this.addMessageHandler(
|
||||
addMessageHandler(
|
||||
"shiny-insert-ui",
|
||||
function (message: {
|
||||
(message: {
|
||||
selector: string;
|
||||
content: { html: string; deps: Array<HtmlDep> };
|
||||
content: { html: string; deps: HtmlDep[] };
|
||||
multiple: false | void;
|
||||
where: WherePosition;
|
||||
}) {
|
||||
}) => {
|
||||
const targets = $(message.selector);
|
||||
|
||||
if (targets.length === 0) {
|
||||
@@ -884,9 +874,9 @@ class ShinyApp {
|
||||
}
|
||||
);
|
||||
|
||||
this.addMessageHandler(
|
||||
addMessageHandler(
|
||||
"shiny-remove-ui",
|
||||
function (message: { selector: string; multiple: false | void }) {
|
||||
(message: { selector: string; multiple: false | void }) => {
|
||||
const els = $(message.selector);
|
||||
|
||||
els.each(function (i, el) {
|
||||
@@ -900,14 +890,11 @@ class ShinyApp {
|
||||
}
|
||||
);
|
||||
|
||||
this.addMessageHandler(
|
||||
"frozen",
|
||||
function (message: { ids: Array<string> }) {
|
||||
for (let i = 0; i < message.ids.length; i++) {
|
||||
shinyForgetLastInputValue(message.ids[i]);
|
||||
}
|
||||
addMessageHandler("frozen", (message: { ids: string[] }) => {
|
||||
for (let i = 0; i < message.ids.length; i++) {
|
||||
shinyForgetLastInputValue(message.ids[i]);
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
function getTabset(id: string) {
|
||||
const $tabset = $("#" + $escape(id));
|
||||
@@ -976,17 +963,17 @@ class ShinyApp {
|
||||
return { $liTag: $liTag, $liTags: $liTags, $divTags: $divTags };
|
||||
}
|
||||
|
||||
this.addMessageHandler(
|
||||
addMessageHandler(
|
||||
"shiny-insert-tab",
|
||||
function (message: {
|
||||
(message: {
|
||||
inputId: string;
|
||||
divTag: { html: string; deps: Array<HtmlDep> };
|
||||
liTag: { html: string; deps: Array<HtmlDep> };
|
||||
divTag: { html: string; deps: HtmlDep[] };
|
||||
liTag: { html: string; deps: HtmlDep[] };
|
||||
target?: string;
|
||||
position: "before" | "after" | void;
|
||||
position: "after" | "before" | void;
|
||||
select: boolean;
|
||||
menuName: string;
|
||||
}) {
|
||||
}) => {
|
||||
const $parentTabset = getTabset(message.inputId);
|
||||
let $tabset = $parentTabset;
|
||||
const $tabContent = getTabContent($tabset);
|
||||
@@ -1016,6 +1003,15 @@ class ShinyApp {
|
||||
throw "Cannot insert a navbarMenu inside another one";
|
||||
$tabset = dropdown.$tabset;
|
||||
tabsetId = dropdown.id;
|
||||
// In the BS4+ case, the server will be generating "top-level" nav markup
|
||||
// (i.e., `li.nav-item a.nav-link`), but when inserting inside a dropdown we
|
||||
// need `li a.dropdown-item` for correct styling
|
||||
// https://getbootstrap.com/docs/5.0/components/navs-tabs/#tabs-with-dropdowns
|
||||
$liTag
|
||||
.removeClass("nav-item")
|
||||
.find(".nav-link")
|
||||
.removeClass("nav-link")
|
||||
.addClass("dropdown-item");
|
||||
}
|
||||
|
||||
// For regular tab items, fix the href (of the li > a tag)
|
||||
@@ -1223,9 +1219,9 @@ class ShinyApp {
|
||||
});
|
||||
}
|
||||
|
||||
this.addMessageHandler(
|
||||
addMessageHandler(
|
||||
"shiny-remove-tab",
|
||||
function (message: { inputId: string; target: string; type: never }) {
|
||||
(message: { inputId: string; target: string; type: never }) => {
|
||||
const $tabset = getTabset(message.inputId);
|
||||
const $tabContent = getTabContent($tabset);
|
||||
const target = getTargetTabs($tabset, $tabContent, message.target);
|
||||
@@ -1241,13 +1237,13 @@ class ShinyApp {
|
||||
}
|
||||
);
|
||||
|
||||
this.addMessageHandler(
|
||||
addMessageHandler(
|
||||
"shiny-change-tab-visibility",
|
||||
function (message: {
|
||||
(message: {
|
||||
inputId: string;
|
||||
target: string;
|
||||
type: "show" | "hide" | null;
|
||||
}) {
|
||||
type: "hide" | "show" | null;
|
||||
}) => {
|
||||
const $tabset = getTabset(message.inputId);
|
||||
const $tabContent = getTabContent($tabset);
|
||||
const target = getTargetTabs($tabset, $tabContent, message.target);
|
||||
@@ -1266,9 +1262,9 @@ class ShinyApp {
|
||||
}
|
||||
);
|
||||
|
||||
this.addMessageHandler(
|
||||
addMessageHandler(
|
||||
"updateQueryString",
|
||||
function (message: { mode: "replace" | unknown; queryString: string }) {
|
||||
(message: { mode: unknown | "replace"; queryString: string }) => {
|
||||
// leave the bookmarking code intact
|
||||
if (message.mode === "replace") {
|
||||
window.history.replaceState(null, null, message.queryString);
|
||||
@@ -1321,9 +1317,9 @@ class ShinyApp {
|
||||
}
|
||||
);
|
||||
|
||||
this.addMessageHandler(
|
||||
addMessageHandler(
|
||||
"resetBrush",
|
||||
function (message: { brushId: Parameters<typeof resetBrush>[0] }) {
|
||||
(message: { brushId: Parameters<typeof resetBrush>[0] }) => {
|
||||
resetBrush(message.brushId);
|
||||
}
|
||||
);
|
||||
@@ -1509,4 +1505,4 @@ class ShinyApp {
|
||||
}
|
||||
|
||||
export { ShinyApp, addCustomMessageHandler };
|
||||
export type { HandlerType, errorsMessageValue };
|
||||
export type { Handler, ErrorsMessageValue };
|
||||
|
||||
@@ -1,27 +1,32 @@
|
||||
import $ from "jquery";
|
||||
import { bindScope } from "./bind";
|
||||
import { toLowerCase } from "../utils";
|
||||
import type { BindScope } from "./bind";
|
||||
|
||||
const _reSingleton = /<!--(SHINY.SINGLETON\[([\w]+)\])-->([\s\S]*?)<!--\/\1-->/;
|
||||
const _reHead = /<head(?:\s[^>]*)?>([\s\S]*?)<\/head>/;
|
||||
const reSingleton = /<!--(SHINY.SINGLETON\[([\w]+)\])-->([\s\S]*?)<!--\/\1-->/;
|
||||
const reHead = /<head(?:\s[^>]*)?>([\s\S]*?)<\/head>/;
|
||||
|
||||
const knownSingletons: Record<string, boolean> = {};
|
||||
const knownSingletons: { [key: string]: boolean } = {};
|
||||
|
||||
type WherePosition = "replace" | "beforeBegin" | "afterEnd";
|
||||
type RenderHtmlWherePosition = "replace" | InsertPosition;
|
||||
type WherePosition =
|
||||
| "afterBegin"
|
||||
| "afterEnd"
|
||||
| "beforeBegin"
|
||||
| "beforeEnd"
|
||||
| "replace";
|
||||
|
||||
function renderHtml(
|
||||
html: string,
|
||||
el: bindScope,
|
||||
where: RenderHtmlWherePosition
|
||||
): ReturnType<typeof _processHtml> {
|
||||
const processed = _processHtml(html);
|
||||
el: BindScope,
|
||||
where: WherePosition
|
||||
): ReturnType<typeof processHtml> {
|
||||
const processed = processHtml(html);
|
||||
|
||||
_addToHead(processed.head);
|
||||
addToHead(processed.head);
|
||||
register(processed.singletons);
|
||||
if (where === "replace") {
|
||||
$(el).html(processed.html);
|
||||
} else {
|
||||
let elElements: Array<HTMLElement>;
|
||||
let elElements: HTMLElement[];
|
||||
|
||||
if (el instanceof HTMLElement) {
|
||||
elElements = [el];
|
||||
@@ -30,7 +35,7 @@ function renderHtml(
|
||||
}
|
||||
$.each(elElements, (i, el) => {
|
||||
// type InsertPosition = "beforebegin" | "afterbegin" | "beforeend" | "afterend"
|
||||
el.insertAdjacentHTML(where, processed.html);
|
||||
el.insertAdjacentHTML(toLowerCase(where), processed.html);
|
||||
});
|
||||
}
|
||||
return processed;
|
||||
@@ -41,7 +46,7 @@ function register(s) {
|
||||
$.extend(knownSingletons, s);
|
||||
}
|
||||
// Takes a string or array of strings and adds them to knownSingletons
|
||||
function registerNames(s: string | Array<string>): void {
|
||||
function registerNames(s: string[] | string): void {
|
||||
if (typeof s === "string") {
|
||||
knownSingletons[s] = true;
|
||||
} else if (s instanceof Array) {
|
||||
@@ -51,7 +56,7 @@ function registerNames(s: string | Array<string>): void {
|
||||
}
|
||||
}
|
||||
// Inserts new content into document head
|
||||
function _addToHead(head: string) {
|
||||
function addToHead(head: string) {
|
||||
if (head.length > 0) {
|
||||
const tempDiv = $("<div>" + head + "</div>").get(0);
|
||||
const $head = $("head");
|
||||
@@ -63,7 +68,7 @@ function _addToHead(head: string) {
|
||||
}
|
||||
}
|
||||
// Reads HTML and returns an object with info about singletons
|
||||
function _processHtml(val: string): {
|
||||
function processHtml(val: string): {
|
||||
html: string;
|
||||
head: string;
|
||||
singletons: typeof knownSingletons;
|
||||
@@ -79,7 +84,7 @@ function _processHtml(val: string): {
|
||||
|
||||
// eslint-disable-next-line no-constant-condition
|
||||
while (true) {
|
||||
newVal = val.replace(_reSingleton, findNewPayload);
|
||||
newVal = val.replace(reSingleton, findNewPayload);
|
||||
if (val.length === newVal.length) break;
|
||||
val = newVal;
|
||||
}
|
||||
@@ -92,7 +97,7 @@ function _processHtml(val: string): {
|
||||
|
||||
// eslint-disable-next-line no-constant-condition
|
||||
while (true) {
|
||||
newVal = val.replace(_reHead, headAddPayload);
|
||||
newVal = val.replace(reHead, headAddPayload);
|
||||
if (val.length === newVal.length) break;
|
||||
val = newVal;
|
||||
}
|
||||
@@ -105,4 +110,4 @@ function _processHtml(val: string): {
|
||||
}
|
||||
|
||||
export { renderHtml, registerNames };
|
||||
export type { WherePosition, RenderHtmlWherePosition };
|
||||
export type { WherePosition };
|
||||
|
||||
@@ -13,5 +13,5 @@
|
||||
|
||||
interface JQuery {
|
||||
// used for testing only
|
||||
_test: () => void;
|
||||
internalTest: () => void;
|
||||
}
|
||||
@@ -1,13 +1,13 @@
|
||||
class Debouncer {
|
||||
target: unknown;
|
||||
func: (...args: Array<unknown>) => void;
|
||||
func: (...args: unknown[]) => void;
|
||||
delayMs: number;
|
||||
timerId: NodeJS.Timeout;
|
||||
args: Array<unknown>;
|
||||
args: unknown[];
|
||||
|
||||
constructor(
|
||||
target: unknown,
|
||||
func: (...args: Array<unknown>) => void,
|
||||
func: (...args: unknown[]) => void,
|
||||
delayMs: number
|
||||
) {
|
||||
this.target = target;
|
||||
@@ -18,7 +18,7 @@ class Debouncer {
|
||||
this.args = null;
|
||||
}
|
||||
|
||||
normalCall(...args: Array<unknown>): void {
|
||||
normalCall(...args: unknown[]): void {
|
||||
this.$clearTimer();
|
||||
this.args = args;
|
||||
|
||||
@@ -30,7 +30,7 @@ class Debouncer {
|
||||
this.$invoke();
|
||||
}, this.delayMs);
|
||||
}
|
||||
immediateCall(...args: Array<unknown>): void {
|
||||
immediateCall(...args: unknown[]): void {
|
||||
this.$clearTimer();
|
||||
this.args = args;
|
||||
this.$invoke();
|
||||
@@ -64,8 +64,8 @@ class Debouncer {
|
||||
// call.
|
||||
function debounce<T>(
|
||||
threshold: number,
|
||||
func: (...args: Array<T>) => void
|
||||
): (...args: Array<T>) => void {
|
||||
func: (...args: T[]) => void
|
||||
): (...args: T[]) => void {
|
||||
let timerId = null;
|
||||
|
||||
return function (...args) {
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
import { InputPolicy } from "../inputPolicies";
|
||||
import type { InputPolicy } from "../inputPolicies";
|
||||
|
||||
class Invoker<T> {
|
||||
target: InputPolicy;
|
||||
func: () => void;
|
||||
|
||||
constructor(target: InputPolicy, func: (...args: Array<T>) => void) {
|
||||
constructor(target: InputPolicy, func: (...args: T[]) => void) {
|
||||
this.target = target;
|
||||
this.func = func;
|
||||
}
|
||||
|
||||
// TODO-barret - Don't know how to define the method twice and still have access to "this"
|
||||
normalCall(...args: Array<T>): void {
|
||||
normalCall(...args: T[]): void {
|
||||
this.func.apply(this.target, args);
|
||||
}
|
||||
immediateCall(...args: Array<T>): void {
|
||||
immediateCall(...args: T[]): void {
|
||||
this.func.apply(this.target, args);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,15 +1,11 @@
|
||||
class Throttler<T = unknown> {
|
||||
target: unknown;
|
||||
func: (...args: Array<T>) => void;
|
||||
func: (...args: T[]) => void;
|
||||
delayMs: number;
|
||||
timerId: NodeJS.Timeout;
|
||||
args: Array<T>;
|
||||
args: T[];
|
||||
|
||||
constructor(
|
||||
target: unknown,
|
||||
func: (...args: Array<T>) => void,
|
||||
delayMs: number
|
||||
) {
|
||||
constructor(target: unknown, func: (...args: T[]) => void, delayMs: number) {
|
||||
this.target = target;
|
||||
this.func = func;
|
||||
this.delayMs = delayMs;
|
||||
@@ -18,7 +14,7 @@ class Throttler<T = unknown> {
|
||||
this.args = null;
|
||||
}
|
||||
|
||||
normalCall(...args: Array<T>): void {
|
||||
normalCall(...args: T[]): void {
|
||||
this.args = args;
|
||||
if (this.timerId === null) {
|
||||
this.$invoke();
|
||||
@@ -31,7 +27,7 @@ class Throttler<T = unknown> {
|
||||
}, this.delayMs);
|
||||
}
|
||||
}
|
||||
immediateCall(...args: Array<T>): void {
|
||||
immediateCall(...args: T[]): void {
|
||||
this.$clearTimer();
|
||||
this.args = args;
|
||||
this.$invoke();
|
||||
@@ -63,13 +59,13 @@ class Throttler<T = unknown> {
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
function throttle<T>(
|
||||
threshold: number,
|
||||
func: (...args: Array<T>) => void
|
||||
): (...args: Array<T>) => void {
|
||||
func: (...args: T[]) => void
|
||||
): (...args: T[]) => void {
|
||||
let executionPending = false;
|
||||
let timerId = null;
|
||||
let self, args: Array<T>;
|
||||
let self, args: T[];
|
||||
|
||||
function throttled(...argumentVals: Array<T>) {
|
||||
function throttled(...argumentVals: T[]) {
|
||||
self = null;
|
||||
args = null;
|
||||
if (timerId === null) {
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
function hasOwnProperty(x: Record<string, unknown>, y: string): boolean {
|
||||
return Object.prototype.hasOwnProperty.call(x, y);
|
||||
}
|
||||
|
||||
export { hasOwnProperty };
|
||||
@@ -5,7 +5,7 @@ import $ from "jquery";
|
||||
import { getQueriesForElement } from "@testing-library/dom";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
|
||||
$.fn._test = function countify() {
|
||||
$.fn.internalTest = function countify() {
|
||||
this.html(`
|
||||
<div>
|
||||
<button>0</button>
|
||||
@@ -24,7 +24,7 @@ $.fn._test = function countify() {
|
||||
test("counter increments", () => {
|
||||
const div = document.createElement("div");
|
||||
|
||||
$(div)._test();
|
||||
$(div).internalTest();
|
||||
|
||||
const { getByText } = getQueriesForElement(div);
|
||||
const counter = getByText("0");
|
||||
@@ -1,9 +1,11 @@
|
||||
import $ from "jquery";
|
||||
|
||||
let BlobBuilder;
|
||||
type BlobBuilderConstructor = typeof window.MSBlobBuilder;
|
||||
|
||||
function setBlobBuilder(BlobBuilder_: MSBlobBuilder): void {
|
||||
BlobBuilder = BlobBuilder_;
|
||||
let blobBuilderClass: BlobBuilderConstructor;
|
||||
|
||||
function setBlobBuilder(blobBuilderClass_: BlobBuilderConstructor): void {
|
||||
blobBuilderClass = blobBuilderClass_;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -29,7 +31,7 @@ function makeBlob(parts: BlobPart[]): Blob {
|
||||
try {
|
||||
return new Blob(parts);
|
||||
} catch (e) {
|
||||
const blobBuilder = new BlobBuilder();
|
||||
const blobBuilder = new blobBuilderClass();
|
||||
|
||||
$.each(parts, function (i, part) {
|
||||
blobBuilder.append(part);
|
||||
@@ -39,3 +41,4 @@ function makeBlob(parts: BlobPart[]): Blob {
|
||||
}
|
||||
|
||||
export { makeBlob, setBlobBuilder };
|
||||
export type { BlobBuilderConstructor };
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
let isQtVal = false;
|
||||
let isIEVal = false;
|
||||
let IEVersionVal = -1;
|
||||
let versionIE = -1;
|
||||
|
||||
function setIsQt(isQt: boolean): void {
|
||||
isQtVal = isQt;
|
||||
@@ -8,8 +8,8 @@ function setIsQt(isQt: boolean): void {
|
||||
function setIsIE(isIE: boolean): void {
|
||||
isIEVal = isIE;
|
||||
}
|
||||
function setIEVersion(IEVersion_: number): void {
|
||||
IEVersionVal = IEVersion_;
|
||||
function setIEVersion(versionIE_: number): void {
|
||||
versionIE = versionIE_;
|
||||
}
|
||||
|
||||
function isQt(): boolean {
|
||||
@@ -18,8 +18,11 @@ function isQt(): boolean {
|
||||
function isIE(): boolean {
|
||||
return isIEVal;
|
||||
}
|
||||
|
||||
// (Name existed before TS conversion)
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
function IEVersion(): number {
|
||||
return IEVersionVal;
|
||||
return versionIE;
|
||||
}
|
||||
|
||||
export { isQt, isIE, IEVersion, setIsQt, setIsIE, setIEVersion };
|
||||
|
||||
@@ -6,6 +6,16 @@
|
||||
// > This is known as "indirect eval" because eval is not being called directly, and so does not trigger the grammatical special case for direct eval in the JavaScript VM. You can call indirect eval using any syntax at all except for an expression of the exact form eval('x'). For example, var eval2 = eval; eval2('x') and [eval][0]('x') and window.eval('x') are all indirect eval calls.
|
||||
// > When you use indirect eval, the code is evaluated in the global scope instead of in the inline scope of the caller.
|
||||
|
||||
/**
|
||||
* Evaluates JavaScript code and executes it via _Indirect Evaluation_
|
||||
* @param y A String value that contains valid JavaScript code.
|
||||
*
|
||||
* From [esbuild](esbuild.github.io/content-types/#direct-eval): Direct usage of `eval("x")` is bad with bundled code. Instead, use indirect calls to `eval` such as `indirectEval("x")`. Even just renaming the function works well enough.
|
||||
*
|
||||
* > This is known as "indirect eval" because eval is not being called directly, and so does not trigger the grammatical special case for direct eval in the JavaScript VM. You can call indirect eval using any syntax at all except for an expression of the exact form `eval('x')`. For example, `var eval2 = eval; eval2('x')` and `[eval][0]('x')` and window.`eval('x')` are all indirect eval calls.
|
||||
* > When you use indirect eval, the code is evaluated in the global scope instead of in the inline scope of the caller.
|
||||
*
|
||||
*/
|
||||
const indirectEval = eval;
|
||||
|
||||
export { indirectEval };
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import $ from "jquery";
|
||||
import { windowDevicePixelRatio } from "../window/pixelRatio";
|
||||
import { makeBlob } from "./blob";
|
||||
import { hasOwnProperty } from "./Object";
|
||||
import { hasOwnProperty } from "./object";
|
||||
|
||||
function escapeHTML(str: string): string {
|
||||
const escaped = {
|
||||
@@ -85,7 +85,7 @@ function parseDate(dateString: string): Date {
|
||||
|
||||
// Given a Date object, return a string in yyyy-mm-dd format, using the
|
||||
// UTC date. This may be a day off from the date in the local time zone.
|
||||
function formatDateUTC(date: Date | null): null | string {
|
||||
function formatDateUTC(date: Date | null): string | null {
|
||||
if (date instanceof Date) {
|
||||
return (
|
||||
date.getUTCFullYear() +
|
||||
@@ -108,10 +108,10 @@ function formatDateUTC(date: Date | null): null | string {
|
||||
// Basically we are trying to filter out extraneous calls to func, so that
|
||||
// when the window size changes or whatever, we don't run resize logic for
|
||||
// elements that haven't actually changed size or aren't visible anyway.
|
||||
interface lastSizeInterface {
|
||||
type LastSizeInterface = {
|
||||
w?: number;
|
||||
h?: number;
|
||||
}
|
||||
};
|
||||
function makeResizeFilter(
|
||||
el: HTMLElement,
|
||||
func: (
|
||||
@@ -119,7 +119,7 @@ function makeResizeFilter(
|
||||
height: HTMLElement["offsetHeight"]
|
||||
) => void
|
||||
): () => void {
|
||||
let lastSize: lastSizeInterface = {};
|
||||
let lastSize: LastSizeInterface = {};
|
||||
|
||||
return function () {
|
||||
const size = { w: el.offsetWidth, h: el.offsetHeight };
|
||||
@@ -178,7 +178,7 @@ function scopeExprToFunc(expr: string): (scope: unknown) => boolean {
|
||||
};
|
||||
}
|
||||
|
||||
function asArray<T>(value: T | Array<T> | null | undefined): Array<T> {
|
||||
function asArray<T>(value: T | T[] | null | undefined): T[] {
|
||||
if (value === null || value === undefined) return [];
|
||||
if (Array.isArray(value)) return value;
|
||||
return [value];
|
||||
@@ -187,9 +187,9 @@ function asArray<T>(value: T | Array<T> | null | undefined): Array<T> {
|
||||
// We need a stable sorting algorithm for ordering
|
||||
// bindings by priority and insertion order.
|
||||
function mergeSort<T>(
|
||||
list: Array<T>,
|
||||
list: T[],
|
||||
sortfunc: (a: T, b: T) => boolean | number
|
||||
): Array<T> {
|
||||
): T[] {
|
||||
function merge(sortfunc, a, b) {
|
||||
let ia = 0;
|
||||
let ib = 0;
|
||||
@@ -233,10 +233,10 @@ const $escape = function (val: string): string {
|
||||
// Maps a function over an object, preserving keys. Like the mapValues
|
||||
// function from lodash.
|
||||
function mapValues<V, R>(
|
||||
obj: Record<string, V>,
|
||||
f: (value: V, key: string, obj: Record<string, V>) => R
|
||||
): Record<string, R> {
|
||||
const newObj: Record<string, R> = {};
|
||||
obj: { [key: string]: V },
|
||||
f: (value: V, key: string, obj: { [key: string]: V }) => R
|
||||
): { [key: string]: R } {
|
||||
const newObj: { [key: string]: R } = {};
|
||||
|
||||
for (const key in obj) {
|
||||
if (hasOwnProperty(obj, key)) newObj[key] = f(obj[key], key, obj);
|
||||
@@ -251,10 +251,12 @@ function isnan(x: unknown): boolean {
|
||||
}
|
||||
|
||||
// Binary equality function used by the equal function.
|
||||
// (Name existed before TS conversion)
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
function _equal(x: unknown, y: unknown): boolean {
|
||||
if ($.type(x) === "object" && $.type(y) === "object") {
|
||||
const xo = x as Record<string, unknown>;
|
||||
const yo = y as Record<string, unknown>;
|
||||
const xo = x as { [key: string]: unknown };
|
||||
const yo = y as { [key: string]: unknown };
|
||||
|
||||
if (Object.keys(xo).length !== Object.keys(yo).length) return false;
|
||||
for (const prop in xo) {
|
||||
@@ -263,8 +265,8 @@ function _equal(x: unknown, y: unknown): boolean {
|
||||
}
|
||||
return true;
|
||||
} else if ($.type(x) === "array" && $.type(y) === "array") {
|
||||
const xa = x as Array<unknown>;
|
||||
const ya = y as Array<unknown>;
|
||||
const xa = x as unknown[];
|
||||
const ya = y as unknown[];
|
||||
|
||||
if (xa.length !== ya.length) return false;
|
||||
for (let i = 0; i < xa.length; i++) if (!_equal(xa[i], ya[i])) return false;
|
||||
@@ -279,7 +281,7 @@ function _equal(x: unknown, y: unknown): boolean {
|
||||
// necessary.
|
||||
//
|
||||
// Objects other than objects and arrays are tested for equality using ===.
|
||||
function equal(...args: Array<unknown>): boolean {
|
||||
function equal(...args: unknown[]): boolean {
|
||||
if (args.length < 2)
|
||||
throw new Error("equal requires at least two arguments.");
|
||||
for (let i = 0; i < args.length - 1; i++) {
|
||||
@@ -292,7 +294,7 @@ function equal(...args: Array<unknown>): boolean {
|
||||
// "==" or "<".
|
||||
const compareVersion = function (
|
||||
a: string,
|
||||
op: "==" | ">=" | ">" | "<=" | "<",
|
||||
op: "<" | "<=" | "==" | ">" | ">=",
|
||||
b: string
|
||||
): boolean {
|
||||
function versionParts(ver) {
|
||||
@@ -328,7 +330,7 @@ const compareVersion = function (
|
||||
};
|
||||
|
||||
function updateLabel(
|
||||
labelTxt: undefined | string,
|
||||
labelTxt: string | undefined,
|
||||
labelNode: JQuery<HTMLElement>
|
||||
): void {
|
||||
// Only update if label was specified in the update method
|
||||
@@ -373,6 +375,10 @@ function isBS3(): boolean {
|
||||
return !window.bootstrap;
|
||||
}
|
||||
|
||||
function toLowerCase<T extends string>(str: T): Lowercase<T> {
|
||||
return str.toLowerCase() as Lowercase<T>;
|
||||
}
|
||||
|
||||
export {
|
||||
escapeHTML,
|
||||
randomId,
|
||||
@@ -398,4 +404,5 @@ export {
|
||||
makeBlob,
|
||||
hasOwnProperty,
|
||||
isBS3,
|
||||
toLowerCase,
|
||||
};
|
||||
|
||||
11
srcts/src/utils/object.ts
Normal file
11
srcts/src/utils/object.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
/**
|
||||
* Wrapper around `Object.prototype.hasOwnProperty.call(x,y)`
|
||||
* @param x Object to inspect
|
||||
* @param y Key to inspect in `x`
|
||||
* @returns Whether object `x` actually has property `y`.
|
||||
* */
|
||||
function hasOwnProperty(x: { [key: string]: unknown }, y: string): boolean {
|
||||
return Object.prototype.hasOwnProperty.call(x, y);
|
||||
}
|
||||
|
||||
export { hasOwnProperty };
|
||||
@@ -1,10 +1,10 @@
|
||||
type UserAgentType = typeof window.navigator.userAgent;
|
||||
type UserAgent = typeof window.navigator.userAgent;
|
||||
|
||||
let userAgentVal: UserAgentType;
|
||||
let userAgent: UserAgent;
|
||||
|
||||
function setUserAgent(userAgent: UserAgentType): void {
|
||||
userAgentVal = userAgent;
|
||||
function setUserAgent(userAgent_: UserAgent): void {
|
||||
userAgent = userAgent_;
|
||||
}
|
||||
|
||||
export type { UserAgentType };
|
||||
export { userAgentVal as userAgent, setUserAgent };
|
||||
export type { UserAgent };
|
||||
export { userAgent, setUserAgent };
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user