Compare commits

...

10 Commits

Author SHA1 Message Date
Winston Chang
281dffde10 Use correct type for messages 2022-10-03 12:20:26 -05:00
Winston Chang
0f714cff34 Use es2020 2022-10-03 11:42:11 -05:00
Winston Chang
98f17e0cd2 Disable eslint rules only within scope 2022-10-03 11:41:28 -05:00
Winston Chang
9b2c04f298 Remove redundant setting 2022-09-30 19:57:03 -05:00
Winston Chang
ed4a97154d Remove makeBlob
Blob has long been available on all major browsers, so makeBlob is no longer needed.
2022-09-30 16:29:38 -05:00
Winston Chang
9dcd62f944 Update eslint 2022-09-30 16:17:19 -05:00
Winston Chang
213c645524 Upgrade esbuild and typescript 2022-09-30 15:59:24 -05:00
Winston Chang
f1c0ac2b30 Upgrade to yarn 3.2.3 2022-09-30 15:57:51 -05:00
Barret Schloerke
16c6d55f60 Enable TypeScript strict mode (#3644) 2022-09-29 16:03:05 -04:00
Hedley
6e40a3dd39 Update jQuery-UI to 1.13.2 (#3697) 2022-09-21 10:34:51 -04:00
119 changed files with 5477 additions and 3385 deletions

View File

@@ -6,7 +6,6 @@ extends:
- 'eslint:recommended'
- 'plugin:@typescript-eslint/recommended'
- 'plugin:jest/recommended'
- 'prettier/@typescript-eslint'
- 'plugin:prettier/recommended'
- 'plugin:jest-dom/recommended'
globals:
@@ -64,6 +63,7 @@ rules:
- error
- default: array-simple
readonly: array-simple
"@typescript-eslint/consistent-indexed-object-style":
- error
- index-signature

File diff suppressed because one or more lines are too long

783
.yarn/releases/yarn-3.2.3.cjs vendored Executable file

File diff suppressed because one or more lines are too long

View File

@@ -6,4 +6,4 @@ plugins:
- path: .yarn/plugins/@yarnpkg/plugin-interactive-tools.cjs
spec: "@yarnpkg/plugin-interactive-tools"
yarnPath: .yarn/releases/yarn-2.4.0.cjs
yarnPath: .yarn/releases/yarn-3.2.3.cjs

View File

@@ -198,6 +198,7 @@ Collate:
'version_bs_date_picker.R'
'version_ion_range_slider.R'
'version_jquery.R'
'version_jqueryui.R'
'version_selectize.R'
'version_strftime.R'
'viewer.R'

11
NEWS.md
View File

@@ -1,6 +1,17 @@
shiny 1.7.2.9000
================
## Full changelog
### Breaking changes
### New features and improvements
* Internal: Added clearer and strict TypeScript type definitions (#3644)
### Bug fixes
* Closed #3687: Updated jQuery-UI to v1.13.2. (#3697)
shiny 1.7.2

View File

@@ -103,10 +103,10 @@ fixedPanel <- function(...,
jqueryuiDependency <- function() {
htmlDependency(
'jqueryui',
'1.12.1',
src = 'www/shared/jqueryui',
package = 'shiny',
script = 'jquery-ui.min.js'
"jqueryui",
version_jqueryui,
src = "www/shared/jqueryui",
package = "shiny",
script = "jquery-ui.min.js"
)
}

2
R/version_jqueryui.R Normal file
View File

@@ -0,0 +1,2 @@
# Generated by tools/updatejQueryUI.R; do not edit by hand
version_jqueryui <- "1.13.2"

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -312,7 +312,7 @@ Mani Mishra <manimishra902@gmail.com>
Hannah Methvin <hannahmethvin@gmail.com>
Leonardo Balter <leonardo.balter@gmail.com>
Benjamin Albert <benjamin_a5@yahoo.com>
Michał Gołębiowski <m.goleb@gmail.com>
Michał Gołębiowski-Owczarek <m.goleb@gmail.com>
Alyosha Pushak <alyosha.pushak@gmail.com>
Fahad Ahmad <fahadahmad41@hotmail.com>
Matt Brundage <github@mattbrundage.com>
@@ -331,3 +331,42 @@ Peter Dave Hello <hsu@peterdavehello.org>
Johannes Schäfer <johnschaefer@gmx.de>
Ville Skyttä <ville.skytta@iki.fi>
Ryan Oriecuia <ryan.oriecuia@visioncritical.com>
Sergei Ratnikov <sergeir82@gmail.com>
milk54 <milk851@gmail.com>
Evelyn Masso <evoutofambit@gmail.com>
Robin <mail@robin-fowler.com>
Simon Asika <asika32764@gmail.com>
Kevin Cupp <kevin.cupp@gmail.com>
Jeremy Mickelson <Jeremy.Mickelson@gmail.com>
Kyle Rosenberg <kyle.rosenberg@gmail.com>
Petri Partio <petri.partio@gmail.com>
pallxk <github@pallxk.com>
Luke Brookhart <luke@onjax.com>
claudi <hirt-claudia@gmx.de>
Eirik Sletteberg <eiriksletteberg@gmail.com>
Albert Johansson <albert@intervaro.se>
A. Wells <borgboyone@users.noreply.github.com>
Robert Brignull <robertbrignull@gmail.com>
Horus68 <pauloizidoro@gmail.com>
Maksymenkov Eugene <foatei@gmail.com>
OskarNS <soerensen.oskar@gmail.com>
Gez Quinn <holla@gezquinn.design>
jigar gala <jigar.gala140291@gmail.com>
Florian Wegscheider <flo.wegscheider@gmail.com>
Fatér Zsolt <fater.zsolt@gmail.com>
Szabolcs Szabolcsi-Toth <nec@shell8.net>
Jérémy Munsch <github@jeremydev.ovh>
Hrvoje Novosel <hrvoje.novosel@gmail.com>
Paul Capron <PaulCapron@users.noreply.github.com>
Micah Miller <mikhey@runbox.com>
sakshi87 <53863764+sakshi87@users.noreply.github.com>
Mikolaj Wolicki <wolicki.mikolaj@gmail.com>
Patrick McKay <patrick.mckay@vumc.org>
c-lambert <58025159+c-lambert@users.noreply.github.com>
Josep Sanz <josepsanzcamp@gmail.com>
Ben Mullins <benm@umich.edu>
Christian Oliff <christianoliff@pm.me>
dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Adam Lidén Hällgren <adamlh92@gmail.com>
James Hinderks <hinderks@gmail.com>
Denny Septian Panggabean <97607754+ddevsr@users.noreply.github.com>

View File

@@ -1,5 +0,0 @@
This a full jQuery UI build, downloaded from:
https://jqueryui.com/resources/download/jquery-ui-1.12.1.zip
The copy of jQuery that is bundled with the download, under external/, is not
included because Shiny already has its own copy of jQuery.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.8 KiB

After

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.9 KiB

After

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.6 KiB

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.8 KiB

After

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.5 KiB

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.2 KiB

After

Width:  |  Height:  |  Size: 6.4 KiB

View File

@@ -58,7 +58,6 @@
<h1>YOUR COMPONENTS:</h1>
<!-- Accordion -->
<h2 class="demoHeaders">Accordion</h2>
<div id="accordion">
@@ -70,23 +69,17 @@
<div>Nam dui erat, auctor a, dignissim quis.</div>
</div>
<!-- Autocomplete -->
<h2 class="demoHeaders">Autocomplete</h2>
<div>
<input id="autocomplete" title="type &quot;a&quot;">
</div>
<!-- Button -->
<h2 class="demoHeaders">Button</h2>
<button id="button">A button element</button>
<button id="button-icon">An icon-only button</button>
<!-- Checkboxradio -->
<h2 class="demoHeaders">Checkboxradio</h2>
<form style="margin-top: 1em;">
@@ -97,8 +90,6 @@
</div>
</form>
<!-- Controlgroup -->
<h2 class="demoHeaders">Controlgroup</h2>
<fieldset>
@@ -125,8 +116,6 @@
</div>
</fieldset>
<!-- Tabs -->
<h2 class="demoHeaders">Tabs</h2>
<div id="tabs">
@@ -140,8 +129,6 @@
<div id="tabs-3">Nam dui erat, auctor a, dignissim quis, sollicitudin eu, felis. Pellentesque nisi urna, interdum eget, sagittis et, consequat vestibulum, lacus. Mauris porttitor ullamcorper augue.</div>
</div>
<h2 class="demoHeaders">Dialog</h2>
<p>
<button id="dialog-link" class="ui-button ui-corner-all ui-widget">
@@ -167,7 +154,6 @@
</div>
<h2 class="demoHeaders">Framework Icons (content color preview)</h2>
<ul id="icons" class="ui-widget ui-helper-clearfix">
<li class="ui-state-default ui-corner-all" title=".ui-icon-caret-1-n"><span class="ui-icon ui-icon-caret-1-n"></span></li>
@@ -345,25 +331,18 @@
<li class="ui-state-default ui-corner-all" title=".ui-icon-grip-diagonal-se"><span class="ui-icon ui-icon-grip-diagonal-se"></span></li>
</ul>
<!-- Slider -->
<h2 class="demoHeaders">Slider</h2>
<div id="slider"></div>
<!-- Datepicker -->
<h2 class="demoHeaders">Datepicker</h2>
<div id="datepicker"></div>
<!-- Progressbar -->
<h2 class="demoHeaders">Progressbar</h2>
<div id="progressbar"></div>
<!-- Progressbar -->
<h2 class="demoHeaders">Selectmenu</h2>
<select id="selectmenu">
@@ -374,14 +353,10 @@
<option>Faster</option>
</select>
<!-- Spinner -->
<h2 class="demoHeaders">Spinner</h2>
<input id="spinner">
<!-- Menu -->
<h2 class="demoHeaders">Menu</h2>
<ul style="width:100px;" id="menu">
@@ -400,8 +375,6 @@
<li><div>Item 5</div></li>
</ul>
<!-- Tooltip -->
<h2 class="demoHeaders">Tooltip</h2>
<p id="tooltip">
@@ -409,7 +382,6 @@
the element with your mouse, the title attribute is displayed in a little box next to the element, just like a native tooltip.
</p>
<!-- Highlight / Error -->
<h2 class="demoHeaders">Highlight / Error</h2>
<div class="ui-widget">
@@ -429,11 +401,8 @@ the element with your mouse, the title attribute is displayed in a little box ne
<script src="external/jquery/jquery.js"></script>
<script src="jquery-ui.js"></script>
<script>
$( "#accordion" ).accordion();
var availableTags = [
"ActionScript",
"AppleScript",
@@ -462,28 +431,18 @@ $( "#autocomplete" ).autocomplete({
source: availableTags
});
$( "#button" ).button();
$( "#button-icon" ).button({
icon: "ui-icon-gear",
showLabel: false
});
$( "#radioset" ).buttonset();
$( "#controlgroup" ).controlgroup();
$( "#tabs" ).tabs();
$( "#dialog" ).dialog({
autoOpen: false,
width: 400,
@@ -509,42 +468,27 @@ $( "#dialog-link" ).click(function( event ) {
event.preventDefault();
});
$( "#datepicker" ).datepicker({
inline: true
});
$( "#slider" ).slider({
range: true,
values: [ 17, 67 ]
});
$( "#progressbar" ).progressbar({
value: 20
});
$( "#spinner" ).spinner();
$( "#menu" ).menu();
$( "#tooltip" ).tooltip();
$( "#selectmenu" ).selectmenu();
// Hover states on the static widgets
$( "#dialog-link, #icons li" ).hover(
function() {

View File

@@ -1,4 +1,4 @@
/*! jQuery UI - v1.12.1 - 2016-09-14
/*! jQuery UI - v1.13.2 - 2022-07-14
* http://jqueryui.com
* Includes: core.css, accordion.css, autocomplete.css, menu.css, button.css, controlgroup.css, checkboxradio.css, datepicker.css, dialog.css, draggable.css, resizable.css, progressbar.css, selectable.css, selectmenu.css, slider.css, sortable.css, spinner.css, tabs.css, tooltip.css, theme.css
* To view and modify this theme, visit http://jqueryui.com/themeroller/?bgShadowXPos=&bgOverlayXPos=&bgErrorXPos=&bgHighlightXPos=&bgContentXPos=&bgHeaderXPos=&bgActiveXPos=&bgHoverXPos=&bgDefaultXPos=&bgShadowYPos=&bgOverlayYPos=&bgErrorYPos=&bgHighlightYPos=&bgContentYPos=&bgHeaderYPos=&bgActiveYPos=&bgHoverYPos=&bgDefaultYPos=&bgShadowRepeat=&bgOverlayRepeat=&bgErrorRepeat=&bgHighlightRepeat=&bgContentRepeat=&bgHeaderRepeat=&bgActiveRepeat=&bgHoverRepeat=&bgDefaultRepeat=&iconsHover=url(%22images%2Fui-icons_555555_256x240.png%22)&iconsHighlight=url(%22images%2Fui-icons_777620_256x240.png%22)&iconsHeader=url(%22images%2Fui-icons_444444_256x240.png%22)&iconsError=url(%22images%2Fui-icons_cc0000_256x240.png%22)&iconsDefault=url(%22images%2Fui-icons_777777_256x240.png%22)&iconsContent=url(%22images%2Fui-icons_444444_256x240.png%22)&iconsActive=url(%22images%2Fui-icons_ffffff_256x240.png%22)&bgImgUrlShadow=&bgImgUrlOverlay=&bgImgUrlHover=&bgImgUrlHighlight=&bgImgUrlHeader=&bgImgUrlError=&bgImgUrlDefault=&bgImgUrlContent=&bgImgUrlActive=&opacityFilterShadow=Alpha(Opacity%3D30)&opacityFilterOverlay=Alpha(Opacity%3D30)&opacityShadowPerc=30&opacityOverlayPerc=30&iconColorHover=%23555555&iconColorHighlight=%23777620&iconColorHeader=%23444444&iconColorError=%23cc0000&iconColorDefault=%23777777&iconColorContent=%23444444&iconColorActive=%23ffffff&bgImgOpacityShadow=0&bgImgOpacityOverlay=0&bgImgOpacityError=95&bgImgOpacityHighlight=55&bgImgOpacityContent=75&bgImgOpacityHeader=75&bgImgOpacityActive=65&bgImgOpacityHover=75&bgImgOpacityDefault=75&bgTextureShadow=flat&bgTextureOverlay=flat&bgTextureError=flat&bgTextureHighlight=flat&bgTextureContent=flat&bgTextureHeader=flat&bgTextureActive=flat&bgTextureHover=flat&bgTextureDefault=flat&cornerRadius=3px&fwDefault=normal&ffDefault=Arial%2CHelvetica%2Csans-serif&fsDefault=1em&cornerRadiusShadow=8px&thicknessShadow=5px&offsetLeftShadow=0px&offsetTopShadow=0px&opacityShadow=.3&bgColorShadow=%23666666&opacityOverlay=.3&bgColorOverlay=%23aaaaaa&fcError=%235f3f3f&borderColorError=%23f1a899&bgColorError=%23fddfdf&fcHighlight=%23777620&borderColorHighlight=%23dad55e&bgColorHighlight=%23fffa90&fcContent=%23333333&borderColorContent=%23dddddd&bgColorContent=%23ffffff&fcHeader=%23333333&borderColorHeader=%23dddddd&bgColorHeader=%23e9e9e9&fcActive=%23ffffff&borderColorActive=%23003eff&bgColorActive=%23007fff&fcHover=%232b2b2b&borderColorHover=%23cccccc&bgColorHover=%23ededed&fcDefault=%23454545&borderColorDefault=%23c5c5c5&bgColorDefault=%23f6f6f6
@@ -45,7 +45,7 @@
left: 0;
position: absolute;
opacity: 0;
filter:Alpha(Opacity=0); /* support: IE8 */
-ms-filter: "alpha(opacity=0)"; /* support: IE8 */
}
.ui-front {
@@ -664,7 +664,7 @@ button.ui-button::-moz-focus-inner {
.ui-progressbar .ui-progressbar-overlay {
background: url("data:image/gif;base64,R0lGODlhKAAoAIABAAAAAP///yH/C05FVFNDQVBFMi4wAwEAAAAh+QQJAQABACwAAAAAKAAoAAACkYwNqXrdC52DS06a7MFZI+4FHBCKoDeWKXqymPqGqxvJrXZbMx7Ttc+w9XgU2FB3lOyQRWET2IFGiU9m1frDVpxZZc6bfHwv4c1YXP6k1Vdy292Fb6UkuvFtXpvWSzA+HycXJHUXiGYIiMg2R6W459gnWGfHNdjIqDWVqemH2ekpObkpOlppWUqZiqr6edqqWQAAIfkECQEAAQAsAAAAACgAKAAAApSMgZnGfaqcg1E2uuzDmmHUBR8Qil95hiPKqWn3aqtLsS18y7G1SzNeowWBENtQd+T1JktP05nzPTdJZlR6vUxNWWjV+vUWhWNkWFwxl9VpZRedYcflIOLafaa28XdsH/ynlcc1uPVDZxQIR0K25+cICCmoqCe5mGhZOfeYSUh5yJcJyrkZWWpaR8doJ2o4NYq62lAAACH5BAkBAAEALAAAAAAoACgAAAKVDI4Yy22ZnINRNqosw0Bv7i1gyHUkFj7oSaWlu3ovC8GxNso5fluz3qLVhBVeT/Lz7ZTHyxL5dDalQWPVOsQWtRnuwXaFTj9jVVh8pma9JjZ4zYSj5ZOyma7uuolffh+IR5aW97cHuBUXKGKXlKjn+DiHWMcYJah4N0lYCMlJOXipGRr5qdgoSTrqWSq6WFl2ypoaUAAAIfkECQEAAQAsAAAAACgAKAAAApaEb6HLgd/iO7FNWtcFWe+ufODGjRfoiJ2akShbueb0wtI50zm02pbvwfWEMWBQ1zKGlLIhskiEPm9R6vRXxV4ZzWT2yHOGpWMyorblKlNp8HmHEb/lCXjcW7bmtXP8Xt229OVWR1fod2eWqNfHuMjXCPkIGNileOiImVmCOEmoSfn3yXlJWmoHGhqp6ilYuWYpmTqKUgAAIfkECQEAAQAsAAAAACgAKAAAApiEH6kb58biQ3FNWtMFWW3eNVcojuFGfqnZqSebuS06w5V80/X02pKe8zFwP6EFWOT1lDFk8rGERh1TTNOocQ61Hm4Xm2VexUHpzjymViHrFbiELsefVrn6XKfnt2Q9G/+Xdie499XHd2g4h7ioOGhXGJboGAnXSBnoBwKYyfioubZJ2Hn0RuRZaflZOil56Zp6iioKSXpUAAAh+QQJAQABACwAAAAAKAAoAAACkoQRqRvnxuI7kU1a1UU5bd5tnSeOZXhmn5lWK3qNTWvRdQxP8qvaC+/yaYQzXO7BMvaUEmJRd3TsiMAgswmNYrSgZdYrTX6tSHGZO73ezuAw2uxuQ+BbeZfMxsexY35+/Qe4J1inV0g4x3WHuMhIl2jXOKT2Q+VU5fgoSUI52VfZyfkJGkha6jmY+aaYdirq+lQAACH5BAkBAAEALAAAAAAoACgAAAKWBIKpYe0L3YNKToqswUlvznigd4wiR4KhZrKt9Upqip61i9E3vMvxRdHlbEFiEXfk9YARYxOZZD6VQ2pUunBmtRXo1Lf8hMVVcNl8JafV38aM2/Fu5V16Bn63r6xt97j09+MXSFi4BniGFae3hzbH9+hYBzkpuUh5aZmHuanZOZgIuvbGiNeomCnaxxap2upaCZsq+1kAACH5BAkBAAEALAAAAAAoACgAAAKXjI8By5zf4kOxTVrXNVlv1X0d8IGZGKLnNpYtm8Lr9cqVeuOSvfOW79D9aDHizNhDJidFZhNydEahOaDH6nomtJjp1tutKoNWkvA6JqfRVLHU/QUfau9l2x7G54d1fl995xcIGAdXqMfBNadoYrhH+Mg2KBlpVpbluCiXmMnZ2Sh4GBqJ+ckIOqqJ6LmKSllZmsoq6wpQAAAh+QQJAQABACwAAAAAKAAoAAAClYx/oLvoxuJDkU1a1YUZbJ59nSd2ZXhWqbRa2/gF8Gu2DY3iqs7yrq+xBYEkYvFSM8aSSObE+ZgRl1BHFZNr7pRCavZ5BW2142hY3AN/zWtsmf12p9XxxFl2lpLn1rseztfXZjdIWIf2s5dItwjYKBgo9yg5pHgzJXTEeGlZuenpyPmpGQoKOWkYmSpaSnqKileI2FAAACH5BAkBAAEALAAAAAAoACgAAAKVjB+gu+jG4kORTVrVhRlsnn2dJ3ZleFaptFrb+CXmO9OozeL5VfP99HvAWhpiUdcwkpBH3825AwYdU8xTqlLGhtCosArKMpvfa1mMRae9VvWZfeB2XfPkeLmm18lUcBj+p5dnN8jXZ3YIGEhYuOUn45aoCDkp16hl5IjYJvjWKcnoGQpqyPlpOhr3aElaqrq56Bq7VAAAOw==");
height: 100%;
filter: alpha(opacity=25); /* support: IE8 */
-ms-filter: "alpha(opacity=25)"; /* support: IE8 */
opacity: 0.25;
}
.ui-progressbar-indeterminate .ui-progressbar-value {
@@ -728,7 +728,7 @@ button.ui-button::-moz-focus-inner {
z-index: 2;
width: 1.2em;
height: 1.2em;
cursor: default;
cursor: pointer;
-ms-touch-action: none;
touch-action: none;
}
@@ -1041,18 +1041,18 @@ a.ui-button:active,
.ui-widget-content .ui-priority-secondary,
.ui-widget-header .ui-priority-secondary {
opacity: .7;
filter:Alpha(Opacity=70); /* support: IE8 */
-ms-filter: "alpha(opacity=70)"; /* support: IE8 */
font-weight: normal;
}
.ui-state-disabled,
.ui-widget-content .ui-state-disabled,
.ui-widget-header .ui-state-disabled {
opacity: .35;
filter:Alpha(Opacity=35); /* support: IE8 */
-ms-filter: "alpha(opacity=35)"; /* support: IE8 */
background-image: none;
}
.ui-state-disabled .ui-icon {
filter:Alpha(Opacity=35); /* support: IE8 - See #6059 */
-ms-filter: "alpha(opacity=35)"; /* support: IE8 - See #6059 */
}
/* Icons
@@ -1093,7 +1093,10 @@ a.ui-button:active,
}
/* positioning */
.ui-icon-blank { background-position: 16px 16px; }
/* Three classes needed to override `.ui-button:hover .ui-icon` */
.ui-icon-blank.ui-icon-blank.ui-icon-blank {
background-image: none;
}
.ui-icon-caret-1-n { background-position: 0 0; }
.ui-icon-caret-1-ne { background-position: -16px 0; }
.ui-icon-caret-1-e { background-position: -32px 0; }
@@ -1304,7 +1307,7 @@ a.ui-button:active,
.ui-widget-overlay {
background: #aaaaaa;
opacity: .003;
filter: Alpha(Opacity=.3); /* support: IE8 */
-ms-filter: Alpha(Opacity=.3); /* support: IE8 */
}
.ui-widget-shadow {
-webkit-box-shadow: 0px 0px 5px #666666;

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1,5 +1,5 @@
/*!
* jQuery UI CSS Framework 1.12.1
* jQuery UI CSS Framework 1.13.2
* http://jqueryui.com
*
* Copyright jQuery Foundation and other contributors
@@ -49,7 +49,7 @@
left: 0;
position: absolute;
opacity: 0;
filter:Alpha(Opacity=0); /* support: IE8 */
-ms-filter: "alpha(opacity=0)"; /* support: IE8 */
}
.ui-front {
@@ -668,7 +668,7 @@ button.ui-button::-moz-focus-inner {
.ui-progressbar .ui-progressbar-overlay {
background: url("data:image/gif;base64,R0lGODlhKAAoAIABAAAAAP///yH/C05FVFNDQVBFMi4wAwEAAAAh+QQJAQABACwAAAAAKAAoAAACkYwNqXrdC52DS06a7MFZI+4FHBCKoDeWKXqymPqGqxvJrXZbMx7Ttc+w9XgU2FB3lOyQRWET2IFGiU9m1frDVpxZZc6bfHwv4c1YXP6k1Vdy292Fb6UkuvFtXpvWSzA+HycXJHUXiGYIiMg2R6W459gnWGfHNdjIqDWVqemH2ekpObkpOlppWUqZiqr6edqqWQAAIfkECQEAAQAsAAAAACgAKAAAApSMgZnGfaqcg1E2uuzDmmHUBR8Qil95hiPKqWn3aqtLsS18y7G1SzNeowWBENtQd+T1JktP05nzPTdJZlR6vUxNWWjV+vUWhWNkWFwxl9VpZRedYcflIOLafaa28XdsH/ynlcc1uPVDZxQIR0K25+cICCmoqCe5mGhZOfeYSUh5yJcJyrkZWWpaR8doJ2o4NYq62lAAACH5BAkBAAEALAAAAAAoACgAAAKVDI4Yy22ZnINRNqosw0Bv7i1gyHUkFj7oSaWlu3ovC8GxNso5fluz3qLVhBVeT/Lz7ZTHyxL5dDalQWPVOsQWtRnuwXaFTj9jVVh8pma9JjZ4zYSj5ZOyma7uuolffh+IR5aW97cHuBUXKGKXlKjn+DiHWMcYJah4N0lYCMlJOXipGRr5qdgoSTrqWSq6WFl2ypoaUAAAIfkECQEAAQAsAAAAACgAKAAAApaEb6HLgd/iO7FNWtcFWe+ufODGjRfoiJ2akShbueb0wtI50zm02pbvwfWEMWBQ1zKGlLIhskiEPm9R6vRXxV4ZzWT2yHOGpWMyorblKlNp8HmHEb/lCXjcW7bmtXP8Xt229OVWR1fod2eWqNfHuMjXCPkIGNileOiImVmCOEmoSfn3yXlJWmoHGhqp6ilYuWYpmTqKUgAAIfkECQEAAQAsAAAAACgAKAAAApiEH6kb58biQ3FNWtMFWW3eNVcojuFGfqnZqSebuS06w5V80/X02pKe8zFwP6EFWOT1lDFk8rGERh1TTNOocQ61Hm4Xm2VexUHpzjymViHrFbiELsefVrn6XKfnt2Q9G/+Xdie499XHd2g4h7ioOGhXGJboGAnXSBnoBwKYyfioubZJ2Hn0RuRZaflZOil56Zp6iioKSXpUAAAh+QQJAQABACwAAAAAKAAoAAACkoQRqRvnxuI7kU1a1UU5bd5tnSeOZXhmn5lWK3qNTWvRdQxP8qvaC+/yaYQzXO7BMvaUEmJRd3TsiMAgswmNYrSgZdYrTX6tSHGZO73ezuAw2uxuQ+BbeZfMxsexY35+/Qe4J1inV0g4x3WHuMhIl2jXOKT2Q+VU5fgoSUI52VfZyfkJGkha6jmY+aaYdirq+lQAACH5BAkBAAEALAAAAAAoACgAAAKWBIKpYe0L3YNKToqswUlvznigd4wiR4KhZrKt9Upqip61i9E3vMvxRdHlbEFiEXfk9YARYxOZZD6VQ2pUunBmtRXo1Lf8hMVVcNl8JafV38aM2/Fu5V16Bn63r6xt97j09+MXSFi4BniGFae3hzbH9+hYBzkpuUh5aZmHuanZOZgIuvbGiNeomCnaxxap2upaCZsq+1kAACH5BAkBAAEALAAAAAAoACgAAAKXjI8By5zf4kOxTVrXNVlv1X0d8IGZGKLnNpYtm8Lr9cqVeuOSvfOW79D9aDHizNhDJidFZhNydEahOaDH6nomtJjp1tutKoNWkvA6JqfRVLHU/QUfau9l2x7G54d1fl995xcIGAdXqMfBNadoYrhH+Mg2KBlpVpbluCiXmMnZ2Sh4GBqJ+ckIOqqJ6LmKSllZmsoq6wpQAAAh+QQJAQABACwAAAAAKAAoAAAClYx/oLvoxuJDkU1a1YUZbJ59nSd2ZXhWqbRa2/gF8Gu2DY3iqs7yrq+xBYEkYvFSM8aSSObE+ZgRl1BHFZNr7pRCavZ5BW2142hY3AN/zWtsmf12p9XxxFl2lpLn1rseztfXZjdIWIf2s5dItwjYKBgo9yg5pHgzJXTEeGlZuenpyPmpGQoKOWkYmSpaSnqKileI2FAAACH5BAkBAAEALAAAAAAoACgAAAKVjB+gu+jG4kORTVrVhRlsnn2dJ3ZleFaptFrb+CXmO9OozeL5VfP99HvAWhpiUdcwkpBH3825AwYdU8xTqlLGhtCosArKMpvfa1mMRae9VvWZfeB2XfPkeLmm18lUcBj+p5dnN8jXZ3YIGEhYuOUn45aoCDkp16hl5IjYJvjWKcnoGQpqyPlpOhr3aElaqrq56Bq7VAAAOw==");
height: 100%;
filter: alpha(opacity=25); /* support: IE8 */
-ms-filter: "alpha(opacity=25)"; /* support: IE8 */
opacity: 0.25;
}
.ui-progressbar-indeterminate .ui-progressbar-value {
@@ -732,7 +732,7 @@ button.ui-button::-moz-focus-inner {
z-index: 2;
width: 1.2em;
height: 1.2em;
cursor: default;
cursor: pointer;
-ms-touch-action: none;
touch-action: none;
}

File diff suppressed because one or more lines are too long

View File

@@ -1,5 +1,5 @@
/*!
* jQuery UI CSS Framework 1.12.1
* jQuery UI CSS Framework 1.13.2
* http://jqueryui.com
*
* Copyright jQuery Foundation and other contributors
@@ -172,18 +172,18 @@ a.ui-button:active,
.ui-widget-content .ui-priority-secondary,
.ui-widget-header .ui-priority-secondary {
opacity: .7;
filter:Alpha(Opacity=70); /* support: IE8 */
-ms-filter: "alpha(opacity=70)"; /* support: IE8 */
font-weight: normal;
}
.ui-state-disabled,
.ui-widget-content .ui-state-disabled,
.ui-widget-header .ui-state-disabled {
opacity: .35;
filter:Alpha(Opacity=35); /* support: IE8 */
-ms-filter: "alpha(opacity=35)"; /* support: IE8 */
background-image: none;
}
.ui-state-disabled .ui-icon {
filter:Alpha(Opacity=35); /* support: IE8 - See #6059 */
-ms-filter: "alpha(opacity=35)"; /* support: IE8 - See #6059 */
}
/* Icons
@@ -224,7 +224,10 @@ a.ui-button:active,
}
/* positioning */
.ui-icon-blank { background-position: 16px 16px; }
/* Three classes needed to override `.ui-button:hover .ui-icon` */
.ui-icon-blank.ui-icon-blank.ui-icon-blank {
background-image: none;
}
.ui-icon-caret-1-n { background-position: 0 0; }
.ui-icon-caret-1-ne { background-position: -16px 0; }
.ui-icon-caret-1-e { background-position: -32px 0; }
@@ -435,7 +438,7 @@ a.ui-button:active,
.ui-widget-overlay {
background: #aaaaaa;
opacity: .003;
filter: Alpha(Opacity=.3); /* support: IE8 */
-ms-filter: Alpha(Opacity=.3); /* support: IE8 */
}
.ui-widget-shadow {
-webkit-box-shadow: 0px 0px 5px #666666;

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -32,33 +32,33 @@
"@babel/preset-env": "^7.14.2",
"@babel/preset-typescript": "^7.13.0",
"@babel/runtime": "^7.14.0",
"@deanc/esbuild-plugin-postcss": "^1.0.1",
"@deanc/esbuild-plugin-postcss": "^1.0.2",
"@testing-library/dom": "^7.31.0",
"@testing-library/jest-dom": "^5.12.0",
"@testing-library/user-event": "^13.1.9",
"@types/highlightjs": "^9.12.1",
"@types/jest": "^26.0.23",
"@types/jqueryui": "1.12.15",
"@types/jqueryui": "1.12.16",
"@types/lodash": "^4.14.170",
"@types/node": "^15.6.1",
"@types/showdown": "^1.9.3",
"@typescript-eslint/eslint-plugin": "^4.25.0",
"@typescript-eslint/parser": "^4.25.0",
"@typescript-eslint/eslint-plugin": "^5.38.1",
"@typescript-eslint/parser": "^5.38.1",
"autoprefixer": "^10.2.6",
"bootstrap-datepicker": "1.9.0",
"browserslist": "^4.19.1",
"caniuse-lite": "^1.0.30001312",
"core-js": "^3.13.0",
"esbuild": "^0.12.4",
"esbuild": "^0.15.10",
"esbuild-plugin-babel": "https://github.com/schloerke/esbuild-plugin-babel#patch-2",
"esbuild-plugin-globals": "^0.1.1",
"esbuild-plugin-sass": "^0.5.2",
"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",
"esbuild-plugin-sass": "^1.0.1",
"eslint": "^8.24.0",
"eslint-config-prettier": "^8.5.0",
"eslint-plugin-jest": "^27.0.4",
"eslint-plugin-jest-dom": "^4.0.2",
"eslint-plugin-prettier": "^4.2.1",
"eslint-plugin-unicorn": "^43.0.2",
"ion-rangeslider": "2.3.1",
"jest": "^26.6.3",
"jquery": "3.6.0",
@@ -67,15 +67,15 @@
"node-gyp": "^8.1.0",
"phantomjs-prebuilt": "^2.1.16",
"postcss": "^8.3.5",
"prettier": "2.3.0",
"prettier": "^2.7.1",
"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",
"ts-node": "^10.9.1",
"type-coverage": "^2.22.0",
"typescript": "^4.8.4",
"util-inspect": "https://github.com/deecewan/browser-util-inspect#c0b4350df4378ffd743e8c36dd3898ce3992823e"
},
"scripts": {

View File

@@ -1,13 +1,23 @@
import {
build as esbuildBuild,
import type {
BuildFailure,
BuildIncremental,
BuildOptions,
BuildResult,
WatchMode,
} from "esbuild";
import readcontrol from "readcontrol";
import { build as esbuildBuild } from "esbuild";
import process from "process";
import { basename } from "path";
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore; Type definitions are not found. This occurs when `strict: true` in tsconfig.json
import readcontrol from "readcontrol";
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore; Type definitions are not found. This occurs when `strict: true` in tsconfig.json
import babelPlugin from "esbuild-plugin-babel";
const outDir = "./inst/www/shared/";
type ShinyDesc = { version: string; package: string; license: string };
@@ -42,7 +52,7 @@ async function build(
}
}
const onRebuild = function (error?: string) {
const onRebuild = function (error: BuildFailure | null) {
if (error) {
console.error(printNames.join(", "), "watch build failed:\n", error);
} else {
@@ -54,7 +64,7 @@ async function build(
};
let incremental = false;
let watch: false | { onRebuild: (error, result) => void } = false;
let watch: WatchMode | false = false;
if (process.argv.length >= 3 && process.argv[2] == "--watch") {
incremental = true;
@@ -69,13 +79,14 @@ async function build(
return esbuildBuild({
incremental: incremental,
watch: watch,
target: "es5",
target: "es2020",
format: "iife",
preserveSymlinks: true,
...opts,
}).then((x) => {
onRebuild();
onRebuild(null);
return x;
});
}
export { outDir, build, shinyDesc, banner };
export { outDir, build, shinyDesc, banner, babelPlugin };

View File

@@ -5,8 +5,7 @@
// - TypeScript -----------------------------------------------------------
import { banner, build, outDir } from "./_build";
import babelPlugin from "esbuild-plugin-babel";
import { banner, build, outDir, babelPlugin } from "./_build";
build({
bundle: true,
@@ -25,8 +24,10 @@ build({
// - Sass -----------------------------------------------------------
import autoprefixer from "autoprefixer";
import postCssPlugin from "@deanc/esbuild-plugin-postcss";
import sassPlugin from "esbuild-plugin-sass";
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore; Type definitions are not found. This occurs when `strict: true` in tsconfig.json
import postCssPlugin from "@deanc/esbuild-plugin-postcss";
const sassOpts = {
minify: true,

View File

@@ -3,9 +3,8 @@
// yarn build
// ```
import { banner, build, outDir, shinyDesc } from "./_build";
import { banner, build, outDir, shinyDesc, babelPlugin } from "./_build";
import globalsPlugin from "esbuild-plugin-globals";
import babelPlugin from "esbuild-plugin-babel";
import type { BuildOptions } from "esbuild";
const opts: BuildOptions = {

View File

@@ -1,9 +1,8 @@
import $ from "jquery";
import { hasDefinedProperty } from "../../utils";
import { InputBinding } from "./inputBinding";
import { hasOwnProperty } from "../../utils";
type ActionButtonReceiveMessageData = { label?: string; icon?: string };
type ActionButtonReceiveMessageData = { label?: string; icon?: string | [] };
class ActionButtonInputBinding extends InputBinding {
find(scope: HTMLElement): JQuery<HTMLElement> {
@@ -40,7 +39,7 @@ class ActionButtonInputBinding extends InputBinding {
const $el = $(el);
// retrieve current label and icon
let label = $el.text();
let label: string = $el.text();
let icon = "";
// to check (and store) the previous icon, we look for a $el child
@@ -57,16 +56,18 @@ class ActionButtonInputBinding extends InputBinding {
}
// update the requested properties
if (hasOwnProperty(data, "label")) label = data.label;
if (hasOwnProperty(data, "icon")) {
icon = data.icon;
// if the user entered icon=character(0), remove the icon
if (icon.length === 0) icon = "";
if (hasDefinedProperty(data, "label")) {
label = data.label;
}
if (hasDefinedProperty(data, "icon")) {
// `data.icon` can be an [] if user gave `character(0)`.
icon = Array.isArray(data.icon) ? "" : data.icon ?? "";
}
// produce new html
$el.html(icon + " " + label);
}
unsubscribe(el: HTMLElement): void {
$(el).off(".actionButtonInputBinding");
}

View File

@@ -1,6 +1,6 @@
import $ from "jquery";
import { InputBinding } from "./inputBinding";
import { hasOwnProperty } from "../../utils";
import { hasDefinedProperty } from "../../utils";
type CheckedHTMLElement = HTMLInputElement;
@@ -35,12 +35,15 @@ class CheckboxInputBinding extends InputBinding {
el: CheckedHTMLElement,
data: CheckboxReceiveMessageData
): void {
if (hasOwnProperty(data, "value")) el.checked = data.value;
if (hasDefinedProperty(data, "value")) {
el.checked = data.value;
}
// checkboxInput()'s label works different from other
// input labels...the label container should always exist
if (hasOwnProperty(data, "label"))
if (hasDefinedProperty(data, "label")) {
$(el).parent().find("span").text(data.label);
}
$(el).trigger("change");
}

View File

@@ -1,7 +1,7 @@
import $ from "jquery";
import { InputBinding } from "./inputBinding";
import { $escape, hasOwnProperty, updateLabel } from "../../utils";
import { $escape, updateLabel, hasDefinedProperty } from "../../utils";
import type { CheckedHTMLElement } from "./checkbox";
type CheckboxGroupHTMLElement = CheckedHTMLElement;
@@ -24,9 +24,11 @@ function getLabelNode(el: CheckboxGroupHTMLElement): JQuery<HTMLElement> {
// 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 {
const parentNode = obj.parentNode as HTMLElement;
// If <label><input /><span>label text</span></label>
if ((obj.parentNode as HTMLElement).tagName === "LABEL") {
return $(obj.parentNode).find("span").text().trim();
if (parentNode.tagName === "LABEL") {
return $(parentNode).find("span").text().trim();
}
return null;
@@ -35,9 +37,11 @@ function getLabel(obj: HTMLElement): string | null {
// 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 {
const parentNode = obj.parentNode as HTMLElement;
// If <label><input /><span>label text</span></label>
if ((obj.parentNode as HTMLElement).tagName === "LABEL") {
$(obj.parentNode).find("span").text(value);
if (parentNode.tagName === "LABEL") {
$(parentNode).find("span").text(value);
}
return null;
@@ -58,7 +62,10 @@ class CheckboxGroupInputBinding extends InputBinding {
}
return values;
}
setValue(el: HTMLElement, value: string[] | string): void {
setValue(el: HTMLElement, value: string[] | string | null): void {
// Null value should be treated as empty array
value = value ?? [];
// Clear all checkboxes
$('input:checkbox[name="' + $escape(el.id) + '"]').prop("checked", false);
@@ -113,7 +120,7 @@ class CheckboxGroupInputBinding extends InputBinding {
const $el = $(el);
// This will replace all the options
if (hasOwnProperty(data, "options")) {
if (hasDefinedProperty(data, "options")) {
// Clear existing options and add each new one
$el.find("div.shiny-options-group").remove();
// Backward compatibility: for HTML generated by shinybootstrap2 package
@@ -121,7 +128,9 @@ class CheckboxGroupInputBinding extends InputBinding {
$el.append(data.options);
}
if (hasOwnProperty(data, "value")) this.setValue(el, data.value);
if (hasDefinedProperty(data, "value")) {
this.setValue(el, data.value);
}
updateLabel(data.label, getLabelNode(el));

View File

@@ -5,8 +5,9 @@ import {
updateLabel,
$escape,
parseDate,
hasOwnProperty,
hasDefinedProperty,
} from "../../utils";
import type { NotUndefined } from "../../utils/extraTypes";
declare global {
interface JQuery {
@@ -14,7 +15,9 @@ declare global {
bsDatepicker(methodName: "getUTCDate"): Date;
// Infinity is not allowed as a literal return type. Using `1e9999` as a placeholder that resolves to Infinity
// https://github.com/microsoft/TypeScript/issues/32277
// eslint-disable-next-line @typescript-eslint/no-loss-of-precision
bsDatepicker(methodName: "getStartDate"): Date | -1e9999;
// eslint-disable-next-line @typescript-eslint/no-loss-of-precision
bsDatepicker(methodName: "getEndDate"): Date | 1e9999;
bsDatepicker(methodName: string): void;
bsDatepicker(methodName: string, params: Date | null): void;
@@ -122,8 +125,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,
protected _setMin(el: HTMLElement, date: Date | null | undefined): void {
if (date === undefined) return;
protected _setMin(el: HTMLElement, date: Date | null): void {
if (date === null) {
$(el).bsDatepicker("setStartDate", null);
return;
@@ -161,8 +163,7 @@ class DateInputBindingBase extends InputBinding {
}
// Given an unambiguous date string or a Date object, set the max (end) date
// null will unset.
protected _setMax(el: HTMLElement, date: Date): void {
if (date === undefined) return;
protected _setMax(el: HTMLElement, date: Date | null): void {
if (date === null) {
$(el).bsDatepicker("setEndDate", null);
return;
@@ -236,7 +237,7 @@ class DateInputBinding extends DateInputBindingBase {
return formatDateUTC(date);
}
// value must be an unambiguous string like '2001-01-01', or a Date object.
setValue(el: HTMLElement, value: Date): void {
setValue(el: HTMLElement, value: Date | null): void {
// R's NA, which is null here will remove current value
if (value === null) {
$(el).find("input").val("").bsDatepicker("update");
@@ -286,7 +287,7 @@ class DateInputBinding extends DateInputBindingBase {
return {
label: this._getLabelNode(el).text(),
value: this.getValue(el),
valueString: $input.val(),
valueString: $input.val() as NotUndefined<ReturnType<typeof $input.val>>,
min: min,
max: max,
language: $input.data("datepicker").language,
@@ -300,14 +301,14 @@ class DateInputBinding extends DateInputBindingBase {
updateLabel(data.label, this._getLabelNode(el));
if (hasOwnProperty(data, "min")) this._setMin($input[0], data.min);
if (hasDefinedProperty(data, "min")) this._setMin($input[0], data.min);
if (hasOwnProperty(data, "max")) this._setMax($input[0], data.max);
if (hasDefinedProperty(data, "max")) this._setMax($input[0], data.max);
// Must set value only after min and max have been set. If new value is
// outside the bounds of the previous min/max, then the result will be a
// blank input.
if (hasOwnProperty(data, "value")) this.setValue(el, data.value);
if (hasDefinedProperty(data, "value")) this.setValue(el, data.value);
$(el).trigger("change");
}

View File

@@ -3,7 +3,7 @@ import $ from "jquery";
import {
$escape,
formatDateUTC,
hasOwnProperty,
hasDefinedProperty,
updateLabel,
} from "../../utils";
import { DateInputBindingBase } from "./date";
@@ -114,12 +114,12 @@ class DateRangeInputBinding extends DateInputBindingBase {
updateLabel(data.label, getLabelNode(el));
if (hasOwnProperty(data, "min")) {
if (hasDefinedProperty(data, "min")) {
this._setMin($startinput[0], data.min);
this._setMin($endinput[0], data.min);
}
if (hasOwnProperty(data, "max")) {
if (hasDefinedProperty(data, "max")) {
this._setMax($startinput[0], data.max);
this._setMax($endinput[0], data.max);
}
@@ -127,7 +127,9 @@ class DateRangeInputBinding extends DateInputBindingBase {
// Must set value only after min and max have been set. If new value is
// outside the bounds of the previous min/max, then the result will be a
// blank input.
if (hasOwnProperty(data, "value")) this.setValue(el, data.value);
if (hasDefinedProperty(data, "value")) {
this.setValue(el, data.value);
}
$el.trigger("change");
}

View File

@@ -24,6 +24,7 @@ function enableDraghover(el: JQuery<HTMLElement>): JQuery<HTMLElement> {
const $el = $(el);
let childCounter = 0;
/* eslint-disable @typescript-eslint/naming-convention */
$el.on({
"dragenter.draghover": (e) => {
if (childCounter++ === 0) {
@@ -91,7 +92,7 @@ function canSetFiles(fileList: FileList): boolean {
return true;
}
function handleDrop(e: JQuery.DragEventBase, el: HTMLInputElement): void {
const files = e.originalEvent.dataTransfer.files,
const files = e.originalEvent?.dataTransfer?.files,
$el = $(el);
if (files === undefined || files === null) {
@@ -109,7 +110,7 @@ function handleDrop(e: JQuery.DragEventBase, el: HTMLInputElement): void {
// 3. The browser supports FileList and input.files assignment.
// (Chrome, Safari)
$el.val("");
el.files = e.originalEvent.dataTransfer.files;
el.files = 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
@@ -188,7 +189,7 @@ function uploadFiles(evt: JQuery.DragEvent): void {
// TODO-barret ; Should this be an internal class property?
let $fileInputs = $();
function fileInputBindingGetId(el: HTMLInputElement): string {
function fileInputBindingGetId(this: any, el: HTMLInputElement): string {
return InputBinding.prototype.getId.call(this, el) || el.name;
}

View File

@@ -2,14 +2,13 @@ import type { RatePolicyModes } from "../../inputPolicies/inputRateDecorator";
import type { BindScope } from "../../shiny/bind";
class InputBinding {
name: string;
name!: string;
// Returns a jQuery object or element array that contains the
// descendants of scope that match this binding
find(scope: BindScope): JQuery<HTMLElement> {
throw "Not implemented";
// add so that typescript isn't mad about an unused var
scope;
scope; // unused var
}
getId(el: HTMLElement): string {
@@ -18,9 +17,9 @@ class InputBinding {
// Gives the input a type in case the server needs to know it
// to deserialize the JSON correctly
getType(el: HTMLElement): string | false {
return false;
el;
getType(el: HTMLElement): string | null {
return null;
el; // unused var
}
getValue(el: HTMLElement): any {
throw "Not implemented";
@@ -32,12 +31,12 @@ class InputBinding {
// getRatePolicy. If false, send value immediately. Default behavior is `false`
subscribe(el: HTMLElement, callback: (value: boolean) => void): void {
// empty
el;
callback;
el; // unused var
callback; // unused var
}
unsubscribe(el: HTMLElement): void {
// empty
el;
el; // unused var
}
// This is used for receiving messages that tell the input object to do
@@ -47,19 +46,19 @@ class InputBinding {
// trigger a change event.
receiveMessage(el: HTMLElement, data: unknown): void {
throw "Not implemented";
el;
data;
el; // unused var
data; // unused var
}
getState(el: HTMLElement): unknown {
throw "Not implemented";
el;
el; // unused var
}
getRatePolicy(
el: HTMLElement
): { policy: RatePolicyModes; delay: number } | null {
return null;
el;
el; // unused var
}
// Some input objects need initialization before being bound. This is

View File

@@ -1,5 +1,5 @@
import $ from "jquery";
import { $escape, hasOwnProperty, updateLabel } from "../../utils";
import { $escape, hasDefinedProperty, updateLabel } from "../../utils";
import { TextInputBindingBase } from "./text";
type NumberHTMLElement = HTMLInputElement;
@@ -23,7 +23,9 @@ class NumberInputBinding extends TextInputBindingBase {
return $(scope).find('input[type="number"]');
}
getValue(el: NumberHTMLElement): string[] | number | string {
getValue(
el: NumberHTMLElement
): string[] | number | string | null | undefined {
const numberVal = $(el).val();
if (typeof numberVal == "string") {
@@ -49,10 +51,12 @@ class NumberInputBinding extends TextInputBindingBase {
el;
}
receiveMessage(el: NumberHTMLElement, data: NumberReceiveMessageData): void {
if (hasOwnProperty(data, "value")) el.value = data.value;
if (hasOwnProperty(data, "min")) el.min = data.min;
if (hasOwnProperty(data, "max")) el.max = data.max;
if (hasOwnProperty(data, "step")) el.step = data.step;
// Setting values to `""` will remove the attribute value from the DOM element.
// The attr key will still remain, but there is not value... ex: `<input id="foo" type="number" min max/>`
if (hasDefinedProperty(data, "value")) el.value = data.value ?? "";
if (hasDefinedProperty(data, "min")) el.min = data.min ?? "";
if (hasDefinedProperty(data, "max")) el.max = data.max ?? "";
if (hasDefinedProperty(data, "step")) el.step = data.step ?? "";
updateLabel(data.label, getLabelNode(el));

View File

@@ -1,6 +1,6 @@
import $ from "jquery";
import { InputBinding } from "./inputBinding";
import { $escape, hasOwnProperty, updateLabel } from "../../utils";
import { $escape, hasDefinedProperty, updateLabel } from "../../utils";
type RadioHTMLElement = HTMLInputElement;
@@ -10,7 +10,7 @@ type ValueLabelObject = {
};
type RadioReceiveMessageData = {
value?: string;
value?: string | [];
options?: ValueLabelObject[];
label: string;
};
@@ -24,9 +24,11 @@ function getLabelNode(el: RadioHTMLElement): JQuery<HTMLElement> {
// 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 {
const parentNode = obj.parentNode as HTMLElement;
// If <label><input /><span>label text</span></label>
if ((obj.parentNode as HTMLElement).tagName === "LABEL") {
return $(obj.parentNode).find("span").text().trim();
if (parentNode.tagName === "LABEL") {
return $(parentNode).find("span").text().trim();
}
return null;
@@ -35,9 +37,11 @@ function getLabel(obj: HTMLElement): string | null {
// 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 {
const parentNode = obj.parentNode as HTMLElement;
// If <label><input /><span>label text</span></label>
if ((obj.parentNode as HTMLElement).tagName === "LABEL") {
$(obj.parentNode).find("span").text(value);
if (parentNode.tagName === "LABEL") {
$(parentNode).find("span").text(value);
}
return null;
@@ -47,7 +51,9 @@ 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 | undefined {
// Select the radio objects that have name equal to the grouping div's id
const checkedItems = $(
'input:radio[name="' + $escape(el.id) + '"]:checked'
@@ -61,8 +67,8 @@ class RadioInputBinding extends InputBinding {
return checkedItems.val();
}
setValue(el: RadioHTMLElement, value: string): void {
if ($.isArray(value) && value.length === 0) {
setValue(el: RadioHTMLElement, value: string | []): void {
if (Array.isArray(value) && value.length === 0) {
// Removing all checked item if the sent data is empty
$('input:radio[name="' + $escape(el.id) + '"]').prop("checked", false);
} else {
@@ -77,7 +83,7 @@ class RadioInputBinding extends InputBinding {
}
getState(el: RadioHTMLElement): {
label: string;
value: string[] | number | string;
value: ReturnType<RadioInputBinding["getValue"]>;
options: ValueLabelObject[];
} {
const $objs = $(
@@ -101,16 +107,20 @@ class RadioInputBinding extends InputBinding {
const $el = $(el);
// This will replace all the options
if (hasOwnProperty(data, "options")) {
if (hasDefinedProperty(data, "options")) {
// Clear existing options and add each new one
$el.find("div.shiny-options-group").remove();
// Backward compatibility: for HTML generated by shinybootstrap2 package
$el.find("label.radio").remove();
// @ts-expect-error; TODO-barret; IDK what this line is doing
// TODO-barret; Should this line be setting attributes instead?
// `data.options` is an array of `{value, label}` objects
$el.append(data.options);
}
if (hasOwnProperty(data, "value")) this.setValue(el, data.value);
if (hasDefinedProperty(data, "value")) {
this.setValue(el, data.value);
}
updateLabel(data.label, getLabelNode(el));

View File

@@ -1,7 +1,8 @@
import $ from "jquery";
import { InputBinding } from "./inputBinding";
import { $escape, hasOwnProperty, updateLabel } from "../../utils";
import { $escape, hasDefinedProperty, updateLabel } from "../../utils";
import { indirectEval } from "../../utils/eval";
import type { NotUndefined } from "../../utils/extraTypes";
type SelectHTMLElement = HTMLSelectElement & { nonempty: boolean };
@@ -13,8 +14,9 @@ type SelectInputReceiveMessageData = {
value?: string;
};
type SelectizeOptions = Selectize.IOptions<string, unknown>;
type SelectizeInfo = Selectize.IApi<string, unknown> & {
settings: Selectize.IOptions<string, unknown>;
settings: SelectizeOptions;
};
function getLabelNode(el: SelectHTMLElement): JQuery<HTMLElement> {
@@ -42,7 +44,7 @@ class SelectInputBinding extends InputBinding {
find(scope: HTMLElement): JQuery<HTMLElement> {
return $(scope).find("select");
}
getType(el: HTMLElement): string {
getType(el: HTMLElement): string | null {
const $el = $(el);
if (!$el.hasClass("symbol")) {
@@ -58,8 +60,10 @@ class SelectInputBinding extends InputBinding {
getId(el: SelectHTMLElement): string {
return InputBinding.prototype.getId.call(this, el) || el.name;
}
getValue(el: HTMLElement): string[] | number | string {
return $(el).val();
getValue(
el: HTMLElement
): NotUndefined<ReturnType<JQuery<HTMLElement>["val"]>> {
return $(el).val() as NotUndefined<ReturnType<JQuery<HTMLElement>["val"]>>;
}
setValue(el: SelectHTMLElement, value: string): void {
if (!isSelectize(el)) {
@@ -67,14 +71,12 @@ class SelectInputBinding extends InputBinding {
} else {
const selectize = this._selectize(el);
if (selectize) {
selectize.setValue(value);
}
selectize.setValue(value);
}
}
getState(el: SelectHTMLElement): {
label: JQuery<HTMLElement>;
value: string[] | number | string;
value: ReturnType<SelectInputBinding["getValue"]>;
options: Array<{ value: string; label: string }>;
} {
// Store options in an array of objects, each with with value and label
@@ -101,13 +103,13 @@ class SelectInputBinding extends InputBinding {
data: SelectInputReceiveMessageData
): void {
const $el = $(el);
let selectize;
// This will replace all the options
if (hasOwnProperty(data, "options")) {
selectize = this._selectize(el);
if (hasDefinedProperty(data, "options")) {
const selectize = this._selectize(el);
// Must destroy selectize before appending new options, otherwise
// selectize will restore the original select
if (selectize) selectize.destroy();
// Clear existing options and add each new one
$el.empty().append(data.options);
@@ -115,7 +117,7 @@ class SelectInputBinding extends InputBinding {
}
// re-initialize selectize
if (hasOwnProperty(data, "config")) {
if (hasDefinedProperty(data, "config")) {
$el
.parent()
.find('script[data-for="' + $escape(el.id) + '"]')
@@ -124,12 +126,22 @@ class SelectInputBinding extends InputBinding {
}
// use server-side processing for selectize
if (hasOwnProperty(data, "url")) {
selectize = this._selectize(el);
if (hasDefinedProperty(data, "url")) {
type CallbackFn = Parameters<
NonNullable<SelectizeInfo["settings"]["load"]>
>[1];
const selectize = this._selectize(el) as ReturnType<
SelectInputBinding["_selectize"]
> & {
settings: {
load: (query: string, callback: CallbackFn) => any;
};
};
selectize.clearOptions();
let loaded = false;
selectize.settings.load = function (query, callback) {
selectize.settings.load = function (query: string, callback: CallbackFn) {
const settings = selectize.settings;
$.ajax({
@@ -155,7 +167,7 @@ class SelectInputBinding extends InputBinding {
// the group's label and value. We use the current settings of
// the selectize object to decide the fieldnames of that obj.
const optgroupId = elem[settings.optgroupField || "optgroup"];
const optgroup = {};
const optgroup: { [key: string]: string } = {};
optgroup[settings.optgroupLabelField || "label"] = optgroupId;
optgroup[settings.optgroupValueField || "value"] = optgroupId;
@@ -163,8 +175,10 @@ class SelectInputBinding extends InputBinding {
});
callback(res);
if (!loaded) {
if (hasOwnProperty(data, "value")) {
selectize.setValue(data.value);
if (hasDefinedProperty(data, "value")) {
if (typeof data.value === "string") {
selectize.setValue(data.value);
}
} else if (settings.maxItems === 1) {
// first item selected by default only for single-select
selectize.setValue(res[0].value);
@@ -178,7 +192,7 @@ class SelectInputBinding extends InputBinding {
selectize.load(function (callback) {
selectize.settings.load.apply(selectize, ["", callback]);
});
} else if (hasOwnProperty(data, "value")) {
} else if (hasDefinedProperty(data, "value")) {
this.setValue(el, data.value);
}
@@ -208,21 +222,22 @@ class SelectInputBinding extends InputBinding {
this._selectize(el);
}
protected _selectize(el: SelectHTMLElement, update = false): SelectizeInfo {
if (!$.fn.selectize) return undefined;
if (!$.fn.selectize) throw "selectize jquery is not defined";
const $el = $(el);
const config = $el
.parent()
.find('script[data-for="' + $escape(el.id) + '"]');
if (config.length === 0) return undefined;
if (config.length === 0)
throw "No config found for selectize with id:" + $escape(el.id);
let options: {
let options: SelectizeOptions & {
labelField: "label";
valueField: "value";
searchField: ["label"];
onItemRemove?: (value: string) => void;
onDropdownClose?: () => void;
} & { [key: string]: unknown } = $.extend(
} = $.extend(
{
labelField: "label",
valueField: "value",
@@ -235,7 +250,7 @@ class SelectInputBinding extends InputBinding {
if (typeof config.data("nonempty") !== "undefined") {
el.nonempty = true;
options = $.extend(options, {
onItemRemove: function (value) {
onItemRemove: function (this: SelectizeInfo, value: string) {
if (this.getValue() === "")
$("select#" + $escape(el.id))
.empty()
@@ -249,9 +264,10 @@ class SelectInputBinding extends InputBinding {
},
onDropdownClose:
// $dropdown: any
function () {
if (this.getValue() === "")
this.setValue($("select#" + $escape(el.id)).val());
function (this: SelectizeInfo) {
if (this.getValue() === "") {
this.setValue($("select#" + $escape(el.id)).val() as string);
}
},
});
} else {
@@ -259,8 +275,9 @@ class SelectInputBinding extends InputBinding {
}
// options that should be eval()ed
if (config.data("eval") instanceof Array)
$.each(config.data("eval"), function (i, x) {
$.each(config.data("eval"), function (i, x: string) {
/*jshint evil: true*/
// @ts-expect-error; Need to type `options` keys to know exactly which values are accessed.
options[x] = indirectEval("(" + options[x] + ")");
});
let control = $el.selectize(options)[0].selectize as SelectizeInfo;

View File

@@ -1,10 +1,14 @@
import type {
IonRangeSliderEvent,
IonRangeSliderOptions,
} from "ion-rangeslider";
import $ from "jquery";
// import { NameValueHTMLElement } from ".";
import {
formatDateUTC,
updateLabel,
$escape,
hasOwnProperty,
hasDefinedProperty,
} from "../../utils";
import type { TextHTMLElement } from "./text";
@@ -28,6 +32,11 @@ type SliderReceiveMessageData = {
min?: number;
max?: number;
step?: number;
// eslint-disable-next-line @typescript-eslint/naming-convention
"data-type"?: string;
// eslint-disable-next-line @typescript-eslint/naming-convention
"time-format"?: string;
timezone?: string;
};
// MUST use window.strftime as the javascript dependency is dynamic
@@ -42,7 +51,7 @@ declare global {
}
// Necessary to get hidden sliders to send their updated values
function forceIonSliderUpdate(slider) {
function forceIonSliderUpdate(slider: any) {
if (slider.$cache && slider.$cache.input)
slider.$cache.input.trigger("change");
else console.log("Couldn't force ion slider to update");
@@ -73,7 +82,7 @@ function getTypePrettifyer(
// The default prettify function for ion.rangeSlider adds thousands
// separators after the decimal mark, so we have our own version here.
// (#1958)
prettify = function (num) {
prettify = function (this: IonRangeSliderOptions, num: number) {
// When executed, `this` will refer to the `IonRangeSlider.options`
// object.
return formatNumber(num, this.prettify_separator);
@@ -104,18 +113,18 @@ class SliderInputBinding extends TextInputBindingBase {
return $(scope).find("input.js-range-slider");
}
getType(el: HTMLElement): string | false {
getType(el: HTMLElement): string | null {
const dataType = $(el).data("data-type");
if (dataType === "date") return "shiny.date";
else if (dataType === "datetime") return "shiny.datetime";
else return false;
else return null;
}
getValue(
el: TextHTMLElement
): number | string | [number | string, number | string] {
const $el = $(el);
const result = $(el).data("ionRangeSlider").result;
const result = $(el).data("ionRangeSlider").result as IonRangeSliderEvent;
// Function for converting numeric value from slider to appropriate type.
let convert: (val: unknown) => number | string;
@@ -182,21 +191,28 @@ class SliderInputBinding extends TextInputBindingBase {
prettify?: Prettify;
} = {};
if (hasOwnProperty(data, "value")) {
if (hasDefinedProperty(data, "value")) {
if (numValues(el) === 2 && data.value instanceof Array) {
msg.from = data.value[0];
msg.to = data.value[1];
} else {
msg.from = data.value as number | string;
if (Array.isArray(data.value)) {
throw "Slider only contains a single value and cannot be updated with an array";
}
msg.from = data.value;
}
}
const sliderFeatures = ["min", "max", "step"];
const sliderFeatures: Array<"max" | "min" | "step"> = [
"min",
"max",
"step",
];
for (let i = 0; i < sliderFeatures.length; i++) {
const feats = sliderFeatures[i];
if (hasOwnProperty(data, feats)) {
if (hasDefinedProperty(data, feats)) {
msg[feats] = data[feats];
}
}
@@ -204,12 +220,16 @@ class SliderInputBinding extends TextInputBindingBase {
updateLabel(data.label, getLabelNode(el));
// (maybe) update data elements
const domElements = ["data-type", "time-format", "timezone"];
const domElements: Array<"data-type" | "time-format" | "timezone"> = [
"data-type",
"time-format",
"timezone",
];
for (let i = 0; i < domElements.length; i++) {
const elem = domElements[i];
if (hasOwnProperty(data, elem)) {
if (hasDefinedProperty(data, elem)) {
$el.data(elem, data[elem]);
}
}
@@ -284,12 +304,12 @@ function formatNumber(
$(document).on("click", ".slider-animate-button", function (evt: Event) {
evt.preventDefault();
const self = $(this);
const target = $("#" + $escape(self.attr("data-target-id")));
const target = $("#" + $escape(self.attr("data-target-id") as string));
const startLabel = "Play";
const stopLabel = "Pause";
const loop =
self.attr("data-loop") !== undefined &&
!/^\s*false\s*$/i.test(self.attr("data-loop"));
!/^\s*false\s*$/i.test(self.attr("data-loop") as string);
let animInterval = self.attr("data-interval") as number | string;
if (isNaN(animInterval as number)) animInterval = 1500;

View File

@@ -1,6 +1,6 @@
import $ from "jquery";
import { InputBinding } from "./inputBinding";
import { hasOwnProperty, isBS3 } from "../../utils";
import { hasDefinedProperty, isBS3 } from "../../utils";
type TabInputReceiveMessageData = { value?: string };
@@ -25,7 +25,7 @@ class BootstrapTabInputBinding extends InputBinding {
return null;
}
setValue(el: HTMLElement, value: string): void {
setValue(el: HTMLElement, value: string | undefined): void {
let success = false;
if (value) {
@@ -56,7 +56,7 @@ class BootstrapTabInputBinding extends InputBinding {
return { value: this.getValue(el) };
}
receiveMessage(el: HTMLElement, data: TabInputReceiveMessageData): void {
if (hasOwnProperty(data, "value")) this.setValue(el, data.value);
if (hasDefinedProperty(data, "value")) this.setValue(el, data.value);
$(el).trigger("change");
}
subscribe(el: HTMLElement, callback: (x: boolean) => void): void {

View File

@@ -1,5 +1,5 @@
import $ from "jquery";
import { $escape, updateLabel, hasOwnProperty } from "../../utils";
import { $escape, updateLabel, hasDefinedProperty } from "../../utils";
import { InputBinding } from "./inputBinding";
@@ -109,11 +109,12 @@ class TextInputBinding extends TextInputBindingBase {
};
}
receiveMessage(el: TextHTMLElement, data: TextReceiveMessageData): void {
if (hasOwnProperty(data, "value")) this.setValue(el, data.value);
if (hasDefinedProperty(data, "value")) this.setValue(el, data.value);
updateLabel(data.label, getLabelNode(el));
if (hasOwnProperty(data, "placeholder")) el.placeholder = data.placeholder;
if (hasDefinedProperty(data, "placeholder"))
el.placeholder = data.placeholder;
$(el).trigger("change");
}

View File

@@ -22,9 +22,12 @@ class DatatableOutputBinding extends OutputBinding {
options?: {
searching?: boolean;
search?: { caseInsensitive?: boolean };
// To be sent to data table;
// Will copy in R value to this location
escape?: string;
} | null;
escape?: string; // Incoming from R
action?: string;
escape?: string;
evalOptions?: string[];
callback?: string;
searchDelay?: number;
@@ -42,7 +45,7 @@ class DatatableOutputBinding extends OutputBinding {
header = "<thead><tr>" + header + "</tr></thead>";
let footer = "";
if (data.options === null || data.options.searching !== false) {
if (data.options?.searching ?? true) {
footer = $.map(colnames, function (x) {
// placeholder needs to be escaped (and HTML tags are stripped off)
return (
@@ -62,17 +65,16 @@ class DatatableOutputBinding extends OutputBinding {
$el.append(content);
// options that should be eval()ed
if (data.evalOptions)
if (data.evalOptions) {
$.each(data.evalOptions, function (i, x) {
/*jshint evil: true */
// @ts-expect-error; If `evalOptions` is defined, `data.options` should be defined
data.options[x] = indirectEval("(" + data.options[x] + ")");
});
}
// caseInsensitive searching? default true
const searchCI =
data.options === null ||
typeof data.options.search === "undefined" ||
data.options.search.caseInsensitive !== false;
const searchCI = data.options?.search?.caseInsensitive !== false;
const oTable = $(el)
.children("table")
.DataTable(
@@ -86,8 +88,14 @@ class DatatableOutputBinding extends OutputBinding {
ajax: {
url: data.action,
type: "POST",
data: function (d) {
data: function (d: NonNullable<typeof data.options>) {
d.search || (d.search = {});
d.search.caseInsensitive = searchCI;
// Copy from the R value (`data.escape`) to the escape option
// (`d.escape`) similar to `data.options.escape`;
// Note: this logic may be wrong, but the method is strongly
// deprecated in favor of DT package. So users should not
// naturally run this line of code
d.escape = data.escape;
},
},
@@ -110,7 +118,7 @@ class DatatableOutputBinding extends OutputBinding {
.first()
.unbind("keyup")
.keyup(
debounce(data.searchDelay, function () {
debounce(data.searchDelay, function (this: HTMLInputElement) {
oTable.search(this.value).draw();
})
);
@@ -124,7 +132,7 @@ class DatatableOutputBinding extends OutputBinding {
if (!x.bSearchable) searchInputs.eq(i as number).hide();
});
searchInputs.keyup(
debounce(data.searchDelay, function () {
debounce(data.searchDelay, function (this: HTMLInputElement) {
oTable.column(searchInputs.index(this)).search(this.value).draw();
})
);

View File

@@ -17,6 +17,7 @@ import {
import { isIE, IEVersion } from "../../utils/browser";
import type { CoordmapInit } from "../../imageutils/initCoordmap";
import type { ErrorsMessageValue } from "../../shiny/shinyapp";
import { ifUndefined } from "../../utils/object";
class ImageOutputBinding extends OutputBinding {
find(scope: HTMLElement): JQuery<HTMLElement> {
@@ -65,10 +66,6 @@ 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 ifUndefined(value, alternate) {
if (value === undefined) return alternate;
return value;
}
const opts = {
clickId: $el.data("click-id"),

View File

@@ -3,7 +3,7 @@ import { asArray } from "../../utils";
import type { ErrorsMessageValue } from "../../shiny/shinyapp";
class OutputBinding {
name: string;
name!: string;
// Returns a jQuery object or element array that contains the
// descendants of scope that match this binding

View File

@@ -22,7 +22,8 @@ class OutputBindingAdapter {
// onResize with a version that does a makeResizeFilter on the element.
if (binding.resize) {
this.onResize = makeResizeFilter(el, function (width, height) {
binding.resize(el, width, height);
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
binding.resize!(el, width, height);
});
}
}

View File

@@ -11,7 +11,7 @@ interface BindingObj<Binding> {
}
class BindingRegistry<Binding extends BindingBase> {
name: string;
name!: string;
bindings: Array<BindingObj<Binding>> = [];
bindingNames: { [key: string]: BindingObj<Binding> } = {};

View File

@@ -0,0 +1,56 @@
// Used to avoid isolated module warning
import type { JQueryEventHandlerBase } from "bootstrap";
import "jquery";
type EvtPrefix<T extends string> = `${T}.${string}`;
type EvtFn<T extends JQuery.Event> = ((evt: T) => void) | null | undefined;
declare global {
interface JQuery {
on(events: EvtPrefix<"change">, handler: EvtFn<JQuery.DragEvent>): this;
on(
events: EvtPrefix<"mousdown">,
handler: EvtFn<JQuery.MouseDownEvent>
): this;
on(
events: EvtPrefix<"dblclick">,
handler: EvtFn<JQuery.DoubleClickEvent>
): this;
on(
events: EvtPrefix<"dblclick2">,
// Note: This may not be the _right type_, but it is how it is handled internally
handler: EvtFn<JQuery.MouseDownEvent>
): this;
on(
events: EvtPrefix<"mousemove">,
handler: EvtFn<JQuery.MouseMoveEvent>
): this;
on(
events: EvtPrefix<"mouseout">,
handler: EvtFn<JQuery.MouseOutEvent>
): this;
on(
events: EvtPrefix<"mousedown">,
handler: EvtFn<JQuery.MouseDownEvent>
): this;
on(
events: EvtPrefix<"mousedown2">,
handler: EvtFn<JQuery.MouseDownEvent>
): this;
on(events: EvtPrefix<"mouseup">, handler: EvtFn<JQuery.MouseUpEvent>): this;
on(events: EvtPrefix<"resize">, handler: EvtFn<JQuery.ResizeEvent>): this;
on(
events: `shown.bs.${string}.sendImageSize`,
selector: string,
handler: (
this: HTMLElement,
e: JQueryEventHandlerBase<HTMLElement, any>
// e: JQuery.Event & {
// namespace: string;
// }
) => void
): this;
}
}

View File

@@ -13,7 +13,7 @@ type UploadEndValue = never;
// FileList object. Subclass/clone it and override the `on*` functions to
// make it do something useful.
class FileProcessor {
files: FileList;
files: File[];
fileIndex = -1;
// Currently need to use small chunk size because R-Websockets can't
// handle continuation frames
@@ -21,7 +21,7 @@ class FileProcessor {
completed = false;
constructor(files: FileList, exec$run = true) {
this.files = files;
this.files = Array.from(files);
// TODO: Register error/abort callbacks
if (exec$run) {
@@ -30,7 +30,7 @@ class FileProcessor {
}
// Begin callbacks. Subclassers/cloners may override any or all of these.
onBegin(files: FileList, cont: () => void): void {
onBegin(files: File[], cont: () => void): void {
files;
setTimeout(cont, 0);
}
@@ -99,10 +99,10 @@ class FileUploader extends FileProcessor {
id: string;
el: HTMLElement;
jobId: JobId;
uploadUrl: UploadUrl;
progressBytes: number;
totalBytes: number;
jobId!: JobId;
uploadUrl!: UploadUrl;
progressBytes!: number;
totalBytes!: number;
constructor(
shinyapp: ShinyApp,
@@ -142,7 +142,7 @@ class FileUploader extends FileProcessor {
): void {
this.shinyapp.makeRequest(method, args, onSuccess, onFailure, blobs);
}
onBegin(files: FileList, cont: () => void): void {
onBegin(files: File[], cont: () => void): void {
// Reset progress bar
this.$setError(null);
this.$setActive(true);
@@ -155,13 +155,12 @@ class FileUploader extends FileProcessor {
this.totalBytes += file.size;
});
const fileInfo = $.map(files, function (file: File, i) {
const fileInfo = $.map(files, function (file: File) {
return {
name: file.name,
size: file.size,
type: file.type,
};
i;
});
this.makeRequest(
@@ -185,6 +184,9 @@ class FileUploader extends FileProcessor {
type: "POST",
cache: false,
xhr: () => {
if (typeof $.ajaxSettings.xhr !== "function")
throw "jQuery's XHR is not a function";
const xhrVal = $.ajaxSettings.xhr();
if (xhrVal.upload) {
@@ -243,7 +245,7 @@ class FileUploader extends FileProcessor {
this.$bar().text("Upload complete");
// Reset the file input's value to "". This allows the same file to be
// uploaded again. https://stackoverflow.com/a/22521275
$(evt.el).val("");
$(evt.el as HTMLElement).val("");
},
(error) => {
this.onError(error);

View File

@@ -18,30 +18,30 @@ type BoundsCss = Bounds;
type BoundsData = Bounds;
type ImageState = {
brushing?: boolean;
dragging?: boolean;
resizing?: boolean;
brushing: boolean;
dragging: boolean;
resizing: boolean;
// Offset of last mouse down and up events (in CSS pixels)
down?: Offset;
up?: Offset;
down: Offset;
up: Offset;
// Which side(s) we're currently resizing
resizeSides?: {
resizeSides: {
left: boolean;
right: boolean;
top: boolean;
bottom: boolean;
};
boundsCss?: BoundsCss;
boundsData?: BoundsData;
boundsCss: BoundsCss;
boundsData: BoundsData;
// Panel object that the brush is in
panel?: Panel;
panel: Panel | null;
// The bounds at the start of a drag/resize (in CSS pixels)
changeStartBounds?: Bounds;
changeStartBounds: Bounds;
};
type BrushOpts = {
@@ -66,9 +66,6 @@ type Brush = {
// A callback when the wrapper div or img is resized.
onResize: () => void;
// TODO define this type as both a getter and a setter interfaces.
// boundsCss: (boxCss: BoundsCss) => void;
// boundsCss: () => BoundsCss;
boundsCss: {
(boxCss: BoundsCss): void;
(): BoundsCss;
@@ -82,11 +79,11 @@ type Brush = {
down: {
(): ImageState["down"];
(offsetCss): void;
(offsetCss: Offset): void;
};
up: {
(): ImageState["up"];
(offsetCss): void;
(offsetCss: Offset): void;
};
isBrushing: () => ImageState["brushing"];
@@ -117,9 +114,9 @@ function createBrush(
const resizeExpand = 10;
const el = $el[0];
let $div = null; // The div representing the brush
let $div: JQuery<HTMLElement> | null = null; // The div representing the brush
const state: ImageState = {};
const state = {} as ImageState;
// Aliases for conciseness
const cssToImg = coordmap.scaleCssToImg;
@@ -225,8 +222,8 @@ function createBrush(
const boundsDataVal = boundsData();
// Check to see if we have valid boundsData
for (const val in boundsDataVal) {
if (isnan(boundsDataVal[val])) return;
for (const val in Object.values(boundsDataVal)) {
if (isnan(val)) return;
}
boundsData(boundsDataVal);
@@ -234,7 +231,7 @@ function createBrush(
}
// Return true if the offset is inside min/max coords
function isInsideBrush(offsetCss) {
function isInsideBrush(offsetCss: Offset) {
const bounds = state.boundsCss;
return (
@@ -246,14 +243,14 @@ function createBrush(
}
// Return true if offset is inside a region to start a resize
function isInResizeArea(offsetCss) {
function isInResizeArea(offsetCss: Offset) {
const sides = whichResizeSides(offsetCss);
return sides.left || sides.right || sides.top || sides.bottom;
}
// Return an object representing which resize region(s) the cursor is in.
function whichResizeSides(offsetCss) {
function whichResizeSides(offsetCss: Offset) {
const b = state.boundsCss;
// Bounds with expansion
const e = {
@@ -299,13 +296,14 @@ function createBrush(
function boundsCss(boxCss: BoundsCss): void;
function boundsCss(boxCss?: BoundsCss) {
if (boxCss === undefined) {
return $.extend({}, state.boundsCss);
return { ...state.boundsCss };
}
let minCss = { x: boxCss.xmin, y: boxCss.ymin };
let maxCss = { x: boxCss.xmax, y: boxCss.ymax };
let minCss: Offset = { x: boxCss.xmin, y: boxCss.ymin };
let maxCss: Offset = { x: boxCss.xmax, y: boxCss.ymax };
const panel = state.panel;
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const panel = state.panel!;
const panelBoundsImg = panel.range;
if (opts.brushClip) {
@@ -332,8 +330,8 @@ function createBrush(
};
// Positions in data space
const minData = state.panel.scaleImgToData(cssToImg(minCss));
const maxData = state.panel.scaleImgToData(cssToImg(maxCss));
const minData = panel.scaleImgToData(cssToImg(minCss));
const maxData = panel.scaleImgToData(cssToImg(maxCss));
// For reversed scales, the min and max can be reversed, so use findBox
// to ensure correct order.
@@ -342,25 +340,28 @@ function createBrush(
// (#1634).
state.boundsData = mapValues(state.boundsData, (val) =>
roundSignif(val, 14)
) as BoundsData;
);
// We also need to attach the data bounds and panel as data attributes, so
// that if the image is re-sent, we can grab the data bounds to create a new
// brush. This should be fast because it doesn't actually modify the DOM.
$div.data("bounds-data", state.boundsData);
$div.data("panel", state.panel);
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
$div!.data("bounds-data", state.boundsData);
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
$div!.data("panel", state.panel);
return undefined;
}
// Get or set the bounds of the brush using coordinates in the data space.
function boundsData(): ImageState["boundsData"];
function boundsData(boxData: Parameters<Panel["scaleDataToImg"]>[0]): void;
function boundsData(boxData?: Parameters<Panel["scaleDataToImg"]>[0]) {
if (boxData === undefined) {
return $.extend({}, state.boundsData);
function boundsData(): BoundsData;
function boundsData(boxData: BoundsData): void;
function boundsData(boxData?: BoundsData | undefined): BoundsData | void {
if (typeof boxData === "undefined") {
return { ...state.boundsData };
}
let boxCss = imgToCss(state.panel.scaleDataToImg(boxData));
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
let boxCss = imgToCss(state.panel!.scaleDataToImg(boxData));
// Round to 13 significant digits to avoid spurious changes in FP values
// (#2197).
@@ -383,6 +384,7 @@ function createBrush(
// Add a new div representing the brush.
function addDiv() {
/* eslint-disable @typescript-eslint/naming-convention */
if ($div) $div.remove();
// Start hidden; we'll show it when movement occurs
@@ -415,7 +417,13 @@ function createBrush(
}
$el.append($div);
$div.offset({ x: 0, y: 0 }).width(0).outerHeight(0);
$div
.offset(
// @ts-expect-error; This is a jQuery Typing issue
{ x: 0, y: 0 }
)
.width(0)
.outerHeight(0);
}
// Update the brush div to reflect the current brush bounds.
@@ -425,7 +433,8 @@ function createBrush(
const imgOffsetCss = findOrigin($el.find("img"));
const b = state.boundsCss;
$div
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
$div!
.offset({
top: imgOffsetCss.y + b.ymin,
left: imgOffsetCss.x + b.xmin,
@@ -434,14 +443,18 @@ function createBrush(
.outerHeight(b.ymax - b.ymin + 1);
}
function down(offsetCss?: Offset) {
function down(): ImageState["down"];
function down(offsetCss: Offset): void;
function down(offsetCss?: Offset | undefined) {
if (offsetCss === undefined) return state.down;
state.down = offsetCss;
return undefined;
}
function up(offsetCss?: Offset) {
function up(): ImageState["up"];
function up(offsetCss: Offset): void;
function up(offsetCss?: Offset | undefined) {
if (offsetCss === undefined) return state.up;
state.up = offsetCss;
@@ -463,7 +476,8 @@ function createBrush(
function brushTo(offsetCss: Offset) {
boundsCss(findBox(state.down, offsetCss));
$div.show();
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
$div!.show();
updateDiv();
}
@@ -479,7 +493,7 @@ function createBrush(
function startDragging() {
state.dragging = true;
state.changeStartBounds = $.extend({}, state.boundsCss);
state.changeStartBounds = { ...state.boundsCss };
}
function dragTo(offsetCss: Offset) {
@@ -498,7 +512,8 @@ function createBrush(
// Clip to the plotting area
if (opts.brushClip) {
const panelBoundsImg = state.panel.range;
const panel = state.panel as Panel;
const panelBoundsImg = panel.range;
const newBoundsImg = cssToImg(newBoundsCss);
// Convert to format for shiftToRange
@@ -539,7 +554,7 @@ function createBrush(
function startResizing() {
state.resizing = true;
state.changeStartBounds = $.extend({}, state.boundsCss);
state.changeStartBounds = { ...state.boundsCss };
state.resizeSides = whichResizeSides(state.down);
}
@@ -554,7 +569,8 @@ function createBrush(
// Calculate what new positions would be, before clipping.
const bImg = cssToImg(state.changeStartBounds);
const panelBoundsImg = state.panel.range;
const panel = state.panel as Panel;
const panelBoundsImg = panel.range;
if (state.resizeSides.left) {
const xminImg = shiftToRange(

View File

@@ -14,8 +14,8 @@ function createClickInfo(
mousedown: (e: JQuery.MouseDownEvent) => void;
dblclickIE8: (e: JQuery.DoubleClickEvent) => void;
} {
let clickTimer: ReturnType<typeof setTimeout> = null;
let pendingE: JQuery.MouseDownEvent = null; // A pending mousedown2 event
let clickTimer: number | undefined = undefined;
let pendingE: JQuery.MouseDownEvent | null = null; // A pending mousedown2 event
// Create a new event of type eventType (like 'mousedown2'), and trigger
// it with the information stored in this.e.
@@ -49,7 +49,7 @@ function createClickInfo(
function scheduleMousedown2(e: JQuery.MouseDownEvent) {
pendingE = e;
clickTimer = setTimeout(function () {
clickTimer = window.setTimeout(function () {
triggerPendingMousedown2();
}, dblclickDelay);
}

View File

@@ -21,7 +21,7 @@ type CreateHandler = {
mouseout?: (e: JQuery.MouseOutEvent) => void;
mousedown?: (e: JQuery.MouseDownEvent) => void;
onResetImg: () => void;
onResize?: () => void;
onResize: ((e: JQuery.ResizeEvent) => void) | null;
};
type BrushInfo = {
@@ -153,7 +153,17 @@ function createBrushHandler(
// el instead of the brush div, because the brush div has
// 'pointer-events:none' so that it won't intercept pointer events.
// If `style` is null, don't add a cursor style.
function setCursorStyle(style) {
function setCursorStyle(
style:
| "crosshair"
| "ew-resize"
| "grabbable"
| "grabbing"
| "nesw-resize"
| "ns-resize"
| "nwse-resize"
| null
) {
$el.removeClass(
"crosshair grabbable grabbing ns-resize ew-resize nesw-resize nwse-resize"
);
@@ -177,7 +187,8 @@ function createBrushHandler(
return;
}
const panel = brush.getPanel();
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const panel = brush.getPanel()!;
// Add the panel (facet) variables, if present
$.extend(coords, panel.panel_vars);
@@ -212,7 +223,9 @@ function createBrushHandler(
.trigger("shiny-internal:brushed", coords);
}
let brushInfoSender;
let brushInfoSender:
| Debouncer<typeof sendBrushInfo>
| Throttler<typeof sendBrushInfo>;
if (opts.brushDelayType === "throttle") {
brushInfoSender = new Throttler(null, sendBrushInfo, opts.brushDelay);

View File

@@ -3,7 +3,7 @@ import { shinySetInputValue } from "../shiny/initedMethods";
import { mapValues } from "../utils";
import type { Offset } from "./findbox";
import type { Bounds } from "./createBrush";
import type { Panel } from "./initPanelScales";
import type { Panel, PanelInit } from "./initPanelScales";
import { initPanelScales } from "./initPanelScales";
// -----------------------------------------------------------------------
@@ -16,13 +16,16 @@ function findScalingRatio($el: JQuery<HTMLElement>) {
const boundingRect = $el[0].getBoundingClientRect();
return {
x: boundingRect.width / $el.outerWidth(),
y: boundingRect.height / $el.outerHeight(),
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
x: boundingRect.width / $el.outerWidth()!,
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
y: boundingRect.height / $el.outerHeight()!,
};
}
function findOrigin($el: JQuery<HTMLElement>): Offset {
const offset = $el.offset();
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const offset = $el.offset()!;
const scalingRatio = findScalingRatio($el);
// Find the size of the padding and border, for the top and left. This is
@@ -50,8 +53,10 @@ function findDims($el: JQuery<HTMLElement>) {
// If there's any padding/border, we need to find the ratio of the actual
// element content compared to the element plus padding and border.
const contentRatio = {
x: $el.width() / $el.outerWidth(),
y: $el.height() / $el.outerHeight(),
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
x: $el.width()! / $el.outerWidth()!,
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
y: $el.height()! / $el.outerHeight()!,
};
// Get the dimensions of the element _after_ any CSS transforms. This
@@ -85,11 +90,13 @@ type Coords = {
};
type CoordmapInit = {
panels: Panel[];
dims: {
height: number;
width: number;
};
panels: PanelInit[];
dims:
| {
height: number;
width: number;
}
| { height: null; width: null };
};
type Coordmap = {
panels: Panel[];
@@ -107,18 +114,19 @@ type Coordmap = {
(offsetImg: Bounds): Bounds;
(offsetImg: Offset): Offset;
(offsetImg: OffsetImg): OffsetCss;
(offsetImg: { [key: string]: number }): { [key: string]: number | null };
};
imgToCssScalingRatio: () => Offset;
cssToImgScalingRatio: () => Offset;
getPanelCss: (offsetCss: OffsetCss, expand?: number) => Panel;
getPanelCss: (offsetCss: OffsetCss, expand?: number) => Panel | null;
isInPanelCss: (offsetCss: OffsetCss, expand?: number) => boolean;
mouseCoordinateSender: (
inputId: string,
clip?: boolean,
nullOutside?: boolean
) => (e: JQuery.MouseDownEvent | JQuery.MouseMoveEvent) => void;
) => (e: JQuery.MouseDownEvent | JQuery.MouseMoveEvent | null) => void;
};
// This adds functions to the coordmap object to handle various
@@ -144,14 +152,13 @@ function initCoordmap(
$el: JQuery<HTMLElement>,
coordmap_: CoordmapInit
): Coordmap {
const coordmap = coordmap_ as Coordmap;
const $img = $el.find("img");
const img = $img[0];
// If we didn't get any panels, create a dummy one where the domain and range
// are simply the pixel dimensions.
// that we modify.
if (coordmap.panels.length === 0) {
if (coordmap_.panels.length === 0) {
const bounds = {
top: 0,
left: 0,
@@ -159,20 +166,23 @@ function initCoordmap(
bottom: img.clientHeight - 1,
};
coordmap.panels[0] = {
coordmap_.panels[0] = {
domain: bounds,
range: bounds,
mapping: {},
};
}
const coordmap = coordmap_ as Coordmap;
// If no dim height and width values are found, set them to the raw image height and width
// These values should be the same...
// This is only done to initialize an image output, whose height and width are unknown until the image is retrieved
coordmap.dims.height = coordmap.dims.height || img.naturalHeight;
coordmap.dims.width = coordmap.dims.width || img.naturalWidth;
// Add scaling functions to each panel
initPanelScales(coordmap.panels);
coordmap.panels = initPanelScales(coordmap_.panels);
// This returns the offset of the mouse in CSS pixels relative to the img,
// but not including the padding or border, if present.
@@ -195,7 +205,7 @@ function initCoordmap(
function scaleCssToImg(offsetCss: Bounds): Bounds;
function scaleCssToImg(offsetCss: Offset): Offset;
function scaleCssToImg(offsetCss: OffsetCss): OffsetImg;
function scaleCssToImg(offsetCss) {
function scaleCssToImg(offsetCss: OffsetCss) {
const pixelScaling = coordmap.imgToCssScalingRatio();
const result = mapValues(offsetCss, (value, key) => {
@@ -221,7 +231,7 @@ function initCoordmap(
function scaleImgToCss(offsetImg: Offset): Offset;
function scaleImgToCss(offsetImg: OffsetImg): OffsetCss;
function scaleImgToCss(offsetImg: { [key: string]: number }): {
[key: string]: number;
[key: string]: number | null;
} {
const pixelScaling = coordmap.imgToCssScalingRatio();
@@ -368,14 +378,15 @@ function initCoordmap(
shinySetInputValue(inputId, coords, { priority: "event" });
return;
}
const panel = coordmap.getPanelCss(coordsCss);
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const panel = coordmap.getPanelCss(coordsCss)!;
const coordsImg = coordmap.scaleCssToImg(coordsCss);
const coordsData = panel.scaleImgToData(coordsImg);
const coords: Coords = {
x: coordsData.x,
y: coordsData.y,
x: coordsData?.x,
y: coordsData?.y,
// eslint-disable-next-line @typescript-eslint/naming-convention
coords_css: coordsCss,
// eslint-disable-next-line @typescript-eslint/naming-convention

View File

@@ -2,6 +2,7 @@
import type { Offset } from "./findbox";
import { mapValues } from "../utils";
import type { Bounds } from "./createBrush";
// range.
function mapLinear(
@@ -35,10 +36,10 @@ function scaler1D(
domainMax: number,
rangeMin: number,
rangeMax: number,
logbase: number
logbase: number | null
) {
return {
scale: function (val: number, clip: boolean) {
scale: function (val: number, clip?: boolean) {
if (logbase) val = Math.log(val) / Math.log(logbase);
return mapLinear(val, domainMin, domainMax, rangeMin, rangeMax, clip);
},
@@ -52,7 +53,7 @@ function scaler1D(
};
}
type Panel = {
type PanelInit = {
domain: {
top: number;
bottom: number;
@@ -72,22 +73,22 @@ type Panel = {
mapping: { [key: string]: string };
// eslint-disable-next-line @typescript-eslint/naming-convention
panel_vars?: { [key: string]: number | string };
scaleDataToImg?: (
val: { [key: string]: number },
clip?: boolean
) => { [key: string]: number };
scaleImgToData?: {
};
type Panel = PanelInit & {
scaleDataToImg: {
(val: Bounds, clip?: boolean): Bounds;
};
scaleImgToData: {
(val: Offset, clip?: boolean): Offset;
(val: { [key: string]: number }, clip?: boolean): { [key: string]: number };
};
clipImg?: (offsetImg: { x: number; y: number }) => { x: number; y: number };
clipImg: (offsetImg: { x: number; y: number }) => { x: number; y: number };
};
// Modify panel, adding scale and inverse-scale functions that take objects
// like {x:1, y:3}, and also add clip function.
function addScaleFuns(panel: Panel) {
function addScaleFuns(panel_: PanelInit): Panel {
const panel = panel_ as Panel;
const d = panel.domain;
const r = panel.range;
const xlog = panel.log && panel.log.x ? panel.log.x : null;
@@ -98,7 +99,16 @@ function addScaleFuns(panel: Panel) {
// Given an object of form {x:1, y:2}, or {x:1, xmin:2:, ymax: 3}, convert
// from data coordinates to img. Whether a value is converted as x or y
// depends on the first character of the key.
panel.scaleDataToImg = function (val, clip) {
// (val: Offset, clip?: boolean): Offset;
// (val: Bounds, clip?: boolean): Bounds;
// (val: { [key: `${"x" | "y"}${string}`]: number }, clip?: boolean): { [key: `${"x" | "y"}${string}`]: number }
// (val: { [key: string]: number | null }, clip?: boolean): {
// [key: string]: number | null;
// };
function scaleDataToImg(
val: Bounds,
clip?: Parameters<typeof xscaler.scale>[1]
): Bounds {
return mapValues(val, (value, key) => {
const prefix = key.substring(0, 1);
@@ -107,12 +117,13 @@ function addScaleFuns(panel: Panel) {
} else if (prefix === "y") {
return yscaler.scale(value, clip);
}
// TODO-future; If we know the input is a valid input (starts with x/y), why do we still have this value?
return null;
});
};
}) as Bounds;
}
panel.scaleDataToImg = scaleDataToImg;
function scaleImgToData(val: Offset, clip?: boolean);
function scaleImgToData(val: { [key: string]: number }, clip?: boolean) {
function scaleImgToData(val: Offset, clip?: boolean): Offset {
return mapValues(val, (value, key) => {
const prefix = key.substring(0, 1);
@@ -121,8 +132,9 @@ function addScaleFuns(panel: Panel) {
} else if (prefix === "y") {
return yscaler.scaleInv(value, clip);
}
// TODO-future; If we know the input is a valid input (starts with x/y), why do we still have this value?
return null;
});
}) as Offset;
}
panel.scaleImgToData = scaleImgToData;
@@ -143,20 +155,18 @@ function addScaleFuns(panel: Panel) {
return newOffset;
};
return panel;
}
// Modifies the panel objects in a coordmap, adding scaleImgToData(),
// 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: Panel[]): void {
function initPanelScales(panels: PanelInit[]): Panel[] {
// Add the functions to each panel object.
for (let i = 0; i < panels.length; i++) {
const panel = panels[i];
addScaleFuns(panel);
}
return panels.map((panel) => addScaleFuns(panel));
}
export type { Panel };
export type { Panel, PanelInit };
export { initPanelScales };

View File

@@ -4,8 +4,6 @@ import { determineBrowserInfo } from "./browser";
import { windowShiny } from "../window/libraries";
import { setShiny } from "../shiny";
import { setBlobBuilder } from "../utils/blob";
import { windowBlobBuilder } from "../window/blobBuilder";
import { setUserAgent } from "../utils/userAgent";
import { windowUserAgent } from "../window/userAgent";
@@ -20,8 +18,6 @@ function init(): void {
disableFormSubmission();
setBlobBuilder(windowBlobBuilder());
initReactlog();
}

View File

@@ -1,6 +1,6 @@
import type { EventPriority } from "./inputPolicy";
import type { InputPolicy, InputPolicyOpts } from "./inputPolicy";
import { hasOwnProperty } from "../utils";
import { hasDefinedProperty } from "../utils";
class InputDeferDecorator implements InputPolicy {
pendingInput: {
@@ -18,7 +18,7 @@ class InputDeferDecorator implements InputPolicy {
}
submit(): void {
for (const nameType in this.pendingInput) {
if (hasOwnProperty(this.pendingInput, nameType)) {
if (hasDefinedProperty(this.pendingInput, nameType)) {
const { value, opts } = this.pendingInput[nameType];
this.target.setInput(nameType, value, opts);

View File

@@ -1,5 +1,5 @@
import type { InputPolicy, InputPolicyOpts } from "./inputPolicy";
import { hasOwnProperty } from "../utils";
import { hasDefinedProperty } from "../utils";
import { splitInputNameType } from "./splitInputNameType";
type LastSentValues = { [key: string]: { [key: string]: string } };
@@ -40,7 +40,7 @@ class InputNoResendDecorator implements InputPolicy {
} = {};
for (const inputName in values) {
if (hasOwnProperty(values, inputName)) {
if (hasDefinedProperty(values, inputName)) {
const { name, inputType } = splitInputNameType(inputName);
cacheValues[name] = {

View File

@@ -2,7 +2,7 @@ import type { AnyVoidFunction } from "../utils/extraTypes";
import type { InputPolicy } from "./inputPolicy";
interface InputRatePolicy<X extends AnyVoidFunction> {
target: InputPolicy;
target: InputPolicy | null;
func: X;
normalCall(...args: Parameters<X>): void;

View File

@@ -9,12 +9,19 @@ import type {
import { shinyAppBindOutput, shinyAppUnbindOutput } from "./initedMethods";
import { sendImageSizeFns } from "./sendImageSize";
const boundInputs = {};
const boundInputs: {
[key: string]: { binding: InputBinding; node: HTMLElement };
} = {};
type BindScope = HTMLElement | JQuery<HTMLElement>;
// todo make sure allowDeferred can NOT be supplied and still work
function valueChangeCallback(inputs, binding, el, allowDeferred) {
function valueChangeCallback(
inputs: InputValidateDecorator,
binding: InputBinding,
el: HTMLElement,
allowDeferred: boolean
) {
let id = binding.getId(el);
if (id) {
@@ -23,7 +30,11 @@ function valueChangeCallback(inputs, binding, el, allowDeferred) {
if (type) id = id + ":" + type;
const opts = {
const opts: {
priority: "deferred" | "immediate";
binding: typeof binding;
el: typeof el;
} = {
priority: allowDeferred ? "deferred" : "immediate",
binding: binding,
el: el,
@@ -54,7 +65,16 @@ function bindInputs(
const { inputs, inputsRate, inputBindings } = shinyCtx;
const bindings = inputBindings.getBindings();
const inputItems = {};
const inputItems: {
[key: string]: {
value: any;
opts: {
immediate: true;
binding: InputBinding;
el: HTMLElement;
};
};
} = {};
for (let i = 0; i < bindings.length; i++) {
const binding = bindings[i].binding;
@@ -84,7 +104,7 @@ function bindInputs(
const thisBinding = binding;
const thisEl = el;
return function (allowDeferred) {
return function (allowDeferred: boolean) {
valueChangeCallback(inputs, thisBinding, thisEl, allowDeferred);
};
})();

View File

@@ -14,7 +14,7 @@ import { debounce, Debouncer } from "../time";
import {
getComputedLinkColor,
getStyle,
hasOwnProperty,
hasDefinedProperty,
mapValues,
pixelRatio,
} from "../utils";
@@ -164,13 +164,17 @@ function initShiny(windowShiny: Shiny): void {
}
);
function getComputedBgColor(el) {
function getComputedBgColor(
el: HTMLElement | null
): string | null | undefined {
if (!el) {
// Top of document, can't recurse further
return null;
}
const bgColor = getStyle(el, "background-color");
if (!bgColor) return bgColor;
const m = bgColor.match(
/^rgba\(\s*([\d.]+)\s*,\s*([\d.]+)\s*,\s*([\d.]+)\s*,\s*([\d.]+)\s*\)$/
);
@@ -190,12 +194,12 @@ function initShiny(windowShiny: Shiny): void {
return bgColor;
}
function getComputedFont(el) {
function getComputedFont(el: HTMLElement) {
const fontFamily = getStyle(el, "font-family");
const fontSize = getStyle(el, "font-size");
return {
families: fontFamily.replace(/"/g, "").split(", "),
families: fontFamily?.replace(/"/g, "").split(", "),
size: fontSize,
};
}
@@ -252,7 +256,7 @@ function initShiny(windowShiny: Shiny): void {
$el.data("shiny-theme-observer", observer);
}
function doSendTheme(el) {
function doSendTheme(el: HTMLElement): void {
// Sending theme info on error isn't necessary (it'd add an unnecessary additional round-trip)
if (el.classList.contains("shiny-output-error")) {
return;
@@ -310,7 +314,7 @@ function initShiny(windowShiny: Shiny): void {
// Return true if the object or one of its ancestors in the DOM tree has
// style='display:none'; otherwise return false.
function isHidden(obj) {
function isHidden(obj: HTMLElement | null): boolean {
// null means we've hit the top of the tree. If width or height is
// non-zero, then we know that no ancestor has display:none.
if (obj === null || obj.offsetWidth !== 0 || obj.offsetHeight !== 0) {
@@ -318,10 +322,10 @@ function initShiny(windowShiny: Shiny): void {
} else if (getStyle(obj, "display") === "none") {
return true;
} else {
return isHidden(obj.parentNode);
return isHidden(obj.parentNode as HTMLElement | null);
}
}
let lastKnownVisibleOutputs = {};
let lastKnownVisibleOutputs: { [key: string]: boolean } = {};
// Set initial state of outputs to hidden, if needed
$(".shiny-bound-output").each(function () {
@@ -336,7 +340,7 @@ function initShiny(windowShiny: Shiny): void {
});
// Send update when hidden state changes
function doSendOutputHiddenState() {
const visibleOutputs = {};
const visibleOutputs: { [key: string]: boolean } = {};
$(".shiny-bound-output").each(function () {
const id = getIdFromEl(this);
@@ -364,7 +368,7 @@ function initShiny(windowShiny: Shiny): void {
});
// Anything left in lastKnownVisibleOutputs is orphaned
for (const name in lastKnownVisibleOutputs) {
if (hasOwnProperty(lastKnownVisibleOutputs, name))
if (hasDefinedProperty(lastKnownVisibleOutputs, name))
inputs.setInput(".clientdata_output_" + name + "_hidden", true);
}
// Update the visible outputs for next time
@@ -394,18 +398,22 @@ function initShiny(windowShiny: Shiny): void {
// the handler only when e's namespace matches. For example, if the
// namespace is "bs", it would match when e.namespace is "bs" or "bs.tab".
// If the namespace is "bs.tab", it would match for "bs.tab", but not "bs".
function filterEventsByNamespace(namespace, handler, ...args) {
namespace = namespace.split(".");
function filterEventsByNamespace(
namespace: string,
handler: (...handlerArgs: any[]) => void,
...args: any[]
) {
const namespaceArr = namespace.split(".");
return function (e) {
const eventNamespace = e.namespace.split(".");
return function (this: HTMLElement, e: JQuery.TriggeredEvent) {
const eventNamespace = e.namespace?.split(".") ?? [];
// If any of the namespace strings aren't present in this event, quit.
for (let i = 0; i < namespace.length; i++) {
if (eventNamespace.indexOf(namespace[i]) === -1) return;
for (let i = 0; i < namespaceArr.length; i++) {
if (eventNamespace.indexOf(namespaceArr[i]) === -1) return;
}
handler.apply(this, [namespace, handler, ...args]);
handler.apply(this, [namespaceArr, handler, ...args]);
};
}
@@ -553,6 +561,7 @@ function initDeferredIframes(): void {
const $el = $(el);
$el.removeClass("shiny-frame-deferred");
// @ts-expect-error; If it is undefined, set using the undefined value
$el.attr("src", $el.attr("data-deferred-src"));
$el.attr("data-deferred-src", null);
});

View File

@@ -5,10 +5,33 @@ import type { EventPriority } from "../inputPolicies";
import type { BindScope } from "./bind";
import type { Handler, ShinyApp } from "./shinyapp";
let fullShinyObj: Shiny = null;
let fullShinyObj: FullShinyDef;
// TODO-future; It would be nice to have a way to export this type value instead of / in addition to `Shiny`
type FullShinyDef = Required<
Pick<
Shiny,
| "bindAll"
| "forgetLastInputValue"
| "initializeInputs"
| "oncustommessage"
| "setInputValue"
| "shinyapp"
| "unbindAll"
| "user"
>
> &
Shiny;
function setShinyObj(shiny: Shiny): void {
fullShinyObj = shiny;
fullShinyObj = shiny as FullShinyDef;
}
function validateShinyHasBeenSet(): FullShinyDef {
if (typeof fullShinyObj === "undefined") {
throw "Shiny has not finish initialization yet. Please wait for the 'shiny-initialized' event.";
}
return fullShinyObj;
}
//// 2021/03: TypeScript Conversion note
@@ -21,40 +44,40 @@ function shinySetInputValue(
value: unknown,
opts?: { priority?: EventPriority }
): void {
fullShinyObj.setInputValue(name, value, opts);
validateShinyHasBeenSet().setInputValue(name, value, opts);
}
function shinyShinyApp(): ShinyApp {
return fullShinyObj.shinyapp;
return validateShinyHasBeenSet().shinyapp;
}
function setShinyUser(user: string): void {
fullShinyObj.user = user;
validateShinyHasBeenSet().user = user;
}
function shinyForgetLastInputValue(name: string): void {
fullShinyObj.forgetLastInputValue(name);
validateShinyHasBeenSet().forgetLastInputValue(name);
}
function shinyBindAll(scope: BindScope): void {
fullShinyObj.bindAll(scope);
validateShinyHasBeenSet().bindAll(scope);
}
function shinyUnbindAll(scope: BindScope, includeSelf = false): void {
fullShinyObj.unbindAll(scope, includeSelf);
validateShinyHasBeenSet().unbindAll(scope, includeSelf);
}
function shinyInitializeInputs(scope: BindScope): void {
fullShinyObj.initializeInputs(scope);
validateShinyHasBeenSet().initializeInputs(scope);
}
function shinyAppBindOutput(id: string, binding: OutputBindingAdapter): void {
fullShinyObj.shinyapp.bindOutput(id, binding);
shinyShinyApp().bindOutput(id, binding);
}
function shinyAppUnbindOutput(
id: string,
binding: OutputBindingAdapter
): boolean {
return fullShinyObj.shinyapp.unbindOutput(id, binding);
return shinyShinyApp().unbindOutput(id, binding);
}
function getShinyOnCustomMessage(): Handler | null {
return fullShinyObj.oncustommessage;
return validateShinyHasBeenSet().oncustommessage;
}
let fileInputBinding: FileInputBinding;
@@ -67,7 +90,7 @@ function setFileInputBinding(fileInputBinding_: FileInputBinding): void {
}
function getShinyCreateWebsocket(): (() => WebSocket) | void {
return fullShinyObj.createSocket;
return validateShinyHasBeenSet().createSocket;
}
export {

View File

@@ -2,6 +2,7 @@ import $ from "jquery";
import { $escape, randomId } from "../utils";
import { shinyUnbindAll } from "./initedMethods";
import type { HtmlDep } from "./render";
import { renderContent } from "./render";
// Milliseconds to fade in or out
@@ -15,6 +16,14 @@ function show({
id = null,
closeButton = true,
type = null,
}: {
html?: string;
action?: string;
deps?: HtmlDep[];
duration?: number | null;
id?: string | null;
closeButton?: boolean;
type?: string | null;
} = {}): ReturnType<typeof randomId> {
if (!id) id = randomId();
@@ -22,9 +31,10 @@ function show({
createPanel();
// Get existing DOM element for this ID, or create if needed.
let $notification = get(id);
let $notificationInit = get(id);
if ($notification.length === 0) $notification = create(id);
if ($notificationInit?.length === 0) $notificationInit = create(id);
const $notification = $notificationInit as JQuery<HTMLElement>;
// Render html and dependencies
const newHtml =
@@ -36,13 +46,16 @@ function show({
// Remove any existing classes of the form 'shiny-notification-xxxx'.
// The xxxx would be strings like 'warning'.
const classes = $notification
.attr("class")
.split(/\s+/)
.filter((cls) => cls.match(/^shiny-notification-/))
.join(" ");
const classes = $notification?.attr("class");
$notification.removeClass(classes);
if (classes) {
const classVal = classes
.split(/\s+/)
.filter((cls: string) => cls.match(/^shiny-notification-/))
.join(" ");
$notification.removeClass(classVal);
}
// Add class. 'default' means no additional CSS class.
if (type && type !== "default")
@@ -67,9 +80,8 @@ function show({
return id;
}
// TODO-barret - Should `id` be required? (some places do not supply one)
function remove(id?: string): void {
get(id).fadeOut(fadeDuration, function () {
function remove(id: string): void {
get(id)?.fadeOut(fadeDuration, function () {
shinyUnbindAll(this);
$(this).remove();
@@ -81,7 +93,7 @@ function remove(id?: string): void {
}
// Returns an individual notification DOM object (wrapped in jQuery).
function get(id?: string) {
function get(id: string | null | undefined) {
if (!id) return null;
return getPanel().find("#shiny-notification-" + $escape(id));
}
@@ -115,10 +127,10 @@ 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) {
function create(id: string): JQuery<HTMLElement> {
let $notification = get(id);
if ($notification.length === 0) {
if ($notification?.length === 0) {
$notification = $(
`<div id="shiny-notification-${id}" class="shiny-notification">` +
'<div class="shiny-notification-close">&times;</div>' +
@@ -135,11 +147,11 @@ function create(id) {
getPanel().append($notification);
}
return $notification;
return $notification as JQuery<HTMLElement>;
}
// Add a callback to remove a notification after a delay in ms.
function addRemovalCallback(id, delay) {
function addRemovalCallback(id: string, delay: number) {
// If there's an existing removalCallback, clear it before adding the new
// one.
clearRemovalCallback(id);
@@ -149,14 +161,14 @@ function addRemovalCallback(id, delay) {
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) {
function clearRemovalCallback(id: string) {
const $notification = get(id);
const oldRemovalCallback: ReturnType<typeof setTimeout> =
$notification.data("removalCallback");
$notification?.data("removalCallback");
if (oldRemovalCallback) {
clearTimeout(oldRemovalCallback);

View File

@@ -1,6 +1,12 @@
import $ from "jquery";
import { shinyShinyApp } from "./initedMethods";
import { showNotification } from "./notifications";
import type { ShinyApp } from "./shinyapp";
// We can use this method as `shinyShinyApp()` will throw if not initialized
function shinyAppConfig() {
return shinyShinyApp().config as NonNullable<ShinyApp["config"]>;
}
function initReactlog(): void {
$(document).on("keydown", function (e) {
@@ -8,9 +14,9 @@ function initReactlog(): void {
return;
const url =
"reactlog?w=" +
window.escape(shinyShinyApp().config.workerId) +
window.escape(shinyAppConfig().workerId) +
"&s=" +
window.escape(shinyShinyApp().config.sessionId);
window.escape(shinyAppConfig().sessionId);
window.open(url);
e.preventDefault();
@@ -39,9 +45,9 @@ function initReactlog(): void {
const url =
"reactlog/mark?w=" +
window.escape(shinyShinyApp().config.workerId) +
window.escape(shinyAppConfig().workerId) +
"&s=" +
window.escape(shinyShinyApp().config.sessionId);
window.escape(shinyAppConfig().sessionId);
// send notification
$.get(url, function (result: "marked" | void) {

View File

@@ -2,7 +2,7 @@ import $ from "jquery";
import { showNotification, removeNotification } from "./notifications";
function updateTime(reconnectTime): void {
function updateTime(reconnectTime: number): void {
const $time = $("#shiny-reconnect-time");
// If the time has been removed, exit and don't reschedule this function.

View File

@@ -1,5 +1,5 @@
import $ from "jquery";
import { asArray, hasOwnProperty } from "../utils";
import { asArray, hasDefinedProperty } from "../utils";
import { isIE } from "../utils/browser";
import type { BindScope } from "./bind";
import {
@@ -86,7 +86,8 @@ type MetaItem = {
type StylesheetItem = {
href: string;
[x: string]: string;
rel?: string;
type?: string;
};
type ScriptItem = {
@@ -154,14 +155,17 @@ function renderDependency(dep_: HtmlDep) {
// pass them through to `addStylesheetsAndRestyle` below.
const stylesheetLinks = dep.stylesheet.map((x) => {
// Add "rel" and "type" fields if not already present.
if (!hasOwnProperty(x, "rel")) x.rel = "stylesheet";
if (!hasOwnProperty(x, "type")) x.type = "text/css";
if (!hasDefinedProperty(x, "rel")) x.rel = "stylesheet";
if (!hasDefinedProperty(x, "type")) x.type = "text/css";
const link = document.createElement("link");
Object.entries(x).forEach(function ([attr, val]) {
Object.entries(x).forEach(function ([attr, val]: [
string,
string | undefined
]) {
if (attr === "href") {
val = encodeURI(val);
val = encodeURI(val as string);
}
// If val isn't truthy (e.g., null), consider it a boolean attribute
link.setAttribute(attr, val ? val : "");
@@ -178,7 +182,7 @@ function renderDependency(dep_: HtmlDep) {
return true;
}
if (hasOwnProperty(htmlDependencies, dep.name)) return false;
if (hasDefinedProperty(htmlDependencies, dep.name)) return false;
registerDependency(dep.name, dep.version);

View File

@@ -4,8 +4,8 @@ import { debounce, Debouncer } from "../time";
class SendImageSize {
// This function gets defined in initShiny() and 'hoisted' so it can be reused
// (to send CSS info) inside of Shiny.renderDependencies()
regular: () => void;
transitioned: () => void;
regular!: () => void;
transitioned!: () => void;
setImageSend(
inputBatchSender: InputBatchSender,

View File

@@ -1,11 +1,5 @@
import $ from "jquery";
import {
$escape,
hasOwnProperty,
makeBlob,
randomId,
scopeExprToFunc,
} from "../utils";
import { $escape, hasOwnProperty, randomId, scopeExprToFunc } from "../utils";
import {
getShinyCreateWebsocket,
getShinyOnCustomMessage,
@@ -49,6 +43,8 @@ type OnSuccessRequest = (value: ResponseValue) => void;
type OnErrorRequest = (err: string) => void;
type InputValues = { [key: string]: unknown };
type MessageValue = Parameters<WebSocket["send"]>[0];
//// 2021/03 - TypeScript conversion note:
// These four variables were moved from being internally defined to being defined globally within the file.
// Before the TypeScript conversion, the values where attached to `window.Shiny.addCustomMessageHandler()`.
@@ -58,16 +54,16 @@ type InputValues = { [key: string]: unknown };
// Records insertion order of handlers. Maps number to name. This is so
// we can dispatch messages to handlers in the order that handlers were
// added.
const messageHandlerOrder = [];
const messageHandlerOrder: string[] = [];
// Keep track of handlers by name. Maps name to handler function.
const messageHandlers = {};
const messageHandlers: { [key: string]: Handler } = {};
// Two categories of message handlers: those that are from Shiny, and those
// that are added by the user. The Shiny ones handle messages in
// msgObj.values, msgObj.errors, and so on. The user ones handle messages
// in msgObj.custom.foo and msgObj.custom.bar.
const customMessageHandlerOrder = [];
const customMessageHandlers = {};
const customMessageHandlerOrder: string[] = [];
const customMessageHandlers: { [key: string]: Handler } = {};
// Adds Shiny (internal) message handler
function addMessageHandler(type: string, handler: Handler) {
@@ -110,30 +106,30 @@ function addCustomMessageHandler(type: string, handler: Handler): void {
//// End message handler variables
class ShinyApp {
$socket: ShinyWebSocket = null;
$socket: ShinyWebSocket | null = null;
config: {
workerId: string;
sessionId: string;
} = null;
} | null = null;
// Cached input values
$inputValues: InputValues = {};
// Input values at initialization (and reconnect)
$initialInput: InputValues;
$initialInput: InputValues | null = null;
// Output bindings
$bindings: { [key: string]: OutputBindingAdapter } = {};
// Cached values/errors
$values = {};
$values: { [key: string]: any } = {};
$errors: { [key: string]: ErrorsMessageValue } = {};
// Conditional bindings (show/hide element based on expression)
$conditionals = {};
$pendingMessages: string[] = [];
$pendingMessages: MessageValue[] = [];
$activeRequests: {
[key: number]: { onSuccess: OnSuccessRequest; onError: OnErrorRequest };
} = {};
@@ -160,7 +156,7 @@ class ShinyApp {
return !!this.$socket;
}
private scheduledReconnect: ReturnType<typeof setTimeout> = null;
private scheduledReconnect: number | undefined = undefined;
reconnect(): void {
// This function can be invoked directly even if there's a scheduled
@@ -230,7 +226,7 @@ class ShinyApp {
while (this.$pendingMessages.length) {
const msg = this.$pendingMessages.shift();
socket.send(msg);
socket.send(msg as string);
}
};
socket.onmessage = (e) => {
@@ -280,7 +276,7 @@ class ShinyApp {
}
$scheduleReconnect(delay: Parameters<typeof setTimeout>[1]): void {
this.scheduledReconnect = setTimeout(() => {
this.scheduledReconnect = window.setTimeout(() => {
this.reconnect();
}, delay);
}
@@ -327,7 +323,9 @@ class ShinyApp {
// session$allowReconnect("force") was called. The "force" option should
// only be used for testing.
if (
(this.$allowReconnect === true && this.$socket.allowReconnect === true) ||
(this.$allowReconnect === true &&
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
this.$socket!.allowReconnect === true) ||
this.$allowReconnect === "force"
) {
const delay = this.reconnectDelay.next();
@@ -371,7 +369,7 @@ class ShinyApp {
args: unknown[],
onSuccess: OnSuccessRequest,
onError: OnErrorRequest,
blobs: Array<ArrayBuffer | Blob | string>
blobs: Array<ArrayBuffer | Blob | string> | undefined
): void {
let requestId = this.$nextRequestId;
@@ -385,7 +383,7 @@ class ShinyApp {
onError: onError,
};
let msg = JSON.stringify({
let msg: Blob | string = JSON.stringify({
method: method,
args: args,
tag: requestId,
@@ -397,7 +395,7 @@ class ShinyApp {
// the length followed by the blob. The json payload is UTF-8 encoded
// and used as the first blob.
const uint32ToBuf = function (val) {
const uint32ToBuf = function (val: number) {
const buffer = new ArrayBuffer(4);
const view = new DataView(buffer);
@@ -409,7 +407,7 @@ class ShinyApp {
payload.push(uint32ToBuf(0x01020202)); // signature
const jsonBuf = makeBlob([msg]);
const jsonBuf: Blob = new Blob([msg]);
payload.push(uint32ToBuf(jsonBuf.size));
payload.push(jsonBuf);
@@ -425,19 +423,21 @@ class ShinyApp {
payload.push(blob);
}
const blob = makeBlob(payload) as unknown;
const blob: Blob = new Blob(payload);
msg = blob as string;
msg = blob;
}
this.$sendMsg(msg);
}
$sendMsg(msg: string): void {
if (!this.$socket.readyState) {
$sendMsg(msg: MessageValue): void {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
if (!this.$socket!.readyState) {
this.$pendingMessages.push(msg);
} else {
this.$socket.send(msg);
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
this.$socket!.send(msg);
}
}
@@ -459,7 +459,7 @@ class ShinyApp {
}
}
receiveOutput<T>(name: string, value: T): T {
receiveOutput<T>(name: string, value: T): T | undefined {
const binding = this.$bindings[name];
const evt: ShinyEventValue = jQuery.Event("shiny:value");
@@ -510,7 +510,7 @@ class ShinyApp {
// necessary.
private _narrowScopeComponent<T>(
scopeComponent: { [key: string]: T },
nsPrefix: string | null
nsPrefix: string
) {
return Object.keys(scopeComponent)
.filter((k) => k.indexOf(nsPrefix) === 0)
@@ -525,7 +525,13 @@ class ShinyApp {
//
// Otherwise, returns a new object with keys in subComponents removed and
// renamed as necessary.
private _narrowScope(scope, nsPrefix: string) {
private _narrowScope(
scope: {
input: InputValues;
output: { [key: string]: any };
},
nsPrefix: string
) {
if (nsPrefix) {
return {
input: this._narrowScopeComponent(scope.input, nsPrefix),
@@ -541,7 +547,7 @@ class ShinyApp {
type: "shiny:conditional",
});
const inputs = {};
const inputs: InputValues = {};
// Input keys use "name:type" format; we don't want the user to
// have to know about the type suffix when referring to inputs.
@@ -562,13 +568,13 @@ class ShinyApp {
let condFunc = el.data("data-display-if-func");
if (!condFunc) {
const condExpr = el.attr("data-display-if");
const condExpr = el.attr("data-display-if") as string;
condFunc = scopeExprToFunc(condExpr);
el.data("data-display-if-func", condFunc);
}
const nsPrefix = el.attr("data-ns-prefix");
const nsPrefix = el.attr("data-ns-prefix") as string;
const nsScope = this._narrowScope(scope, nsPrefix);
const show = condFunc(nsScope);
const showing = el.css("display") !== "none";
@@ -610,6 +616,7 @@ class ShinyApp {
data = data.slice(len + 1);
msgObj.custom = {};
// @ts-expect-error; `custom` value is of unknown type. So setting within it is not allowed
msgObj.custom[type] = data;
}
@@ -655,7 +662,7 @@ class ShinyApp {
// * Use arrow functions to allow the Types to propagate.
// * However, `_sendMessagesToHandlers()` will adjust the `this` context to the same _`this`_.
addMessageHandler("values", (message: { [key: string]: unknown }) => {
addMessageHandler("values", (message: { [key: string]: any }) => {
for (const name in this.$bindings) {
if (hasOwnProperty(this.$bindings, name))
this.$bindings[name].showProgress(false);
@@ -670,7 +677,10 @@ class ShinyApp {
addMessageHandler(
"errors",
function (message: { [key: string]: ErrorsMessageValue }) {
function (
this: ShinyApp,
message: { [key: string]: ErrorsMessageValue }
) {
for (const key in message) {
if (hasOwnProperty(message, key))
this.receiveError(key, message[key]);
@@ -716,8 +726,12 @@ class ShinyApp {
addMessageHandler(
"progress",
(message: { type: string; message: { id: string } }) => {
function (
this: ShinyApp,
message: { type: string; message: { id: string } }
) {
if (message.type && message.message) {
// @ts-expect-error; Unknown values handled with followup if statement
const handler = this.progressHandlers[message.type];
if (handler) handler.call(this, message.message);
@@ -756,14 +770,15 @@ class ShinyApp {
addMessageHandler(
"response",
(message: { tag: string; value?: ResponseValue; error?: string }) => {
(message: { tag: number; value?: ResponseValue; error?: string }) => {
const requestId = message.tag;
const request = this.$activeRequests[requestId];
if (request) {
delete this.$activeRequests[requestId];
if ("value" in message) request.onSuccess(message.value);
else request.onError(message.error);
if ("value" in message)
request.onSuccess(message.value as UploadInitValue);
else request.onError(message.error as string);
}
}
);
@@ -827,12 +842,13 @@ class ShinyApp {
hasOwnProperty(message, "name") &&
hasOwnProperty(message, "status")
) {
const binding = this.$bindings[message.name];
const binding = this.$bindings[message.name as string];
// @ts-expect-error; TODO-barret; Could this be transformed into `.trigger(TYPE)`?
$(binding ? binding.el : null).trigger({
type: "shiny:" + message.status,
});
if (binding) {
$(binding.el).trigger("shiny:" + message.status);
} else {
$().trigger("shiny:" + message.status);
}
}
}
);
@@ -908,7 +924,7 @@ class ShinyApp {
}
function getTabContent($tabset: JQuery<HTMLElement>) {
const tabsetId = $tabset.attr("data-tabsetid");
const tabsetId = $tabset.attr("data-tabsetid") as string;
const $tabContent = $(
"div.tab-content[data-tabsetid='" + $escape(tabsetId) + "']"
);
@@ -933,13 +949,13 @@ class ShinyApp {
"'"
);
}
const $liTags = [];
const $divTags = [];
const $liTags: Array<JQuery<HTMLElement>> = [];
const $divTags: Array<JQuery<HTMLElement>> = [];
if ($aTag.attr("data-toggle") === "dropdown") {
// dropdown
const $dropdownTabset = $aTag.find("+ ul.dropdown-menu");
const dropdownId = $dropdownTabset.attr("data-tabsetid");
const dropdownId = $dropdownTabset.attr("data-tabsetid") as string;
const $dropdownLiTags = $dropdownTabset
.find("a[data-toggle='tab']")
@@ -983,12 +999,16 @@ class ShinyApp {
// Unless the item is being prepended/appended, the target tab
// must be provided
let target = null;
let $targetLiTag = null;
let $targetLiTag: JQuery<HTMLElement> | null = null;
if (message.target !== null) {
target = getTargetTabs($tabset, $tabContent, message.target);
$targetLiTag = target.$liTag;
const targetInfo = getTargetTabs(
$tabset,
$tabContent,
message.target as string
);
$targetLiTag = targetInfo.$liTag;
}
// If the item is to be placed inside a navbarMenu (dropdown),
@@ -1106,7 +1126,10 @@ class ShinyApp {
fix the dummy id given to the tab in the R side -- there, we always
set the tab id (counter dummy) to "id" and the tabset id to "tsid")
*/
function getTabIndex($tabset, tabsetId) {
function getTabIndex(
$tabset: JQuery<HTMLElement>,
tabsetId: string | undefined
) {
// The 0 is to ensure this works for empty tabsetPanels as well
const existingTabIds = [0];
// loop through all existing tabs, find the one with highest id
@@ -1117,9 +1140,11 @@ class ShinyApp {
if ($tab.length > 0) {
// remove leading url if it exists. (copy of bootstrap url stripper)
const href = $tab.attr("href").replace(/.*(?=#[^\s]+$)/, "");
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const href = $tab.attr("href")!.replace(/.*(?=#[^\s]+$)/, "");
// remove tab id to get the index
const index = href.replace("#tab-" + tabsetId + "-", "");
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const index = href!.replace("#tab-" + tabsetId + "-", "");
existingTabIds.push(Number(index));
}
@@ -1151,7 +1176,7 @@ class ShinyApp {
const dropdownId = $dropdownTabset.attr("data-tabsetid");
return { $tabset: $dropdownTabset, id: dropdownId };
} else if (message.target !== null) {
} else if (message.target !== null && $targetLiTag !== null) {
// if our item is to be placed next to a tab that is inside
// a navbarMenu, our item will also be inside
const $uncleTabset = $targetLiTag.parent("ul");
@@ -1168,7 +1193,7 @@ class ShinyApp {
);
// If the given tabset has no active tabs, select the first one
function ensureTabsetHasVisibleTab($tabset) {
function ensureTabsetHasVisibleTab($tabset: JQuery<HTMLElement>) {
const inputBinding = $tabset.data("shiny-input-binding");
// Use the getValue() method to avoid duplicating the CSS selector
@@ -1189,7 +1214,7 @@ class ShinyApp {
// Given a tabset ul jquery object, return the value of the first tab
// (in document order) that's visible and able to be selected.
function getFirstTab($ul) {
function getFirstTab($ul: JQuery<HTMLElement>) {
return (
$ul
.find("li:visible a[data-toggle='tab']")
@@ -1198,21 +1223,31 @@ class ShinyApp {
);
}
function tabApplyFunction(target, func, liTags = false) {
function tabApplyFunction(
target: ReturnType<typeof getTargetTabs>,
func: ($el: JQuery<HTMLElement>) => void,
liTags = false
) {
$.each(target, function (key, el) {
if (key === "$liTag") {
// $liTag is always just one jQuery element
func(el);
func(el as ReturnType<typeof getTargetTabs>["$liTag"]);
} else if (key === "$divTags") {
// $divTags is always an array (even if length = 1)
$.each(el, function (i, div) {
func(div);
});
$.each(
el as ReturnType<typeof getTargetTabs>["$divTags"],
function (i, div) {
func(div);
}
);
} else if (liTags && key === "$liTags") {
// $liTags is always an array (even if length = 0)
$.each(el, function (i, div) {
func(div);
});
$.each(
el as ReturnType<typeof getTargetTabs>["$liTags"],
function (i, div) {
func(div);
}
);
}
});
}
@@ -1228,7 +1263,7 @@ class ShinyApp {
ensureTabsetHasVisibleTab($tabset);
function removeEl($el) {
function removeEl($el: JQuery<HTMLElement>) {
shinyUnbindAll($el, true);
$el.remove();
}
@@ -1250,7 +1285,7 @@ class ShinyApp {
ensureTabsetHasVisibleTab($tabset);
function changeVisibility($el) {
function changeVisibility($el: JQuery<HTMLElement>) {
if (message.type === "show") $el.css("display", "");
else if (message.type === "hide") {
$el.hide();
@@ -1265,6 +1300,7 @@ class ShinyApp {
(message: { mode: unknown | "replace"; queryString: string }) => {
// leave the bookmarking code intact
if (message.mode === "replace") {
// @ts-expect-error; No title value being supplied
window.history.replaceState(null, null, message.queryString);
return;
}
@@ -1298,6 +1334,7 @@ class ShinyApp {
if (what === "query") relURL += message.queryString;
else relURL += oldQS + message.queryString; // leave old QS if it exists
// @ts-expect-error; No title value being supplied
window.history.pushState(null, null, relURL);
// for the case when message.queryString has both a query string
@@ -1327,7 +1364,7 @@ class ShinyApp {
progressHandlers = {
// Progress for a particular object
binding: function (message: { id: string }): void {
binding: function (this: ShinyApp, message: { id: string }): void {
const key = message.id;
const binding = this.$bindings[key];
@@ -1395,17 +1432,24 @@ class ShinyApp {
// Stack bars
const $progressBar = $progress.find(".progress");
$progressBar.css("top", depth * $progressBar.height() + "px");
if ($progressBar) {
$progressBar.css(
"top",
depth * ($progressBar.height() as number) + "px"
);
// Stack text objects
const $progressText = $progress.find(".progress-text");
// Stack text objects
const $progressText = $progress.find(".progress-text");
$progressText.css(
"top",
3 * $progressBar.height() + depth * $progressText.outerHeight() + "px"
);
$progressText.css(
"top",
3 * ($progressBar.height() as number) +
depth * ($progressText.outerHeight() as number) +
"px"
);
$progress.hide();
$progress.hide();
}
}
},
@@ -1492,9 +1536,11 @@ class ShinyApp {
}
url +=
"/session/" +
encodeURIComponent(this.config.sessionId) +
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
encodeURIComponent(this.config!.sessionId) +
"/dataobj/shinytest?w=" +
encodeURIComponent(this.config.workerId) +
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
encodeURIComponent(this.config!.workerId) +
"&nonce=" +
randomId();

View File

@@ -50,7 +50,7 @@ function renderHtml(
}
// Take an object where keys are names of singletons, and merges it into
// knownSingletons
function register(s) {
function register(s: typeof knownSingletons) {
$.extend(knownSingletons, s);
}
// Takes a string or array of strings and adds them to knownSingletons
@@ -84,7 +84,12 @@ function processHtml(val: string): {
const newSingletons: typeof knownSingletons = {};
let newVal: string;
const findNewPayload = function (match, p1, sig, payload) {
const findNewPayload = function (
match: string,
p1: string,
sig: string,
payload: string
) {
if (knownSingletons[sig] || newSingletons[sig]) return "";
newSingletons[sig] = true;
return payload;
@@ -97,8 +102,8 @@ function processHtml(val: string): {
val = newVal;
}
const heads = [];
const headAddPayload = function (match, payload) {
const heads: string[] = [];
const headAddPayload = function (match: string, payload: string) {
heads.push(payload);
return "";
};

View File

@@ -0,0 +1,9 @@
// Used to avoid isolated module warning
import "jquery";
declare global {
interface JQuery {
// used for testing only
internalTest: () => void;
}
}

View File

@@ -3,13 +3,17 @@ import type { InputRatePolicy } from "../inputPolicies/inputRatePolicy";
import type { AnyVoidFunction } from "../utils/extraTypes";
class Debouncer<X extends AnyVoidFunction> implements InputRatePolicy<X> {
target: InputPolicy;
target: InputPolicy | null;
func: X;
delayMs: number | undefined;
timerId: ReturnType<typeof setTimeout> | null;
args: Parameters<X> | null;
constructor(target: InputPolicy, func: X, delayMs: number | undefined) {
constructor(
target: InputPolicy | null,
func: X,
delayMs: number | undefined
) {
this.target = target;
this.func = func;
this.delayMs = delayMs;

View File

@@ -3,10 +3,10 @@ import type { InputRatePolicy } from "../inputPolicies/inputRatePolicy";
import type { AnyVoidFunction } from "../utils/extraTypes";
class Invoker<X extends AnyVoidFunction> implements InputRatePolicy<X> {
target: InputPolicy;
target: InputPolicy | null;
func: X;
constructor(target: InputPolicy, func: X) {
constructor(target: InputPolicy | null, func: X) {
this.target = target;
this.func = func;
}

View File

@@ -4,13 +4,17 @@ import type { InputRatePolicy } from "../inputPolicies/inputRatePolicy";
import type { AnyVoidFunction } from "../utils/extraTypes";
class Throttler<X extends AnyVoidFunction> implements InputRatePolicy<X> {
target: InputPolicy;
target: InputPolicy | null;
func: X;
delayMs: number | undefined;
timerId: ReturnType<typeof setTimeout> | null;
args: Parameters<X> | null;
constructor(target: InputPolicy, func: X, delayMs: number | undefined) {
constructor(
target: InputPolicy | null,
func: X,
delayMs: number | undefined
) {
this.target = target;
this.func = func;
this.delayMs = delayMs;

View File

@@ -5,7 +5,7 @@ import $ from "jquery";
import { getQueriesForElement } from "@testing-library/dom";
import userEvent from "@testing-library/user-event";
$.fn.internalTest = function countify() {
$.fn.internalTest = function countify(this: JQuery<HTMLElement>) {
this.html(`
<div>
<button>0</button>
@@ -13,10 +13,13 @@ $.fn.internalTest = function countify() {
`);
const $button = this.find("button");
$button._count = 0;
// JQuery<HTMLButtonElement>
let count = 0;
$button.click(() => {
$button._count++;
$button.text($button._count);
count++;
$button.text(count);
});
};

View File

@@ -1,44 +0,0 @@
import $ from "jquery";
type BlobBuilderConstructor = typeof window.MSBlobBuilder;
let blobBuilderClass: BlobBuilderConstructor;
function setBlobBuilder(blobBuilderClass_: BlobBuilderConstructor): void {
blobBuilderClass = blobBuilderClass_;
return;
}
function makeBlob(parts: BlobPart[]): Blob {
// Browser compatibility is a mess right now. The code as written works in
// a variety of modern browsers, but sadly gives a deprecation warning
// message on the console in current versions (as of this writing) of
// Chrome.
// Safari 6.0 (8536.25) on Mac OS X 10.8.1:
// Has Blob constructor but it doesn't work with ArrayBufferView args
// Google Chrome 21.0.1180.81 on Xubuntu 12.04:
// Has Blob constructor, accepts ArrayBufferView args, accepts ArrayBuffer
// but with a deprecation warning message
// Firefox 15.0 on Xubuntu 12.04:
// Has Blob constructor, accepts both ArrayBuffer and ArrayBufferView args
// Chromium 18.0.1025.168 (Developer Build 134367 Linux) on Xubuntu 12.04:
// No Blob constructor. Has WebKitBlobBuilder.
try {
return new Blob(parts);
} catch (e) {
const blobBuilder = new blobBuilderClass();
$.each(parts, function (i, part) {
blobBuilder.append(part);
});
return blobBuilder.getBlob();
}
}
export { makeBlob, setBlobBuilder };
export type { BlobBuilderConstructor };

View File

@@ -1,4 +1,19 @@
type AnyFunction = (...args: any[]) => any;
type AnyVoidFunction = (...args: any[]) => void;
type MapValuesUnion<T> = T[keyof T];
type MapWithResult<X, R> = {
[Property in keyof X]: R;
};
export type { AnyFunction, AnyVoidFunction };
/**
* Exclude undefined from T
*/
type NotUndefined<T> = T extends undefined ? never : T;
export type {
AnyFunction,
AnyVoidFunction,
MapValuesUnion,
MapWithResult,
NotUndefined,
};

View File

@@ -1,21 +1,22 @@
import $ from "jquery";
import { windowDevicePixelRatio } from "../window/pixelRatio";
import { makeBlob } from "./blob";
import { hasOwnProperty } from "./object";
import type { MapValuesUnion, MapWithResult } from "./extraTypes";
import { hasOwnProperty, hasDefinedProperty } from "./object";
function escapeHTML(str: string): string {
const escaped = {
/* eslint-disable @typescript-eslint/naming-convention */
const escaped: { [key: string]: string } = {
"&": "&amp;",
"<": "&lt;",
">": "&gt;",
// eslint-disable-next-line prettier/prettier
"\"": "&quot;",
'"': "&quot;",
"'": "&#039;",
"/": "&#x2F;",
};
return str.replace(/[&<>'"/]/g, function (m) {
return escaped[m];
return escaped[m] as string;
});
}
@@ -41,13 +42,14 @@ function strToBool(str: string): boolean | undefined {
function getStyle(el: Element, styleProp: string): string | undefined {
let x = undefined;
// @ts-expect-error; Old, IE 5+ attribute only - https://developer.mozilla.org/en-US/docs/Web/API/Element/currentStyle
if (el.currentStyle) x = el.currentStyle[styleProp];
else if (window.getComputedStyle) {
if ("currentStyle" in el) {
// @ts-expect-error; Old, IE 5+ attribute only - https://developer.mozilla.org/en-US/docs/Web/API/Element/currentStyle
x = el.currentStyle[styleProp];
} else {
// getComputedStyle can return null when we're inside a hidden iframe on
// Firefox; don't attempt to retrieve style props in this case.
// https://bugzilla.mozilla.org/show_bug.cgi?id=548397
const style = document.defaultView.getComputedStyle(el, null);
const style = document?.defaultView?.getComputedStyle(el, null);
if (style) x = style.getPropertyValue(styleProp);
}
@@ -85,6 +87,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(x: Date): string;
function formatDateUTC(date: Date | null): string | null {
if (date instanceof Date) {
return (
@@ -186,11 +189,11 @@ function asArray<T>(value: T | T[] | null | undefined): T[] {
// We need a stable sorting algorithm for ordering
// bindings by priority and insertion order.
function mergeSort<T>(
list: T[],
sortfunc: (a: T, b: T) => boolean | number
): T[] {
function merge(sortfunc, a, b) {
function mergeSort<Item>(
list: Item[],
sortfunc: (a: Item, b: Item) => boolean | number
): Item[] {
function merge(a: Item[], b: Item[]) {
let ia = 0;
let ib = 0;
const sorted = [];
@@ -214,8 +217,8 @@ function mergeSort<T>(
for (let i = 0; i < list.length; i += chunkSize * 2) {
const listA = list.slice(i, i + chunkSize);
const listB = list.slice(i + chunkSize, i + chunkSize * 2);
const merged = merge(sortfunc, listA, listB);
const args = [i, merged.length];
const merged = merge(listA, listB);
const args = [i, merged.length] as [number, number];
Array.prototype.push.apply(args, merged);
Array.prototype.splice.apply(list, args);
@@ -226,21 +229,24 @@ function mergeSort<T>(
}
// Escape jQuery selector metacharacters: !"#$%&'()*+,./:;<=>?@[\]^`{|}~
const $escape = function (val: string): string {
function $escape(val: undefined): undefined;
function $escape(val: string): string;
function $escape(val: string | undefined): string | undefined {
if (typeof val === "undefined") return val;
return val.replace(/([!"#$%&'()*+,./:;<=>?@[\\\]^`{|}~])/g, "\\$1");
};
}
// Maps a function over an object, preserving keys. Like the mapValues
// function from lodash.
function mapValues<V, R>(
obj: { [key: string]: V },
f: (value: V, key: string, obj: { [key: string]: V }) => R
): { [key: string]: R } {
const newObj: { [key: string]: R } = {};
function mapValues<T extends { [key: string]: any }, R>(
obj: T,
f: (value: MapValuesUnion<T>, key: string, object: typeof obj) => R
): MapWithResult<T, R> {
const newObj = {} as MapWithResult<T, R>;
for (const key in obj) {
if (hasOwnProperty(obj, key)) newObj[key] = f(obj[key], key, obj);
}
Object.keys(obj).forEach((key: keyof typeof obj) => {
newObj[key] = f(obj[key], key as string, obj);
});
return newObj;
}
@@ -297,26 +303,26 @@ const compareVersion = function (
op: "<" | "<=" | "==" | ">" | ">=",
b: string
): boolean {
function versionParts(ver) {
function versionParts(ver: string) {
return (ver + "")
.replace(/-/, ".")
.replace(/(\.0)+[^.]*$/, "")
.split(".");
}
function cmpVersion(a, b) {
a = versionParts(a);
b = versionParts(b);
const len = Math.min(a.length, b.length);
function cmpVersion(a: string, b: string) {
const aParts = versionParts(a);
const bParts = versionParts(b);
const len = Math.min(aParts.length, bParts.length);
let cmp;
for (let i = 0; i < len; i++) {
cmp = parseInt(a[i], 10) - parseInt(b[i], 10);
cmp = parseInt(aParts[i], 10) - parseInt(bParts[i], 10);
if (cmp !== 0) {
return cmp;
}
}
return a.length - b.length;
return aParts.length - bParts.length;
}
const diff = cmpVersion(a, b);
@@ -401,8 +407,8 @@ export {
compareVersion,
updateLabel,
getComputedLinkColor,
makeBlob,
hasOwnProperty,
hasDefinedProperty,
isBS3,
toLowerCase,
};

View File

@@ -1,5 +1,42 @@
function hasOwnProperty(x: { [key: string]: unknown }, y: string): boolean {
return Object.prototype.hasOwnProperty.call(x, y);
import type { NotUndefined } from "./extraTypes";
// Inspriation from https://fettblog.eu/typescript-hasownproperty/
// But mixing with "NonNullable key of Obj" instead of "key to unknown values"
function hasOwnProperty<Prop extends keyof X, X extends { [key: string]: any }>(
obj: X,
prop: Prop
): obj is X & { [key in NonNullable<Prop>]: X[key] } {
return Object.prototype.hasOwnProperty.call(obj, prop);
}
export { hasOwnProperty };
// Return true if the key exists on the object and the value is not undefined.
//
// This method is mainly used in input bindings' `receiveMessage` method.
// Since we know that the values are sent by Shiny via `{jsonlite}`,
// then we know that there are no `undefined` values. `null` is possible, but not `undefined`.
function hasDefinedProperty<
Prop extends keyof X,
X extends { [key: string]: any }
>(
obj: X,
prop: Prop
): obj is X & { [key in NonNullable<Prop>]: NotUndefined<X[key]> } {
return (
Object.prototype.hasOwnProperty.call(obj, prop) && obj[prop] !== undefined
);
}
// Return type for non-null value
function ifUndefined<X extends NotUndefined<any>, Y>(
value: X,
alternate: Y
): NotUndefined<X>;
// Return type for null value
function ifUndefined<X extends undefined, Y>(value: X, alternate: Y): Y;
// Logic
function ifUndefined<X, Y>(value: X, alternate: Y): X | Y {
if (value === undefined) return alternate;
return value;
}
export { hasOwnProperty, hasDefinedProperty, ifUndefined };

View File

@@ -1,16 +0,0 @@
import type { BlobBuilderConstructor } from "../utils/blob";
function windowBlobBuilder(): BlobBuilderConstructor {
const blob =
// @ts-expect-error; Using legacy definitions of Blob builders
window.BlobBuilder ||
// @ts-expect-error; Using legacy definitions of Blob builders
window.WebKitBlobBuilder ||
// @ts-expect-error; Using legacy definitions of Blob builders
window.MozBlobBuilder ||
window.MSBlobBuilder;
return blob;
}
export { windowBlobBuilder };

View File

@@ -1,12 +1,12 @@
import type { Shiny } from "../shiny";
function windowShiny(): Shiny | null {
// Use `unknown` type as we know what we are doing is _dangerous_
function windowShiny(): Shiny {
// Use `any` type as we know what we are doing is _dangerous_
// Immediately init shiny on the window
if (!(window as unknown)["Shiny"]) {
(window as unknown)["Shiny"] = {};
if (!(window as any)["Shiny"]) {
(window as any)["Shiny"] = {};
}
return (window as unknown)["Shiny"];
return (window as any)["Shiny"];
}
export { windowShiny };

View File

@@ -1,7 +1,7 @@
import { InputBinding } from "./inputBinding";
declare type ActionButtonReceiveMessageData = {
label?: string;
icon?: string;
icon?: string | [];
};
declare class ActionButtonInputBinding extends InputBinding {
find(scope: HTMLElement): JQuery<HTMLElement>;

View File

@@ -14,7 +14,7 @@ declare type CheckboxGroupValue = CheckboxGroupHTMLElement["value"];
declare class CheckboxGroupInputBinding extends InputBinding {
find(scope: HTMLElement): JQuery<HTMLElement>;
getValue(el: CheckboxGroupHTMLElement): CheckboxGroupValue[];
setValue(el: HTMLElement, value: string[] | string): void;
setValue(el: HTMLElement, value: string[] | string | null): void;
getState(el: CheckboxGroupHTMLElement): {
label: string;
value: ReturnType<CheckboxGroupInputBinding["getValue"]>;

View File

@@ -31,8 +31,8 @@ declare class DateInputBindingBase extends InputBinding {
parts: string[];
separators: string[];
}): string;
protected _setMin(el: HTMLElement, date: Date | null | undefined): void;
protected _setMax(el: HTMLElement, date: Date): void;
protected _setMin(el: HTMLElement, date: Date | null): void;
protected _setMax(el: HTMLElement, date: Date | null): void;
protected _newDate(date: Date | never | string): Date | null;
protected _floorDateTime(date: Date): Date;
protected _dateAsUTC(date: Date): Date;
@@ -40,7 +40,7 @@ declare class DateInputBindingBase extends InputBinding {
}
declare class DateInputBinding extends DateInputBindingBase {
getValue(el: HTMLElement): string;
setValue(el: HTMLElement, value: Date): void;
setValue(el: HTMLElement, value: Date | null): void;
getState(el: HTMLElement): {
label: string;
value: string | null;

View File

@@ -4,7 +4,7 @@ declare class InputBinding {
name: string;
find(scope: BindScope): JQuery<HTMLElement>;
getId(el: HTMLElement): string;
getType(el: HTMLElement): string | false;
getType(el: HTMLElement): string | null;
getValue(el: HTMLElement): any;
subscribe(el: HTMLElement, callback: (value: boolean) => void): void;
unsubscribe(el: HTMLElement): void;

View File

@@ -9,7 +9,7 @@ declare type NumberReceiveMessageData = {
};
declare class NumberInputBinding extends TextInputBindingBase {
find(scope: HTMLElement): JQuery<HTMLElement>;
getValue(el: NumberHTMLElement): string[] | number | string;
getValue(el: NumberHTMLElement): string[] | number | string | null | undefined;
setValue(el: NumberHTMLElement, value: number): void;
getType(el: NumberHTMLElement): string;
receiveMessage(el: NumberHTMLElement, data: NumberReceiveMessageData): void;

View File

@@ -5,17 +5,17 @@ declare type ValueLabelObject = {
label: string;
};
declare type RadioReceiveMessageData = {
value?: string;
value?: string | [];
options?: ValueLabelObject[];
label: string;
};
declare class RadioInputBinding extends InputBinding {
find(scope: HTMLElement): JQuery<HTMLElement>;
getValue(el: RadioHTMLElement): string[] | number | string | null;
setValue(el: RadioHTMLElement, value: string): void;
getValue(el: RadioHTMLElement): string[] | number | string | null | undefined;
setValue(el: RadioHTMLElement, value: string | []): void;
getState(el: RadioHTMLElement): {
label: string;
value: string[] | number | string;
value: ReturnType<RadioInputBinding["getValue"]>;
options: ValueLabelObject[];
};
receiveMessage(el: RadioHTMLElement, data: RadioReceiveMessageData): void;

View File

@@ -1,5 +1,6 @@
/// <reference types="selectize" />
import { InputBinding } from "./inputBinding";
import type { NotUndefined } from "../../utils/extraTypes";
declare type SelectHTMLElement = HTMLSelectElement & {
nonempty: boolean;
};
@@ -10,18 +11,19 @@ declare type SelectInputReceiveMessageData = {
url?: string;
value?: string;
};
declare type SelectizeOptions = Selectize.IOptions<string, unknown>;
declare type SelectizeInfo = Selectize.IApi<string, unknown> & {
settings: Selectize.IOptions<string, unknown>;
settings: SelectizeOptions;
};
declare class SelectInputBinding extends InputBinding {
find(scope: HTMLElement): JQuery<HTMLElement>;
getType(el: HTMLElement): string;
getType(el: HTMLElement): string | null;
getId(el: SelectHTMLElement): string;
getValue(el: HTMLElement): string[] | number | string;
getValue(el: HTMLElement): NotUndefined<ReturnType<JQuery<HTMLElement>["val"]>>;
setValue(el: SelectHTMLElement, value: string): void;
getState(el: SelectHTMLElement): {
label: JQuery<HTMLElement>;
value: string[] | number | string;
value: ReturnType<SelectInputBinding["getValue"]>;
options: Array<{
value: string;
label: string;

View File

@@ -7,6 +7,9 @@ declare type SliderReceiveMessageData = {
min?: number;
max?: number;
step?: number;
"data-type"?: string;
"time-format"?: string;
timezone?: string;
};
declare global {
interface Window {
@@ -18,7 +21,7 @@ declare global {
}
declare class SliderInputBinding extends TextInputBindingBase {
find(scope: HTMLElement): JQuery<HTMLElement>;
getType(el: HTMLElement): string | false;
getType(el: HTMLElement): string | null;
getValue(el: TextHTMLElement): number | string | [number | string, number | string];
setValue(el: HTMLElement, value: number | string | [number | string, number | string]): void;
subscribe(el: HTMLElement, callback: (x: boolean) => void): void;

View File

@@ -5,7 +5,7 @@ declare type TabInputReceiveMessageData = {
declare class BootstrapTabInputBinding extends InputBinding {
find(scope: HTMLElement): JQuery<HTMLElement>;
getValue(el: HTMLElement): string | null;
setValue(el: HTMLElement, value: string): void;
setValue(el: HTMLElement, value: string | undefined): void;
getState(el: HTMLElement): {
value: string | null;
};

View File

@@ -10,9 +10,10 @@ declare class DatatableOutputBinding extends OutputBinding {
search?: {
caseInsensitive?: boolean;
};
escape?: string;
} | null;
action?: string;
escape?: string;
action?: string;
evalOptions?: string[];
callback?: string;
searchDelay?: number;

View File

@@ -0,0 +1,20 @@
import type { JQueryEventHandlerBase } from "bootstrap";
import "jquery";
declare type EvtPrefix<T extends string> = `${T}.${string}`;
declare type EvtFn<T extends JQuery.Event> = ((evt: T) => void) | null | undefined;
declare global {
interface JQuery {
on(events: EvtPrefix<"change">, handler: EvtFn<JQuery.DragEvent>): this;
on(events: EvtPrefix<"mousdown">, handler: EvtFn<JQuery.MouseDownEvent>): this;
on(events: EvtPrefix<"dblclick">, handler: EvtFn<JQuery.DoubleClickEvent>): this;
on(events: EvtPrefix<"dblclick2">, handler: EvtFn<JQuery.MouseDownEvent>): this;
on(events: EvtPrefix<"mousemove">, handler: EvtFn<JQuery.MouseMoveEvent>): this;
on(events: EvtPrefix<"mouseout">, handler: EvtFn<JQuery.MouseOutEvent>): this;
on(events: EvtPrefix<"mousedown">, handler: EvtFn<JQuery.MouseDownEvent>): this;
on(events: EvtPrefix<"mousedown2">, handler: EvtFn<JQuery.MouseDownEvent>): this;
on(events: EvtPrefix<"mouseup">, handler: EvtFn<JQuery.MouseUpEvent>): this;
on(events: EvtPrefix<"resize">, handler: EvtFn<JQuery.ResizeEvent>): this;
on(events: `shown.bs.${string}.sendImageSize`, selector: string, handler: (this: HTMLElement, e: JQueryEventHandlerBase<HTMLElement, any>) => void): this;
}
}
export {};

View File

@@ -7,12 +7,12 @@ declare type UploadInitValue = {
};
declare type UploadEndValue = never;
declare class FileProcessor {
files: FileList;
files: File[];
fileIndex: number;
aborted: boolean;
completed: boolean;
constructor(files: FileList, exec$run?: boolean);
onBegin(files: FileList, cont: () => void): void;
onBegin(files: File[], cont: () => void): void;
onFile(file: File, cont: () => void): void;
onComplete(): void;
onAbort(): void;
@@ -35,7 +35,7 @@ declare class FileUploader extends FileProcessor {
type: string;
}>>, onSuccess: (value: UploadInitValue) => void, onFailure: Parameters<ShinyApp["makeRequest"]>[3], blobs: Parameters<ShinyApp["makeRequest"]>[4]): void;
makeRequest(method: "uploadEnd", args: [string, string], onSuccess: (value: unknown) => void, onFailure: Parameters<ShinyApp["makeRequest"]>[3], blobs: Parameters<ShinyApp["makeRequest"]>[4]): void;
onBegin(files: FileList, cont: () => void): void;
onBegin(files: File[], cont: () => void): void;
onFile(file: File, cont: () => void): void;
onComplete(): void;
onError(message: string): void;

View File

@@ -10,21 +10,21 @@ declare type Bounds = {
declare type BoundsCss = Bounds;
declare type BoundsData = Bounds;
declare type ImageState = {
brushing?: boolean;
dragging?: boolean;
resizing?: boolean;
down?: Offset;
up?: Offset;
resizeSides?: {
brushing: boolean;
dragging: boolean;
resizing: boolean;
down: Offset;
up: Offset;
resizeSides: {
left: boolean;
right: boolean;
top: boolean;
bottom: boolean;
};
boundsCss?: BoundsCss;
boundsData?: BoundsData;
panel?: Panel;
changeStartBounds?: Bounds;
boundsCss: BoundsCss;
boundsData: BoundsData;
panel: Panel | null;
changeStartBounds: Bounds;
};
declare type BrushOpts = {
brushDirection: "x" | "xy" | "y";
@@ -54,11 +54,11 @@ declare type Brush = {
getPanel: () => ImageState["panel"];
down: {
(): ImageState["down"];
(offsetCss: any): void;
(offsetCss: Offset): void;
};
up: {
(): ImageState["up"];
(offsetCss: any): void;
(offsetCss: Offset): void;
};
isBrushing: () => ImageState["brushing"];
startBrushing: () => void;

View File

@@ -8,7 +8,7 @@ declare type CreateHandler = {
mouseout?: (e: JQuery.MouseOutEvent) => void;
mousedown?: (e: JQuery.MouseDownEvent) => void;
onResetImg: () => void;
onResize?: () => void;
onResize: ((e: JQuery.ResizeEvent) => void) | null;
};
declare type BrushInfo = {
xmin: number;

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