Compare commits

...

46 Commits

Author SHA1 Message Date
Carson
5d563a00eb Port most everything to bslib 2025-04-25 10:19:04 -05:00
Carson
d4bf6aaab9 Restrict type='submit' behavior specifically to submitButton() 2025-04-25 10:06:12 -05:00
Carson
49c5d29003 First pass at adding textSubmitInput() 2025-04-25 10:06:12 -05:00
Garrick Aden-Buie
f79a22b987 feat: Fully reload ui/server when autoreload occurs (#4184)
* feat: Fully reload ui/server when autoreload occurs

* chore: remove stray empty line

* chore: clean up function names and add comments

* docs: Add news item

* feat: Use {watcher} for autoreload file watching (#4185)

* feat: Use {watcher}

* chore: shikokuchuo/watcher@dev

* chore: watcher is on CRAN now

* chore: Undo air format changes

* feat: Use `shiny.autoreload.interval` for watcher latency

* chore: Simply track last time auto-reload changed

* docs: rewrite options docs for clarity

* chore: code style

* docs: global.R changes are not applied

* feat(ui/server): Autoreload also reloads global and R support files

* chore: remove outdated comment

* chore: safer comparisons

* chore: Restore legacy autoreload watcher if {watcher} not installed

* rename: autoload_r_support_if_needed()

* chore: use `rlang::is_false()`

* chore: use_build_ignore("_dev")
2025-04-24 13:53:40 -04:00
Garrick Aden-Buie
83219e3551 fix: Improve jquery node detection (#4203) 2025-03-25 21:26:27 -04:00
Garrick Aden-Buie
f55c26af4a docs: Link to outputOptions() from render functions (#4196)
* docs(downloadHandler): Link to `outputOptions()`

* docs: include `outputOptions()` in other render functions
2025-03-03 11:11:11 -05:00
Garrick Aden-Buie
9fbb2c5829 docs: Rewrite news for #4183 (#4195) 2025-02-28 08:06:54 -05:00
Winston Chang
531f31b66f textInput(), textAreaInput(), numericInput(), passwordInput(): allow updating value on blur (#4183)
* textInput: Add updateOn parameter and allow setting debounce delay

* `devtools::document()` (GitHub Actions)

* `yarn build` (GitHub Actions)

* Update news

* Remove debounce parameter

* `devtools::document()` (GitHub Actions)

* `yarn build` (GitHub Actions)

* Add updateOn parameter to numericInput, passwordInput

* Add updateOn to textAreaInput()

* `devtools::document()` (GitHub Actions)

* feat: Ignore change events unless from server messages when `updateOn="blur"`

* refactor: `updateOn="change"` instead of `"input"`

* feat: Update inputs on Enter or Cmd/Ctrl+Enter (textarea)

* chore: Document `...` and ensure they are empty

* chore: Use `rlang::arg_match()`

* chore: code style (air format)

* fix: textAreaInput, not inputTextArea

* docs(NEWS): Minor edit

* chore: If element has focus, ignore change event

---------

Co-authored-by: wch <wch@users.noreply.github.com>
Co-authored-by: Garrick Aden-Buie <garrick@adenbuie.com>
2025-02-26 12:45:26 -05:00
Winston Chang
58e152154a Stop using Babel; compile JS to ES2020 (#4066)
Co-authored-by: wch <wch@users.noreply.github.com>
Co-authored-by: Garrick Aden-Buie <garrick@adenbuie.com>
Co-authored-by: gadenbuie <gadenbuie@users.noreply.github.com>
Co-authored-by: Barret Schloerke <barret@posit.co>
2025-02-03 12:37:19 -05:00
Garrick Aden-Buie
55b37fdeb3 fix(insertTab): Render inserted nav html only once (#4179)
* fix: Fix checking if `scope` is a jquery element

Fixes rstudio/bslib#1159

* refactor: Don't check binding validity if `scope` isn't an element

* fix(insertTab): Render inserted nav html only once

* chore: Don't need to delay binding

* fix: Bind all after inserting nav controls

Output bindings require outputs to be attached to the DOM.

* chore: align comment

* chore: Add news item
2025-01-27 17:15:27 -05:00
Garrick Aden-Buie
b8a5aef53a feat: De-duplicate client console messages (#4177)
* feat: De-duplicate client console messages

* refactor(ShinyErrorConsole): Add `appendConsoleMessage()` static method

* fix: Make `appendConsoleMessage()` an instance method

* rename: `createClientMessageElement()`

* docs: add news item
2025-01-27 16:57:10 -05:00
Carson Sievert
d764ea9b4e Busy indicator improvements (#4172)
* Make sure spinner is visible when htmlwidget errors are visible

* Give recalculating outputs a min-height large enough to show the spinner

* tableOutput() now gets the spinner treatment

* yarn run bundle_extras

* Forward visibility hidden for all recalculating widgets, not just those with a error message (otherwise spinner won't be visible after a req())

* Update news
2025-01-22 14:14:20 -06:00
olivroy
8ad779f949 Various test lints (#4171)
Co-authored-by: Garrick Aden-Buie <garrick@adenbuie.com>
2025-01-21 15:08:00 -05:00
olivroy
7642fc84b7 Replace crayon by cli + address some TODOs to add some color (#4170)
* Replace crayon by cli + address some TODOs to add some color

* docs: add news

---------

Co-authored-by: Garrick Aden-Buie <garrick@adenbuie.com>
2025-01-21 11:00:51 -05:00
Garrick Aden-Buie
0952f3e0a7 ci: update for 2025 (#4178)
* ci: update for 2025

* chore: RStudio --> Posit
---------

Co-authored-by: gadenbuie <gadenbuie@users.noreply.github.com>
2025-01-14 13:26:41 -05:00
Garrick Aden-Buie
13ca8dfc57 fix: Schedule .modal("hide") for transitioning modals (#4173)
* fix: Schedule modal removal for transitioning modals

* refactor: Simplify and both call and schedule modal hiding at same time

* Increment version number to 1.10.0.9000

* docs: Add NEWS entry
2024-12-31 16:27:25 -05:00
Garrick Aden-Buie
79f42f5846 v1.10.0 (#4166)
* Increment version number to 1.10.0

* `yarn build` (GitHub Actions)

* Sync package version (GitHub Actions)

* chore: urlchecker::url_update()

✔ Updated: <https://shiny.rstudio.com/reference/shiny/latest/insertUI.html> to <https://shiny.posit.co/r/reference/shiny/latest/insertui.html> in NEWS.md
✔ Updated: <https://shiny.rstudio.com/reference/shiny/latest/modalDialog.html> to <https://shiny.posit.co/r/reference/shiny/latest/modaldialog.html> in NEWS.md
✔ Updated: <https://shiny.rstudio.com/reference/shiny/latest/Progress.html> to <https://shiny.posit.co/r/reference/shiny/latest/progress.html> in NEWS.md
✔ Updated: <https://shiny.rstudio.com/reference/shiny/latest/renderTable.html> to <https://shiny.posit.co/r/reference/shiny/latest/rendertable.html> in NEWS.md
✔ Updated: <https://shiny.rstudio.com/reference/shiny/latest/showNotification.html> to <https://shiny.posit.co/r/reference/shiny/latest/shownotification.html> in NEWS.md
✔ Updated: <https://shiny.rstudio.com/reference/shiny/latest/withProgress.html> to <https://shiny.posit.co/r/reference/shiny/latest/withprogress.html> in NEWS.md

* chore: revdepcheck

* chore: polish NEWS

* chore: update CRAN comments

* Polish NEWS

---------

Co-authored-by: gadenbuie <gadenbuie@users.noreply.github.com>
Co-authored-by: Carson <cpsievert1@gmail.com>
2024-12-16 09:30:53 -06:00
Joe Cheng
9a35b01e23 Fix observeEvent stack trace stripping (#4163)
* Fix observeEvent stack trace stripping

* Add unit test

* Add deep stack version of unit test
2024-12-09 20:50:53 -08:00
Joe Cheng
5bf0701939 Plot outputs incorrectly sized inside scaled outputs (#4139)
* Fix #4135: Plot outputs incorrectly sized inside scaled outputs

CSS zoom property affects el.getBoundingClientRect() but not
el.offsetWidth/Height. When reporting sizes of outputs from
client to server, we need to back out the CSS zoom because
those sizes are used as CSS width/height, which will be
affected by zoom.

(Note that something similar happens with CSS transforms but
we don't have a good way to deal with them)

* Squelch TS error

* `yarn build` (GitHub Actions)

* Add TODO

Co-authored-by: Garrick Aden-Buie <garrick@adenbuie.com>

* Rebuild JS

---------

Co-authored-by: jcheng5 <jcheng5@users.noreply.github.com>
Co-authored-by: Garrick Aden-Buie <garrick@adenbuie.com>
2024-12-08 01:25:32 -08:00
Garrick Aden-Buie
e5083f4938 feat: Avoid throwing errors for shared input/output IDs (#4101)
* refactor: Factor out message display from error handler

* feat: Add custom event for sending a client message

* feat: Report binding validity problem via event instead of throwing error

* feat: Don't need to hide shared input/output message

Now that it's not an error, it's safe to report

* refactor: Move `inDevMode()` logic into error console

* refactor: Rename `.error` --> `.event`

* feat: wrap client error message

It's otherwise hard to tell that the error is scrollable
Plus the scrolling is over the whole message rather than the part that overflows

* feat: always send client console messages to browser console as well

* chore: throw if `shiny:client-message` receives an event that isn't CustomEvent

* feat: Handle status in `showShinyClientMessage()`

* Renamed `showMessageInClientConsole()` to `showShinyClientMessage()` to improve clarity

* Added `status` argument to `showShinyClientMessage()` to allow for different message types

* refactor: Don't throw errors for duplicate IDs

Brings dev mode in line with current "prod" behavior,
where errors aren't thrown for duplciates. In both cases
we still get console or client messages.

* refactor: Clean up `status` inside `checkValidity()`

* refactor: Have `checkValidity()` handle emitting the client console event
2024-12-06 16:00:19 -05:00
Garrick Aden-Buie
ce6a562a3c chore: routine (#4161) 2024-12-06 15:16:29 -05:00
Dan Gealow
b6bcfc8683 Work around a selectize bug (re-fixes #3966) (#4142)
* Work around a selectize bug (re-fixes #3966)

---------

Co-authored-by: Garrick Aden-Buie <garrick@adenbuie.com>
2024-12-06 15:03:59 -05:00
Dean Attali
d37beeece7 ExtendedTask: add example to docs (#4087)
Co-authored-by: Garrick Aden-Buie <garrick@adenbuie.com>
2024-12-06 14:24:01 -05:00
Joe Cheng
79ee25620f Limit deep stack growth (#4156)
* Limit deep stack growth

* Improvements to deep stack trace culling

- Keep around the first deep stack trace; it may have useful
  information. (We may want to change this in the future to
  keep the first two stack traces, or even make it an option)
- Print out an indicator that we've elided stack traces, and
  how many

* Add comments

* Add NEWS item

* Add test for unlimited deep stacks

* Code review feedback

* Code review feedback

Co-authored-by: Carson Sievert <cpsievert1@gmail.com>

* Use head() over indexing

Co-authored-by: Carson Sievert <cpsievert1@gmail.com>

* Improve unit test robustness

* Remove vector indices from snapshot

* Make stack trace stripping work across deep stacks

* Pass tests

* Try passing tests again

* Rename keep_head to retain_first_n

* Remove misleading variable assignment

* Add more comments, refine dropTrivialTestFrames

* Don't call stripStackTraces if we're not stripping

* Use deep stack deduplication instead of elision

This hopefully will avoid any potential ..stacktraceon../off..
scoring issues, and will be more useful for users. The downside
is that it's still possible to have uselessly large deep stack
traces, but at least that will only happen now if you have
manually written gigantic async/promise chains by hand or maybe
did some clever metaprogramming. The coro case should be fine.

* Add coro-based unit test

* Use rlang::hash, it's much faster

* typo

Co-authored-by: Carson Sievert <cpsievert1@gmail.com>

* Remove unnecessary logic

* Simplify/robustify reactlog version checking test

* Warn only once on call stack digest cache miss

* Super conservatively wrap appendCallStackWithDupe in try/catch

* Use more specific attribute name

Co-authored-by: Carson Sievert <cpsievert1@gmail.com>

* Remove excessively cautious try/catch

---------

Co-authored-by: Carson Sievert <cpsievert1@gmail.com>
2024-12-06 10:17:05 -08:00
Joe Cheng
82c678a1eb Update NEWS.md for stack trace domain explosion 2024-12-03 13:37:58 -08:00
Joe Cheng
458924569a Stack trace domain explosion (#4155)
* Avoid way too many promise domains being activated

Using `captureStackTraces` in wrapForContext is a bad idea, it
piles on a new domain every time a handler is bound.

* Use captureStackTraces, it means the same thing

* Update promises version requirement

* Add test for stack trace growth

* Simplify stack trace snapshot tests

The `category` column isn't a good candidate for snapshot
testing, as its contents vary depending on how the package
was loaded/installed. During devtools::test() or similar,
shiny package code shows up as 'user'. But during CI, it
doesn't show up as anything.
2024-12-03 13:23:50 -08:00
Garrick Aden-Buie
501b012b2b chore: Remove zipfs from recommended vscode extensions (#4151) 2024-11-07 10:52:10 -05:00
Garrick Aden-Buie
ee1aac847a docs(runExample): display.mode follows DESCRIPTION (#4152)
Fixes #4077

The argument documentation wasn't updated to reflect that `runExample()` can run more than just Shiny's examples
2024-11-07 10:51:27 -05:00
bart1
7785a76a67 Closing bracket missing (#4150)
* type

* typo
2024-11-01 09:05:20 -05:00
Adam Foryś
79af1d6c92 Fix url bookmarking with possibility to modify excludes (#3762)
* Fix url bookmarking with possibility to modify excludes

* Update NEWS.md
2024-10-28 09:36:42 -05:00
Yihui Xie
a145add5d4 Use double-tilde for strikethrough in Markdown tests (#4144)
* Use double-tilde for strikethrough in Markdown tests

The current dev version of commonmark has disabled single tilde   for strikethrough: https://github.com/r-lib/commonmark/pull/33 Using double-tilde will make it consistent with Pandoc's Markdown (where single-tilde is for subscripts).

* Skip tests if commonmark is outdated

* Revert "Skip tests if commonmark is outdated"

This reverts commit 97bee20863.

---------

Co-authored-by: Carson Sievert <cpsievert1@gmail.com>
2024-10-15 11:21:53 -05:00
Dan Gealow
abf71389be Fix sporadic dates (#3664) (#3665)
* Remove dateInput and dateRangeInput handlers for keyup and input events

This prevents spurious updates while typing, but still sends when enter is pressed, focus is lost, or the GUI is clicked (due to the remaining `changeDate` and `change` handlers).

* chore: small edits to comments and NEWS item

---------

Co-authored-by: Garrick Aden-Buie <garrick@adenbuie.com>
2024-09-30 17:26:43 -04:00
Garrick Aden-Buie
2e2114f99d fix(busy): Show pulse if only UI are recalculating (#4137)
* fix(busy): Show pulse if only UI are recalculating

Because UI elements don't get spinners
2024-09-30 13:55:47 -04:00
Garrick Aden-Buie
09d415502f docs(NEWS): Fix name of sliderInput() function (#4136) 2024-09-27 11:14:40 -04:00
Garrick Aden-Buie
c489fef4ff fix(input_slider): Make sure last used handle is always above others (#4131) 2024-09-27 10:09:43 -04:00
Garrick Aden-Buie
9d12b0fca7 fix(conditionalPanel): Coerce condition result to boolean (#4127)
Co-authored-by: Kamil Zyla <kamil@appsilon.com>
2024-09-27 09:38:15 -04:00
Garrick Aden-Buie
cc9b9d4e6a feat(pulse): Tweak pulse animation and height (#4122) 2024-09-27 09:31:44 -04:00
Joe Cheng
34f9e4484d Merge pull request #4134 from rstudio/test/fix-reactivity-timing
fix: Timing of throttle/debounce reactivity test
2024-09-25 15:51:29 -07:00
Garrick Aden-Buie
03a3f8f886 test(reactivity): Consolidate identical tests into for loop 2024-09-25 10:09:17 -04:00
Garrick Aden-Buie
b900db0c74 test: Update the other test 2024-09-24 22:05:52 -04:00
Garrick Aden-Buie
5fb3ebc2d9 ci: run tests again 2024-09-24 22:00:19 -04:00
Garrick Aden-Buie
fbc6b2df57 chore: take out debugging code 2024-09-24 21:32:27 -04:00
Garrick Aden-Buie
6208225354 fix: Tweak updates to avoid overlapping events 2024-09-24 21:22:40 -04:00
Garrick Aden-Buie
e22b693418 chore: show value in debug too 2024-09-24 21:01:29 -04:00
Garrick Aden-Buie
c7ca49c634 debug: Add debugging messages for debounce/throttle test 2024-09-24 20:44:31 -04:00
Carson Sievert
d84aa94762 Start new version (#4113)
* Start new version

* `yarn build` (GitHub Actions)

* Sync package version (GitHub Actions)

---------

Co-authored-by: cpsievert <cpsievert@users.noreply.github.com>
2024-08-01 10:28:25 -05:00
109 changed files with 8097 additions and 25260 deletions

View File

@@ -26,7 +26,6 @@
^\.vscode$
^\.madgerc$
^\.prettierrc\.yml$
^babel\.config\.json$
^jest\.config\.js$
^package\.json$
^tsconfig\.json$
@@ -37,3 +36,4 @@
^\.browserslistrc$
^\.eslintrc\.yml$
^\.yarnrc\.yml$
^_dev$

View File

@@ -1,6 +1,5 @@
{
"recommendations": [
"arcanis.vscode-zipfs",
"dbaeumer.vscode-eslint",
"esbenp.prettier-vscode"
]

View File

@@ -1,7 +1,7 @@
Package: shiny
Type: Package
Title: Web Application Framework for R
Version: 1.9.1
Version: 1.10.0.9001
Authors@R: c(
person("Winston", "Chang", role = c("aut", "cre"), email = "winston@posit.co", comment = c(ORCID = "0000-0002-1576-2126")),
person("Joe", "Cheng", role = "aut", email = "joe@posit.co"),
@@ -83,9 +83,9 @@ Imports:
R6 (>= 2.0),
sourcetools,
later (>= 1.0.0),
promises (>= 1.1.0),
promises (>= 1.3.2),
tools,
crayon,
cli,
rlang (>= 0.4.10),
fastmap (>= 1.1.1),
withr,
@@ -95,10 +95,11 @@ Imports:
cachem (>= 1.1.0),
lifecycle (>= 0.2.0)
Suggests:
coro (>= 1.1.0),
datasets,
DT,
Cairo (>= 1.5-5),
testthat (>= 3.0.0),
testthat (>= 3.2.1),
knitr (>= 1.6),
markdown,
rmarkdown,
@@ -110,7 +111,8 @@ Suggests:
dygraphs,
ragg,
showtext,
sass
sass,
watcher
URL: https://shiny.posit.co/,
https://github.com/rstudio/shiny
BugReports: https://github.com/rstudio/shiny/issues
@@ -209,7 +211,6 @@ Collate:
RoxygenNote: 7.3.2
Encoding: UTF-8
Roxygen: list(markdown = TRUE)
RdMacros: lifecycle
Config/testthat/edition: 3
Config/Needs/check:
shinytest2

75
NEWS.md
View File

@@ -1,3 +1,68 @@
# shiny (development version)
## New features
* `textInput()`, `textAreaInput()`, `numericInput()` and `passwordInput()` all gain an `updateOn` option. `updateOn = "change"` is the default and previous behavior, where the input value updates immediately whenever the value changes. With `updateOn = "blur"`, the input value will update only when the text input loses focus or when the user presses Enter (or Cmd/Ctrl + Enter for `textAreaInput()`). (#4183)
* `textAreaInput()` gains a `autoresize` option, which automatically resizes the text area to fit its content.
* The `callback` argument of Shiny.js' `InputBinding.subscribe()` method gains support for a value of `"event"`. This makes it possible for an input binding to use event priority when updating the value (i.e., send immediately and always resend, even if the value hasn't changed).
## Changes
* Shiny no longer suspends input changes when _any_ `<input type="submit">` or `<button type="submit">` is on the page. Instead, it now only suspends when a `submitButton()` is present. If you have reason for creating a submit button from custom HTML, add a CSS class of `shiny-submit-button` to the button.
## Improvements
* When auto-reload is enabled, Shiny now reloads the entire app when support files, like Shiny modules, additional script files, or web assets, change. To enable auto-reload, call `devmode(TRUE)` to enable Shiny's developer mode, or set `options(shiny.autoreload = TRUE)` to specifically enable auto-reload. You can choose which files are watched for changes with the `shiny.autoreload.pattern` option. (#4184)
* When busy indicators are enabled (i.e., `useBusyIndicators()`), Shiny now:
* Shows a spinner on recalculating htmlwidgets that have previously rendered an error (including `req()` and `validate()`). (#4172)
* Shows a spinner on `tableOutput()`. (#4172)
* Places a minimum height on recalculating outputs so that the spinner is always visible. (#4172)
* Shiny now uses `{cli}` instead of `{crayon}` for rich log messages. (@olivroy #4170)
* Shiny's Typescript assets are now compiled to ES2021 instead of ES5. (#4066)
## Bug fixes
* Fixed a bug with modals where calling `removeModal()` too quickly after `showModal()` would fail to remove the modal if the remove modal message was received while the modal was in the process of being revealed. (#4173)
* The Shiny Client Console (enabled with `shiny::devmode()`) no longer displays duplicate warning or error message. (#4177)
* Updated the JavaScript used when inserting a tab to avoid rendering dynamic UI elements twice when adding the new tab via `insertTab()` or `bslib::nav_insert()`. (#4179)
# shiny 1.10.0
## New features and improvements
* When busy indicators are enabled (i.e., `useBusyIndicators()` is in the UI), Shiny now:
* Shows the pulse indicator when dynamic UI elements are recalculating and no other spinners are visible in the app. (#4137)
* Makes the pulse indicator slightly smaller by default and improves its appearance to better blend with any background. (#4122)
* Improve collection of deep stack traces (stack traces that are tracked across steps in an async promise chain) with `{coro}` async generators such as `{elmer}` chat streams. Previously, Shiny treated each iteration of an async generator as a distinct deep stack, leading to pathologically long stack traces; now, Shiny only keeps/prints unique deep stack trace, discarding duplicates. (#4156)
* Added an example to the `ExtendedTask` documentation. (@daattali #4087)
## Bug fixes
* Fixed a bug in `conditionalPanel()` that would cause the panel to repeatedly show/hide itself when the provided condition was not boolean. (@kamilzyla, #4127)
* Fixed a bug with `sliderInput()` when used as a range slider that made it impossible to change the slider value when both handles were at the maximum value. (#4131)
* `dateInput()` and `dateRangeInput()` no longer send immediate updates to the server when the user is typing a date input. Instead, it waits until the user presses Enter or clicks out of the field to send the update, avoiding spurious and incorrect date values. Note that an update is still sent immediately when the field is cleared. (#3664)
* Fixed a bug in `onBookmark()` hook that caused elements to not be excluded from URL bookmarking. (#3762)
* Fixed a bug with stack trace capturing that caused reactives with very long async promise chains (hundreds/thousands of steps) to become extremely slow. Chains this long are unlikely to be written by hand, but `{coro}` async generators and `{elmer}` async streaming were easily creating problematically long chains. (#4155)
* Duplicate input and output IDs -- e.g. using `"debug"` for two inputs or two outputs -- or shared IDs -- e.g. using `"debug"` as the `inputId` for an input and an output -- now result in a console warning message, but not an error. When `devmode()` is enabled, an informative message is shown in the Shiny Client Console. We recommend all Shiny devs enable `devmode()` when developing Shiny apps locally. (#4101)
* Updating the choices of a `selectizeInput()` via `updateSelectizeInput()` with `server = TRUE` no longer retains the selected choice as a deselected option if the current value is not part of the new choices. (@dvg-p4 #4142)
* Fixed a bug where stack traces from `observeEvent()` were being stripped of stack frames too aggressively. (#4163)
# shiny 1.9.1
## Bug fixes
@@ -1020,7 +1085,7 @@ Shiny can now display notifications on the client browser by using the `showNoti
<img src="http://shiny.rstudio.com/images/notification.png" alt="notification" width="50%"/>
</p>
[Here](https://shiny.rstudio.com/articles/notifications.html)'s our article about it, and the [reference documentation](https://shiny.rstudio.com/reference/shiny/latest/showNotification.html).
[Here](https://shiny.rstudio.com/articles/notifications.html)'s our article about it, and the [reference documentation](https://shiny.posit.co/r/reference/shiny/latest/shownotification.html).
## Progress indicators
@@ -1029,7 +1094,7 @@ If your Shiny app contains computations that take a long time to complete, a pro
**_Important note_:**
> If you were already using progress bars and had customized them with your own CSS, you can add the `style = "old"` argument to your `withProgress()` call (or `Progress$new()`). This will result in the same appearance as before. You can also call `shinyOptions(progress.style = "old")` in your app's server function to make all progress indicators use the old styling.
To see new progress bars in action, see [this app](https://gallery.shinyapps.io/085-progress/) in the gallery. You can also learn more about this in [our article](https://shiny.rstudio.com/articles/progress.html) and in the reference documentation (either for the easier [`withProgress` functional API](https://shiny.rstudio.com/reference/shiny/latest/withProgress.html) or the more complicated, but more powerful, [`Progress` object-oriented API](https://shiny.rstudio.com/reference/shiny/latest/Progress.html).
To see new progress bars in action, see [this app](https://gallery.shinyapps.io/085-progress/) in the gallery. You can also learn more about this in [our article](https://shiny.rstudio.com/articles/progress.html) and in the reference documentation (either for the easier [`withProgress` functional API](https://shiny.posit.co/r/reference/shiny/latest/withprogress.html) or the more complicated, but more powerful, [`Progress` object-oriented API](https://shiny.posit.co/r/reference/shiny/latest/progress.html).
## Reconnection
@@ -1043,7 +1108,7 @@ Shiny has now built-in support for displaying modal dialogs like the one below (
<img src="http://shiny.rstudio.com/images/modal-dialog.png" alt="modal-dialog" width="50%"/>
</p>
To learn more about this, read [our article](https://shiny.rstudio.com/articles/modal-dialogs.html) and the [reference documentation](https://shiny.rstudio.com/reference/shiny/latest/modalDialog.html).
To learn more about this, read [our article](https://shiny.rstudio.com/articles/modal-dialogs.html) and the [reference documentation](https://shiny.posit.co/r/reference/shiny/latest/modaldialog.html).
## `insertUI` and `removeUI`
@@ -1051,7 +1116,7 @@ Sometimes in a Shiny app, arbitrary HTML UI may need to be created on-the-fly in
See [this simple demo app](https://gallery.shinyapps.io/111-insert-ui/) of how one could use `insertUI` and `removeUI` to insert and remove text elements using a queue. Also see [this other app](https://gallery.shinyapps.io/insertUI/) that demonstrates how to insert and remove a few common Shiny input objects. Finally, [this app](https://gallery.shinyapps.io/insertUI-modules/) shows how to dynamically insert modules using `insertUI`.
For more, read [our article](https://shiny.rstudio.com/articles/dynamic-ui.html) about dynamic UI generation and the reference documentation about [`insertUI`](https://shiny.rstudio.com/reference/shiny/latest/insertUI.html) and [`removeUI`](https://shiny.rstudio.com/reference/shiny/latest/insertUI.html).
For more, read [our article](https://shiny.rstudio.com/articles/dynamic-ui.html) about dynamic UI generation and the reference documentation about [`insertUI`](https://shiny.posit.co/r/reference/shiny/latest/insertui.html) and [`removeUI`](https://shiny.posit.co/r/reference/shiny/latest/insertui.html).
## Documentation for connecting to an external database
@@ -1085,7 +1150,7 @@ There are many more minor features, small improvements, and bug fixes than we ca
<img src="http://shiny.rstudio.com/images/render-table.png" alt="render-table" width="75%"/>
</p>
For more, read our [short article](https://shiny.rstudio.com/articles/render-table.html) about this update, experiment with all the new features in this [demo app](https://gallery.shinyapps.io/109-render-table/), or check out the [reference documentation](https://shiny.rstudio.com/reference/shiny/latest/renderTable.html).
For more, read our [short article](https://shiny.rstudio.com/articles/render-table.html) about this update, experiment with all the new features in this [demo app](https://gallery.shinyapps.io/109-render-table/), or check out the [reference documentation](https://shiny.posit.co/r/reference/shiny/latest/rendertable.html).
## Full changelog

View File

@@ -177,7 +177,7 @@ utils::globalVariables(".GenericCallEnv", add = TRUE)
#' cache by putting this at the top of your app.R, server.R, or global.R:
#'
#' ```
#' shinyOptions(cache = cachem::cache_disk(file.path(dirname(tempdir()), "myapp-cache"))
#' shinyOptions(cache = cachem::cache_disk(file.path(dirname(tempdir()), "myapp-cache")))
#' ```
#'
#' This will create a subdirectory in your system temp directory named

View File

@@ -99,13 +99,13 @@ saveShinySaveState <- function(state) {
# Encode the state to a URL. This does not save to disk.
encodeShinySaveState <- function(state) {
exclude <- c(state$exclude, "._bookmark_")
inputVals <- serializeReactiveValues(state$input, exclude, stateDir = NULL)
# Allow user-supplied onSave function to do things like add state$values.
if (!is.null(state$onSave))
isolate(state$onSave(state))
exclude <- c(state$exclude, "._bookmark_")
inputVals <- serializeReactiveValues(state$input, exclude, stateDir = NULL)
inputVals <- vapply(inputVals,
function(x) toJSON(x, strict_atomic = FALSE),
character(1),

View File

@@ -1113,7 +1113,7 @@ plotOutput <- function(outputId, width = "100%", height="400px",
#' @rdname renderTable
#' @export
tableOutput <- function(outputId) {
div(id = outputId, class="shiny-html-output")
div(id = outputId, class="shiny-html-output shiny-table-output")
}
dataTableDependency <- list(

View File

@@ -130,6 +130,44 @@ captureStackTraces <- function(expr) {
#' @include globals.R
.globals$deepStack <- NULL
getCallStackDigest <- function(callStack, warn = FALSE) {
dg <- attr(callStack, "shiny.stack.digest", exact = TRUE)
if (!is.null(dg)) {
return(dg)
}
if (isTRUE(warn)) {
rlang::warn(
"Call stack doesn't have a cached digest; expensively computing one now",
.frequency = "once",
.frequency_id = "deepstack-uncached-digest-warning"
)
}
rlang::hash(getCallNames(callStack))
}
saveCallStackDigest <- function(callStack) {
attr(callStack, "shiny.stack.digest") <- getCallStackDigest(callStack, warn = FALSE)
callStack
}
# Appends a call stack to a list of call stacks, but only if it's not already
# in the list. The list is deduplicated by digest; ideally the digests on the
# list are cached before calling this function (you will get a warning if not).
appendCallStackWithDedupe <- function(lst, x) {
digests <- vapply(lst, getCallStackDigest, character(1), warn = TRUE)
xdigest <- getCallStackDigest(x, warn = TRUE)
stopifnot(all(nzchar(digests)))
stopifnot(length(xdigest) == 1)
stopifnot(nzchar(xdigest))
if (xdigest %in% digests) {
return(lst)
} else {
return(c(lst, list(x)))
}
}
createStackTracePromiseDomain <- function() {
# These are actually stateless, we wouldn't have to create a new one each time
# if we didn't want to. They're pretty cheap though.
@@ -142,13 +180,14 @@ createStackTracePromiseDomain <- function() {
currentStack <- sys.calls()
currentParents <- sys.parents()
attr(currentStack, "parents") <- currentParents
currentStack <- saveCallStackDigest(currentStack)
currentDeepStack <- .globals$deepStack
}
function(...) {
# Fulfill time
if (deepStacksEnabled()) {
origDeepStack <- .globals$deepStack
.globals$deepStack <- c(currentDeepStack, list(currentStack))
.globals$deepStack <- appendCallStackWithDedupe(currentDeepStack, currentStack)
on.exit(.globals$deepStack <- origDeepStack, add = TRUE)
}
@@ -165,13 +204,14 @@ createStackTracePromiseDomain <- function() {
currentStack <- sys.calls()
currentParents <- sys.parents()
attr(currentStack, "parents") <- currentParents
currentStack <- saveCallStackDigest(currentStack)
currentDeepStack <- .globals$deepStack
}
function(...) {
# Fulfill time
if (deepStacksEnabled()) {
origDeepStack <- .globals$deepStack
.globals$deepStack <- c(currentDeepStack, list(currentStack))
.globals$deepStack <- appendCallStackWithDedupe(currentDeepStack, currentStack)
on.exit(.globals$deepStack <- origDeepStack, add = TRUE)
}
@@ -199,6 +239,7 @@ doCaptureStack <- function(e) {
calls <- sys.calls()
parents <- sys.parents()
attr(calls, "parents") <- parents
calls <- saveCallStackDigest(calls)
attr(e, "stack.trace") <- calls
}
if (deepStacksEnabled()) {
@@ -281,88 +322,115 @@ printStackTrace <- function(cond,
full = get_devmode_option("shiny.fullstacktrace", FALSE),
offset = getOption("shiny.stacktraceoffset", TRUE)) {
should_drop <- !full
should_strip <- !full
should_prune <- !full
stackTraceCalls <- c(
stackTraces <- c(
attr(cond, "deep.stack.trace", exact = TRUE),
list(attr(cond, "stack.trace", exact = TRUE))
)
stackTraceParents <- lapply(stackTraceCalls, attr, which = "parents", exact = TRUE)
stackTraceCallNames <- lapply(stackTraceCalls, getCallNames)
stackTraceCalls <- lapply(stackTraceCalls, offsetSrcrefs, offset = offset)
# Use dropTrivialFrames logic to remove trailing bits (.handleSimpleError, h)
if (should_drop) {
# toKeep is a list of logical vectors, of which elements (stack frames) to keep
toKeep <- lapply(stackTraceCallNames, dropTrivialFrames)
# We apply the list of logical vector indices to each data structure
stackTraceCalls <- mapply(stackTraceCalls, FUN = `[`, toKeep, SIMPLIFY = FALSE)
stackTraceCallNames <- mapply(stackTraceCallNames, FUN = `[`, toKeep, SIMPLIFY = FALSE)
stackTraceParents <- mapply(stackTraceParents, FUN = `[`, toKeep, SIMPLIFY = FALSE)
# Stripping of stack traces is the one step where the different stack traces
# interact. So we need to do this in one go, instead of individually within
# printOneStackTrace.
if (!full) {
stripResults <- stripStackTraces(lapply(stackTraces, getCallNames))
} else {
# If full is TRUE, we don't want to strip anything
stripResults <- rep_len(list(TRUE), length(stackTraces))
}
delayedAssign("all_true", {
# List of logical vectors that are all TRUE, the same shape as
# stackTraceCallNames. Delay the evaluation so we don't create it unless
# we need it, but if we need it twice then we don't pay to create it twice.
lapply(stackTraceCallNames, function(st) {
rep_len(TRUE, length(st))
})
})
# stripStackTraces and lapply(stackTraceParents, pruneStackTrace) return lists
# of logical vectors. Use mapply(FUN = `&`) to boolean-and each pair of the
# logical vectors.
toShow <- mapply(
if (should_strip) stripStackTraces(stackTraceCallNames) else all_true,
if (should_prune) lapply(stackTraceParents, pruneStackTrace) else all_true,
FUN = `&`,
mapply(
seq_along(stackTraces),
rev(stackTraces),
rev(stripResults),
FUN = function(i, trace, stripResult) {
if (is.integer(trace)) {
noun <- if (trace > 1L) "traces" else "trace"
message("[ reached getOption(\"shiny.deepstacktrace\") -- omitted ", trace, " more stack ", noun, " ]")
} else {
if (i != 1) {
message("From earlier call:")
}
printOneStackTrace(
stackTrace = trace,
stripResult = stripResult,
full = full,
offset = offset
)
}
# No mapply return value--we're just printing
NULL
},
SIMPLIFY = FALSE
)
dfs <- mapply(seq_along(stackTraceCalls), rev(stackTraceCalls), rev(stackTraceCallNames), rev(toShow), FUN = function(i, calls, nms, index) {
st <- data.frame(
num = rev(which(index)),
call = rev(nms[index]),
loc = rev(getLocs(calls[index])),
category = rev(getCallCategories(calls[index])),
stringsAsFactors = FALSE
)
if (i != 1) {
message("From earlier call:")
}
if (nrow(st) == 0) {
message(" [No stack trace available]")
} else {
width <- floor(log10(max(st$num))) + 1
formatted <- paste0(
" ",
formatC(st$num, width = width),
": ",
mapply(paste0(st$call, st$loc), st$category, FUN = function(name, category) {
if (category == "pkg")
crayon::silver(name)
else if (category == "user")
crayon::blue$bold(name)
else
crayon::white(name)
}),
"\n"
)
cat(file = stderr(), formatted, sep = "")
}
st
}, SIMPLIFY = FALSE)
invisible()
}
printOneStackTrace <- function(stackTrace, stripResult, full, offset) {
calls <- offsetSrcrefs(stackTrace, offset = offset)
callNames <- getCallNames(stackTrace)
parents <- attr(stackTrace, "parents", exact = TRUE)
should_drop <- !full
should_strip <- !full
should_prune <- !full
if (should_drop) {
toKeep <- dropTrivialFrames(callNames)
calls <- calls[toKeep]
callNames <- callNames[toKeep]
parents <- parents[toKeep]
stripResult <- stripResult[toKeep]
}
toShow <- rep(TRUE, length(callNames))
if (should_prune) {
toShow <- toShow & pruneStackTrace(parents)
}
if (should_strip) {
toShow <- toShow & stripResult
}
# If we're running in testthat, hide the parts of the stack trace that can
# vary based on how testthat was launched. It's critical that this is not
# happen at the same time as dropTrivialFrames, which happens before
# pruneStackTrace; because dropTrivialTestFrames removes calls from the top
# (or bottom? whichever is the oldest?) of the stack, it breaks `parents`
# which is based on absolute indices of calls. dropTrivialFrames gets away
# with this because it only removes calls from the opposite side of the stack.
toShow <- toShow & dropTrivialTestFrames(callNames)
st <- data.frame(
num = rev(which(toShow)),
call = rev(callNames[toShow]),
loc = rev(getLocs(calls[toShow])),
category = rev(getCallCategories(calls[toShow])),
stringsAsFactors = FALSE
)
if (nrow(st) == 0) {
message(" [No stack trace available]")
} else {
width <- floor(log10(max(st$num))) + 1
formatted <- paste0(
" ",
formatC(st$num, width = width),
": ",
mapply(paste0(st$call, st$loc), st$category, FUN = function(name, category) {
if (category == "pkg")
cli::col_silver(name)
else if (category == "user")
cli::style_bold(cli::col_blue(name))
else
cli::col_white(name)
}),
"\n"
)
cat(file = stderr(), formatted, sep = "")
}
invisible(st)
}
stripStackTraces <- function(stackTraces, values = FALSE) {
score <- 1L # >=1: show, <=0: hide
lapply(seq_along(stackTraces), function(i) {
@@ -458,6 +526,33 @@ dropTrivialFrames <- function(callnames) {
)
}
dropTrivialTestFrames <- function(callnames) {
if (!identical(Sys.getenv("TESTTHAT_IS_SNAPSHOT"), "true")) {
return(rep_len(TRUE, length(callnames)))
}
hideable <- callnames %in% c(
"test",
"devtools::test",
"test_check",
"testthat::test_check",
"test_dir",
"testthat::test_dir",
"test_file",
"testthat::test_file",
"test_local",
"testthat::test_local"
)
firstGoodCall <- min(which(!hideable))
toRemove <- firstGoodCall - 1L
c(
rep_len(FALSE, toRemove),
rep_len(TRUE, length(callnames) - toRemove)
)
}
offsetSrcrefs <- function(calls, offset = TRUE) {
if (offset) {
srcrefs <- getSrcRefs(calls)

View File

@@ -41,6 +41,54 @@
#' is, a function that quickly returns a promise) and allows even that very
#' session to immediately unblock and carry on with other user interactions.
#'
#' @examplesIf rlang::is_interactive() && rlang::is_installed("future")
#'
#' library(shiny)
#' library(bslib)
#' library(future)
#' plan(multisession)
#'
#' ui <- page_fluid(
#' titlePanel("Extended Task Demo"),
#' p(
#' 'Click the button below to perform a "calculation"',
#' "that takes a while to perform."
#' ),
#' input_task_button("recalculate", "Recalculate"),
#' p(textOutput("result"))
#' )
#'
#' server <- function(input, output) {
#' rand_task <- ExtendedTask$new(function() {
#' future(
#' {
#' # Slow operation goes here
#' Sys.sleep(2)
#' sample(1:100, 1)
#' },
#' seed = TRUE
#' )
#' })
#'
#' # Make button state reflect task.
#' # If using R >=4.1, you can do this instead:
#' # rand_task <- ExtendedTask$new(...) |> bind_task_button("recalculate")
#' bind_task_button(rand_task, "recalculate")
#'
#' observeEvent(input$recalculate, {
#' # Invoke the extended in an observer
#' rand_task$invoke()
#' })
#'
#' output$result <- renderText({
#' # React to updated results when the task completes
#' number <- rand_task$result()
#' paste0("Your number is ", number, ".")
#' })
#' }
#'
#' shinyApp(ui, server)
#'
#' @export
ExtendedTask <- R6Class("ExtendedTask", portable = TRUE, cloneable = FALSE,
public = list(

View File

@@ -29,22 +29,36 @@
#' A numeric vector of length 1.
#'
#' @export
numericInput <- function(inputId, label, value, min = NA, max = NA, step = NA,
width = NULL) {
numericInput <- function(
inputId,
label,
value,
min = NA,
max = NA,
step = NA,
width = NULL,
...,
updateOn = c("change", "blur")
) {
rlang::check_dots_empty()
updateOn <- rlang::arg_match(updateOn)
value <- restoreInput(id = inputId, default = value)
# build input tag
inputTag <- tags$input(id = inputId, type = "number", class="shiny-input-number form-control",
value = formatNoSci(value))
if (!is.na(min))
inputTag$attribs$min = min
if (!is.na(max))
inputTag$attribs$max = max
if (!is.na(step))
inputTag$attribs$step = step
inputTag <- tags$input(
id = inputId,
type = "number",
class = "shiny-input-number form-control",
value = formatNoSci(value),
`data-update-on` = updateOn
)
if (!is.na(min)) inputTag$attribs$min = min
if (!is.na(max)) inputTag$attribs$max = max
if (!is.na(step)) inputTag$attribs$step = step
div(class = "form-group shiny-input-container",
div(
class = "form-group shiny-input-container",
style = css(width = validateCssUnit(width)),
shinyInputLabel(inputId, label),
inputTag

View File

@@ -30,12 +30,29 @@
#' shinyApp(ui, server)
#' }
#' @export
passwordInput <- function(inputId, label, value = "", width = NULL,
placeholder = NULL) {
div(class = "form-group shiny-input-container",
passwordInput <- function(
inputId,
label,
value = "",
width = NULL,
placeholder = NULL,
...,
updateOn = c("change", "blur")
) {
rlang::check_dots_empty()
updateOn <- rlang::arg_match(updateOn)
div(
class = "form-group shiny-input-container",
style = css(width = validateCssUnit(width)),
shinyInputLabel(inputId, label),
tags$input(id = inputId, type="password", class="shiny-input-password form-control", value=value,
placeholder = placeholder)
tags$input(
id = inputId,
type = "password",
class = "shiny-input-password form-control",
value = value,
placeholder = placeholder,
`data-update-on` = updateOn
)
)
}

View File

@@ -57,7 +57,7 @@ submitButton <- function(text = "Apply Changes", icon = NULL, width = NULL) {
div(
tags$button(
type="submit",
class="btn btn-primary",
class="btn btn-primary submit-button",
style = css(width = validateCssUnit(width)),
list(icon, text)
)

View File

@@ -10,6 +10,14 @@
#' @param placeholder A character string giving the user a hint as to what can
#' be entered into the control. Internet Explorer 8 and 9 do not support this
#' option.
#' @param ... Ignored, included to require named arguments and for future
#' feature expansion.
#' @param updateOn A character vector specifying when the input should be
#' updated. Options are `"change"` (default) and `"blur"`. Use `"change"` to
#' update the input immediately whenever the value changes. Use `"blur"`to
#' delay the input update until the input loses focus (the user moves away
#' from the input), or when Enter is pressed (or Cmd/Ctrl + Enter for
#' [textAreaInput()]).
#' @return A text input control that can be added to a UI definition.
#'
#' @family input elements
@@ -34,15 +42,31 @@
#' unless `value` is provided.
#'
#' @export
textInput <- function(inputId, label, value = "", width = NULL,
placeholder = NULL) {
textInput <- function(
inputId,
label,
value = "",
width = NULL,
placeholder = NULL,
...,
updateOn = c("change", "blur")
) {
rlang::check_dots_empty()
updateOn <- rlang::arg_match(updateOn)
value <- restoreInput(id = inputId, default = value)
div(class = "form-group shiny-input-container",
div(
class = "form-group shiny-input-container",
style = css(width = validateCssUnit(width)),
shinyInputLabel(inputId, label),
tags$input(id = inputId, type="text", class="shiny-input-text form-control", value=value,
placeholder = placeholder)
tags$input(
id = inputId,
type = "text",
class = "shiny-input-text form-control",
value = value,
placeholder = placeholder,
`data-update-on` = updateOn
)
)
}

View File

@@ -16,6 +16,8 @@
#' @param resize Which directions the textarea box can be resized. Can be one of
#' `"both"`, `"none"`, `"vertical"`, and `"horizontal"`. The default, `NULL`,
#' will use the client browser's default setting for resizing textareas.
#' @param autoresize If `TRUE`, the textarea will automatically resize to fit
#' the input text.
#' @return A textarea input control that can be added to a UI definition.
#'
#' @family input elements
@@ -41,8 +43,22 @@
#' unless `value` is provided.
#'
#' @export
textAreaInput <- function(inputId, label, value = "", width = NULL, height = NULL,
cols = NULL, rows = NULL, placeholder = NULL, resize = NULL) {
textAreaInput <- function(
inputId,
label,
value = "",
width = NULL,
height = NULL,
cols = NULL,
rows = NULL,
placeholder = NULL,
resize = NULL,
...,
autoresize = FALSE,
updateOn = c("change", "blur")
) {
rlang::check_dots_empty()
updateOn <- rlang::arg_match(updateOn)
value <- restoreInput(id = inputId, default = value)
@@ -50,23 +66,30 @@ textAreaInput <- function(inputId, label, value = "", width = NULL, height = NUL
resize <- match.arg(resize, c("both", "none", "vertical", "horizontal"))
}
style <- css(
# The width is specified on the parent div.
width = if (!is.null(width)) "100%",
height = validateCssUnit(height),
resize = resize
)
classes <- c("shiny-input-textarea", "form-control")
if (autoresize) {
classes <- c(classes, "textarea-autoresize")
if (is.null(rows)) {
rows <- 1
}
}
div(class = "form-group shiny-input-container",
div(
class = "form-group shiny-input-container",
style = css(width = validateCssUnit(width)),
shinyInputLabel(inputId, label),
style = if (!is.null(width)) paste0("width: ", validateCssUnit(width), ";"),
tags$textarea(
id = inputId,
class = "shiny-input-textarea form-control",
class = classes,
placeholder = placeholder,
style = style,
style = css(
width = if (!is.null(width)) "100%",
height = validateCssUnit(height),
resize = resize
),
rows = rows,
cols = cols,
`data-update-on` = updateOn,
value
)
)

View File

@@ -53,10 +53,12 @@ Context <- R6Class(
promises::with_promise_domain(reactivePromiseDomain(), {
withReactiveDomain(.domain, {
env <- .getReactiveEnvironment()
rLog$enter(.reactId, id, .reactType, .domain)
on.exit(rLog$exit(.reactId, id, .reactType, .domain), add = TRUE)
env$runWith(self, func)
captureStackTraces({
env <- .getReactiveEnvironment()
rLog$enter(.reactId, id, .reactType, .domain)
on.exit(rLog$exit(.reactId, id, .reactType, .domain), add = TRUE)
env$runWith(self, func)
})
})
})
},
@@ -223,9 +225,7 @@ wrapForContext <- function(func, ctx) {
function(...) {
.getReactiveEnvironment()$runWith(ctx, function() {
captureStackTraces(
func(...)
)
func(...)
})
}
}

View File

@@ -2304,7 +2304,7 @@ observeEvent <- function(eventExpr, handlerExpr,
priority = priority,
domain = domain,
autoDestroy = TRUE,
..stacktraceon = FALSE # TODO: Does this go in the bindEvent?
..stacktraceon = TRUE
))
o <- inject(bindEvent(

View File

@@ -445,7 +445,9 @@ stopApp <- function(returnValue = invisible()) {
#' @param host The IPv4 address that the application should listen on. Defaults
#' to the `shiny.host` option, if set, or `"127.0.0.1"` if not.
#' @param display.mode The mode in which to display the example. Defaults to
#' `showcase`, but may be set to `normal` to see the example without
#' `"auto"`, which uses the value of `DisplayMode` in the example's
#' `DESCRIPTION` file. Set to `"showcase"` to show the app code and
#' description with the running app, or `"normal"` to see the example without
#' code or commentary.
#' @param package The package in which to find the example (defaults to
#' `"shiny"`).

View File

@@ -65,16 +65,20 @@ getShinyOption <- function(name, default = NULL) {
#' changes are detected, all connected Shiny sessions are reloaded. This
#' allows for fast feedback loops when tweaking Shiny UI.
#'
#' Since monitoring for changes is expensive (we simply poll for last
#' modified times), this feature is intended only for development.
#' Monitoring for changes is no longer expensive, thanks to the \pkg{watcher}
#' package, but this feature is still intended only for development.
#'
#' You can customize the file patterns Shiny will monitor by setting the
#' shiny.autoreload.pattern option. For example, to monitor only ui.R:
#' `options(shiny.autoreload.pattern = glob2rx("ui.R"))`
#' shiny.autoreload.pattern option. For example, to monitor only `ui.R`:
#' `options(shiny.autoreload.pattern = glob2rx("ui.R"))`.
#'
#' The default polling interval is 500 milliseconds. You can change this
#' by setting e.g. `options(shiny.autoreload.interval = 2000)` (every
#' two seconds).}
#' As mentioned above, Shiny no longer polls watched files for changes.
#' Instead, using \pkg{watcher}, Shiny is notified of file changes as they
#' occur. These changes are batched together within a customizable latency
#' period. You can adjust this period by setting
#' `options(shiny.autoreload.interval = 2000)` (in milliseconds). This value
#' converted to seconds and passed to the `latency` argument of
#' [watcher::watcher()]. The default latency is 250ms.}
#' \item{shiny.deprecation.messages (defaults to `TRUE`)}{This controls whether messages for
#' deprecated functions in Shiny will be printed. See
#' [shinyDeprecated()] for more information.}

View File

@@ -2024,7 +2024,7 @@ ShinySession <- R6Class(
tmpdata <- tempfile(fileext = ext)
return(Context$new(getDefaultReactiveDomain(), '[download]')$run(function() {
promises::with_promise_domain(reactivePromiseDomain(), {
promises::with_promise_domain(createStackTracePromiseDomain(), {
captureStackTraces({
self$incrementBusyCount()
hybrid_chain(
# ..stacktraceon matches with the top-level ..stacktraceoff..

View File

@@ -162,11 +162,29 @@ shinyAppDir_serverR <- function(appDir, options=list()) {
sharedEnv <- globalenv()
}
# To enable hot-reloading of support files, this function is called
# whenever the UI or Server func source is updated. To avoid loading
# support files 2x, we follow the last cache update trigger timestamp.
autoload_r_support_if_needed <- local({
autoload_last_loaded <- -1
function() {
if (!isTRUE(getOption("shiny.autoload.r", TRUE))) return()
last_cache_trigger <- cachedAutoReloadLastChanged$get()
if (identical(autoload_last_loaded, last_cache_trigger)) return()
loadSupport(appDir, renv = sharedEnv, globalrenv = globalenv())
autoload_last_loaded <<- last_cache_trigger
}
})
# uiHandlerSource is a function that returns an HTTP handler for serving up
# ui.R as a webpage. The "cachedFuncWithFile" call makes sure that the closure
# we're creating here only gets executed when ui.R's contents change.
uiHandlerSource <- cachedFuncWithFile(appDir, "ui.R", case.sensitive = FALSE,
function(uiR) {
autoload_r_support_if_needed()
if (file.exists(uiR)) {
# If ui.R contains a call to shinyUI (which sets .globals$ui), use that.
# If not, then take the last expression that's returned from ui.R.
@@ -197,6 +215,7 @@ shinyAppDir_serverR <- function(appDir, options=list()) {
serverSource <- cachedFuncWithFile(appDir, "server.R", case.sensitive = FALSE,
function(serverR) {
autoload_r_support_if_needed()
# If server.R contains a call to shinyServer (which sets .globals$server),
# use that. If not, then take the last expression that's returned from
# server.R.
@@ -232,10 +251,9 @@ shinyAppDir_serverR <- function(appDir, options=list()) {
onStart <- function() {
oldwd <<- getwd()
setwd(appDir)
# TODO: we should support hot reloading on global.R and R/*.R changes.
if (getOption("shiny.autoload.r", TRUE)) {
loadSupport(appDir, renv=sharedEnv, globalrenv=globalenv())
} else {
autoload_r_support_if_needed()
} else {
if (file.exists(file.path.ci(appDir, "global.R")))
sourceUTF8(file.path.ci(appDir, "global.R"))
}
@@ -290,33 +308,77 @@ initAutoReloadMonitor <- function(dir) {
return(function(){})
}
filePattern <- getOption("shiny.autoreload.pattern",
".*\\.(r|html?|js|css|png|jpe?g|gif)$")
filePattern <- getOption(
"shiny.autoreload.pattern",
".*\\.(r|html?|js|css|png|jpe?g|gif)$"
)
lastValue <- NULL
observeLabel <- paste0("File Auto-Reload - '", basename(dir), "'")
obs <- observe(label = observeLabel, {
files <- sort_c(
list.files(dir, pattern = filePattern, recursive = TRUE, ignore.case = TRUE)
)
times <- file.info(files)$mtime
names(times) <- files
if (is.null(lastValue)) {
# First run
lastValue <<- times
} else if (!identical(lastValue, times)) {
# We've changed!
lastValue <<- times
if (is_installed("watcher")) {
check_for_update <- function(paths) {
paths <- grep(
filePattern,
paths,
ignore.case = TRUE,
value = TRUE
)
if (length(paths) == 0) {
return()
}
cachedAutoReloadLastChanged$set()
autoReloadCallbacks$invoke()
}
# [garrick, 2025-02-20] Shiny <= v1.10.0 used `invalidateLater()` with an
# autoreload.interval in ms. {watcher} instead uses a latency parameter in
# seconds, which serves a similar purpose and that I'm keeping for backcompat.
latency <- getOption("shiny.autoreload.interval", 250) / 1000
watcher <- watcher::watcher(dir, check_for_update, latency = latency)
watcher$start()
onStop(watcher$stop)
} else {
# Fall back to legacy observer behavior
if (!is_false(getOption("shiny.autoreload.legacy_warning", TRUE))) {
cli::cli_warn(
c(
"Using legacy autoreload file watching. Please install {.pkg watcher} for a more performant autoreload file watcher.",
"i" = "Set {.run options(shiny.autoreload.legacy_warning = FALSE)} to suppress this warning."
),
.frequency = "regularly",
.frequency_id = "shiny.autoreload.legacy_warning"
)
}
invalidateLater(getOption("shiny.autoreload.interval", 500))
})
lastValue <- NULL
observeLabel <- paste0("File Auto-Reload - '", basename(dir), "'")
watcher <- observe(label = observeLabel, {
files <- sort_c(
list.files(dir, pattern = filePattern, recursive = TRUE, ignore.case = TRUE)
)
times <- file.info(files)$mtime
names(times) <- files
if (is.null(lastValue)) {
# First run
lastValue <<- times
} else if (!identical(lastValue, times)) {
# We've changed!
lastValue <<- times
cachedAutoReloadLastChanged$set()
autoReloadCallbacks$invoke()
}
invalidateLater(getOption("shiny.autoreload.interval", 500))
})
onStop(watcher$destroy)
watcher$destroy
}
onStop(obs$destroy)
obs$destroy
invisible(watcher)
}
#' Load an app's supporting R files
@@ -421,8 +483,6 @@ shinyAppDir_appR <- function(fileName, appDir, options=list())
wasDir <- setwd(appDir)
on.exit(setwd(wasDir))
# TODO: we should support hot reloading on R/*.R changes.
# In an upcoming version of shiny, this option will go away.
if (getOption("shiny.autoload.r", TRUE)) {
# Create a child env which contains all the helpers and will be the shared parent
# of the ui.R and server.R load.

View File

@@ -383,8 +383,10 @@ markOutputAttrs <- function(renderFunc, snapshotExclude = NULL,
#' The corresponding HTML output tag should be `div` or `img` and have
#' the CSS class name `shiny-image-output`.
#'
#' @seealso For more details on how the images are generated, and how to control
#' @seealso
#' * For more details on how the images are generated, and how to control
#' the output, see [plotPNG()].
#' * Use [outputOptions()] to set general output options for an image output.
#'
#' @param expr An expression that returns a list.
#' @inheritParams renderUI
@@ -598,6 +600,7 @@ isTemp <- function(path, tempDir = tempdir(), mustExist) {
#' used in an interactive RMarkdown document.
#'
#' @example res/text-example.R
#' @seealso [outputOptions()]
#' @export
renderPrint <- function(expr, env = parent.frame(), quoted = FALSE,
width = getOption('width'), outputArgs=list())
@@ -719,7 +722,7 @@ renderText <- function(expr, env = parent.frame(), quoted = FALSE,
#' call to [uiOutput()] when `renderUI` is used in an
#' interactive R Markdown document.
#'
#' @seealso [uiOutput()]
#' @seealso [uiOutput()], [outputOptions()]
#' @export
#' @examples
#' ## Only run examples in interactive R sessions
@@ -809,6 +812,13 @@ renderUI <- function(expr, env = parent.frame(), quoted = FALSE,
#'
#' shinyApp(ui, server)
#' }
#'
#' @seealso
#' * The download handler, like other outputs, is suspended (disabled) by
#' default for download buttons and links that are hidden. Use
#' [outputOptions()] to control this behavior, e.g. to set
#' `suspendWhenHidden = FALSE` if the download is initiated by
#' programmatically clicking on the download button using JavaScript.
#' @export
downloadHandler <- function(filename, content, contentType=NULL, outputArgs=list()) {
renderFunc <- function(shinysession, name, ...) {

View File

@@ -158,8 +158,7 @@ print.shiny_runtests <- function(x, ..., reporter = "summary") {
if (any(x$pass)) {
# TODO in future... use clisymbols::symbol$tick and crayon green
cat("* Success\n")
cli::cat_bullet("Success", bullet = "tick", bullet_col = "green")
mapply(
x$file,
x$pass,
@@ -171,9 +170,8 @@ print.shiny_runtests <- function(x, ..., reporter = "summary") {
}
)
}
if (any(!x$pass)) {
# TODO in future... use clisymbols::symbol$cross and crayon red
cat("* Failure\n")
if (!all(x$pass)) {
cli::cat_bullet("Failure", bullet = "cross", bullet_col = "red")
mapply(
x$file,
x$pass,

View File

@@ -770,22 +770,45 @@ formatNoSci <- function(x) {
format(x, scientific = FALSE, digits = 15)
}
# A simple getter/setting to track the last time the auto-reload process
# updated. This value is used by `cachedFuncWithFile()` when auto-reload is
# enabled to reload app/ui/server files when watched supporting files change.
cachedAutoReloadLastChanged <- local({
last_update <- 0
list(
set = function() {
last_update <<- as.integer(Sys.time())
invisible(last_update)
},
get = function() {
last_update
}
)
})
# Returns a function that calls the given func and caches the result for
# subsequent calls, unless the given file's mtime changes.
cachedFuncWithFile <- function(dir, file, func, case.sensitive = FALSE) {
dir <- normalizePath(dir, mustWork=TRUE)
mtime <- NA
dir <- normalizePath(dir, mustWork = TRUE)
value <- NULL
last_mtime_file <- NA
last_autoreload <- 0
function(...) {
fname <- if (case.sensitive)
file.path(dir, file)
else
fname <- if (case.sensitive) {
file.path(dir, file)
} else {
file.path.ci(dir, file)
}
now <- file.info(fname)$mtime
if (!identical(mtime, now)) {
autoreload <- last_autoreload < cachedAutoReloadLastChanged$get()
if (autoreload || !identical(last_mtime_file, now)) {
value <<- func(fname, ...)
mtime <<- now
last_mtime_file <<- now
last_autoreload <<- cachedAutoReloadLastChanged$get()
}
value
}

View File

@@ -1,15 +0,0 @@
{
"presets": [
"@babel/preset-typescript",
[
"@babel/preset-env",
{
"useBuiltIns": "usage",
"corejs": "3.12"
}
]
],
"ignore":[
"node_modules/core-js"
]
}

View File

@@ -0,0 +1,40 @@
## revdepcheck results
We checked 1278 reverse dependencies (1277 from CRAN + 1 from Bioconductor), comparing R CMD check results across CRAN and dev versions of shiny.
* We saw 2 new problems (NOTEs only)
* We failed to check 19 packages due to installation issues
Issues with CRAN packages are summarised below.
### New problems
R CMD check displayed NOTEs for two packages, unrelated to changes in shiny.
* HH
checking installed package size ... NOTE
* PopED
checking installed package size ... NOTE
### Failed to check
* animalEKF
* AovBay
* Certara.VPCResults
* chipPCR
* ctsem
* dartR.sim
* diveR
* gap
* jsmodule
* loon.shiny
* robmedExtra
* rstanarm
* SensMap
* Seurat
* shinyTempSignal
* Signac
* statsr
* TestAnaAPP
* tidyvpc

View File

@@ -1,2 +1,2 @@
/*! shiny 1.9.1 | (c) 2012-2024 RStudio, PBC. | License: GPL-3 | file LICENSE */
:where([data-shiny-busy-spinners] .recalculating){position:relative}[data-shiny-busy-spinners] .recalculating:after{position:absolute;content:"";--_shiny-spinner-url: var(--shiny-spinner-url, url(spinners/ring.svg));--_shiny-spinner-color: var(--shiny-spinner-color, var(--bs-primary, #007bc2));--_shiny-spinner-size: var(--shiny-spinner-size, 32px);--_shiny-spinner-delay: var(--shiny-spinner-delay, 1s);background:var(--_shiny-spinner-color);width:var(--_shiny-spinner-size);height:var(--_shiny-spinner-size);inset:calc(50% - var(--_shiny-spinner-size) / 2);mask-image:var(--_shiny-spinner-url);-webkit-mask-image:var(--_shiny-spinner-url);opacity:0;animation-delay:var(--_shiny-spinner-delay);animation-name:fade-in;animation-duration:.25s;animation-fill-mode:forwards}[data-shiny-busy-spinners] .recalculating:has(>*),[data-shiny-busy-spinners] .recalculating:empty{opacity:1}[data-shiny-busy-spinners] .recalculating>*:not(.recalculating){opacity:var(--_shiny-fade-opacity);transition:opacity .25s ease var(--shiny-spinner-delay, 1s)}[data-shiny-busy-spinners] .recalculating.shiny-html-output:after{display:none}[data-shiny-busy-spinners][data-shiny-busy-pulse].shiny-busy:after{--_shiny-pulse-background: var( --shiny-pulse-background, linear-gradient( 120deg, var(--bs-body-bg, #fff), var(--bs-indigo, #4b00c1), var(--bs-purple, #74149c), var(--bs-pink, #bf007f), var(--bs-body-bg, #fff) ) );--_shiny-pulse-height: var(--shiny-pulse-height, 5px);--_shiny-pulse-speed: var(--shiny-pulse-speed, 1.85s);position:fixed;top:0;left:0;height:var(--_shiny-pulse-height);background:var(--_shiny-pulse-background);z-index:9999;animation-name:busy-page-pulse;animation-duration:var(--_shiny-pulse-speed);animation-iteration-count:infinite;animation-timing-function:ease-in-out;content:""}[data-shiny-busy-spinners][data-shiny-busy-pulse].shiny-busy:has(.recalculating):after{display:none}[data-shiny-busy-spinners][data-shiny-busy-pulse].shiny-busy:has(#shiny-disconnected-overlay):after{display:none}[data-shiny-busy-pulse]:not([data-shiny-busy-spinners]).shiny-busy:after{--_shiny-pulse-background: var( --shiny-pulse-background, linear-gradient( 120deg, var(--bs-body-bg, #fff), var(--bs-indigo, #4b00c1), var(--bs-purple, #74149c), var(--bs-pink, #bf007f), var(--bs-body-bg, #fff) ) );--_shiny-pulse-height: var(--shiny-pulse-height, 5px);--_shiny-pulse-speed: var(--shiny-pulse-speed, 1.85s);position:fixed;top:0;left:0;height:var(--_shiny-pulse-height);background:var(--_shiny-pulse-background);z-index:9999;animation-name:busy-page-pulse;animation-duration:var(--_shiny-pulse-speed);animation-iteration-count:infinite;animation-timing-function:ease-in-out;content:""}[data-shiny-busy-pulse]:not([data-shiny-busy-spinners]).shiny-busy:has(#shiny-disconnected-overlay):after{display:none}@keyframes fade-in{0%{opacity:0}to{opacity:1}}@keyframes busy-page-pulse{0%{left:-75%;width:75%}50%{left:100%;width:75%}to{left:-75%;width:75%}}.shiny-spinner-output-container{--shiny-spinner-size: 0px}
/*! shiny 1.10.0.9000 | (c) 2012-2025 Posit Software, PBC. | License: GPL-3 | file LICENSE */
:where([data-shiny-busy-spinners] .recalculating){position:relative}[data-shiny-busy-spinners] .recalculating{min-height:var(--shiny-spinner-size, 32px)}[data-shiny-busy-spinners] .recalculating:after{position:absolute;content:"";--_shiny-spinner-url: var(--shiny-spinner-url, url(spinners/ring.svg));--_shiny-spinner-color: var(--shiny-spinner-color, var(--bs-primary, #007bc2));--_shiny-spinner-size: var(--shiny-spinner-size, 32px);--_shiny-spinner-delay: var(--shiny-spinner-delay, 1s);background:var(--_shiny-spinner-color);width:var(--_shiny-spinner-size);height:var(--_shiny-spinner-size);inset:calc(50% - var(--_shiny-spinner-size) / 2);mask-image:var(--_shiny-spinner-url);-webkit-mask-image:var(--_shiny-spinner-url);opacity:0;animation-delay:var(--_shiny-spinner-delay);animation-name:fade-in;animation-duration:.25s;animation-fill-mode:forwards}[data-shiny-busy-spinners] .recalculating:has(>*),[data-shiny-busy-spinners] .recalculating:empty{opacity:1}[data-shiny-busy-spinners] .recalculating>*:not(.recalculating){opacity:var(--_shiny-fade-opacity);transition:opacity .25s ease var(--shiny-spinner-delay, 1s)}[data-shiny-busy-spinners] .recalculating.html-widget-output{visibility:inherit!important}[data-shiny-busy-spinners] .recalculating.html-widget-output>*{visibility:hidden}[data-shiny-busy-spinners] .recalculating.html-widget-output :after{visibility:visible}[data-shiny-busy-spinners] .recalculating.shiny-html-output:not(.shiny-table-output):after{display:none}[data-shiny-busy-spinners][data-shiny-busy-pulse].shiny-busy:after{--_shiny-pulse-background: var( --shiny-pulse-background, linear-gradient( 120deg, transparent, var(--bs-indigo, #4b00c1), var(--bs-purple, #74149c), var(--bs-pink, #bf007f), transparent ) );--_shiny-pulse-height: var(--shiny-pulse-height, 3px);--_shiny-pulse-speed: var(--shiny-pulse-speed, 1.2s);position:fixed;top:0;left:0;height:var(--_shiny-pulse-height);background:var(--_shiny-pulse-background);z-index:9999;animation-name:busy-page-pulse;animation-duration:var(--_shiny-pulse-speed);animation-direction:alternate;animation-iteration-count:infinite;animation-timing-function:ease-in-out;content:""}[data-shiny-busy-spinners][data-shiny-busy-pulse].shiny-busy:has(.recalculating:not(.shiny-html-output)):after{display:none}[data-shiny-busy-spinners][data-shiny-busy-pulse].shiny-busy:has(.recalculating.shiny-table-output):after{display:none}[data-shiny-busy-spinners][data-shiny-busy-pulse].shiny-busy:has(#shiny-disconnected-overlay):after{display:none}[data-shiny-busy-pulse]:not([data-shiny-busy-spinners]).shiny-busy:after{--_shiny-pulse-background: var( --shiny-pulse-background, linear-gradient( 120deg, transparent, var(--bs-indigo, #4b00c1), var(--bs-purple, #74149c), var(--bs-pink, #bf007f), transparent ) );--_shiny-pulse-height: var(--shiny-pulse-height, 3px);--_shiny-pulse-speed: var(--shiny-pulse-speed, 1.2s);position:fixed;top:0;left:0;height:var(--_shiny-pulse-height);background:var(--_shiny-pulse-background);z-index:9999;animation-name:busy-page-pulse;animation-duration:var(--_shiny-pulse-speed);animation-direction:alternate;animation-iteration-count:infinite;animation-timing-function:ease-in-out;content:""}[data-shiny-busy-pulse]:not([data-shiny-busy-spinners]).shiny-busy:has(#shiny-disconnected-overlay):after{display:none}@keyframes fade-in{0%{opacity:0}to{opacity:1}}@keyframes busy-page-pulse{0%{left:-14%;right:97%}45%{left:0%;right:14%}55%{left:14%;right:0%}to{left:97%;right:-14%}}.shiny-spinner-output-container{--shiny-spinner-size: 0px}

File diff suppressed because one or more lines are too long

View File

@@ -235,6 +235,10 @@
z-index: 2;
}
.irs--shiny .irs-handle.type_last {
z-index: 3;
}
.irs--shiny .irs-handle.state_hover, .irs--shiny .irs-handle:hover {
background: #fff;
}

File diff suppressed because one or more lines are too long

View File

@@ -143,6 +143,11 @@ $font-family: $font-family-base !default;
border-radius: $handle_width;
z-index: 2;
&.type_last {
// Ensure last-used handle is on top if it overlaps with another handle
z-index: 3;
}
&.state_hover,
&:hover {
background: $handle_color_hover;

View File

@@ -1 +1 @@
Selectize.define("selectize-plugin-a11y",function(c){var t=this,l=13;typeof t.accessibility=="undefined"&&(t.accessibility={}),t.accessibility.helpers={randomId:function(e){for(var r="",s=e||10,i="abcdefghijklmnopqrstuvwxyz0123456789",n=i.length,a=0;a<s;a++)r+=i[Math.floor(n*Math.random())];return r}},t.accessibility.liveRegion={$region:"",speak:function(e){var r=$("<div></div>");r.text(e),this.$region.html(r)},domListener:function(){var e=new MutationObserver(function(r){r.forEach(function(s){var i=$(s.target);if(i.hasClass("items"))if(i.hasClass("dropdown-active")){t.$control_input.attr("aria-expanded","true");for(var n=t.$dropdown_content[0].children,a=0;a<n.length;a++){var o=n[a].attributes;o.role||n[a].setAttribute("role","option"),o.id||n[a].setAttribute("id",t.accessibility.helpers.randomId())}}else t.$control_input.attr("aria-expanded","false"),t.$control_input.removeAttr("aria-activedescendant");else i.hasClass("active")&&i.attr("data-value")&&(t.$control_input.attr("aria-activedescendant",i.attr("id")),t.accessibility.liveRegion.speak(i.text(),500))})});e.observe(t.$dropdown[0],{attributes:!0,attributeFilter:["class"],subtree:!0,attributeOldValue:!0}),e.observe(t.$control[0],{attributes:!0,attributeFilter:["class"]}),e.observe(t.$control_input[0],{attributes:!0,attributeFilter:["value"]})},setAttributes:function(){this.$region.attr({"aria-live":"assertive",role:"log","aria-relevant":"additions","aria-atomic":"true"})},setStyles:function(){this.$region.css({position:"absolute",width:"1px",height:"1px","margin-top":"-1px",clip:"rect(1px, 1px, 1px, 1px)",overflow:"hidden"})},init:function(){this.$region=$("<div>"),this.setAttributes(),this.setStyles(),$("body").append(this.$region),this.domListener()}},this.setup=function(){var e=t.setup;return function(){e.apply(this,arguments);var r=t.accessibility.helpers.randomId(),s=t.accessibility.helpers.randomId();t.$control.on("keydown",function(i){i.keyCode===l&&(t.settings.openOnFocus?(t.settings.openOnFocus=!1,t.focus(),setTimeout(function(){t.settings.openOnFocus=!0},0)):t.focus())}),t.$control_input.attr({role:"combobox","aria-expanded":"false",haspopup:"listbox","aria-owns":s,"aria-label":t.$wrapper.closest("[data-accessibility-selectize-label]").attr("data-accessibility-selectize-label")}),t.$dropdown_content.attr({role:"listbox",id:s}),t.accessibility.liveRegion.init()}}(),this.destroy=function(){var e=t.destroy;return function(){return t.accessibility.liveRegion.$region.remove(),e.apply(this,arguments)}}()});
Selectize.define("selectize-plugin-a11y",function(c){var t=this,l=13;typeof t.accessibility>"u"&&(t.accessibility={}),t.accessibility.helpers={randomId:function(e){for(var r="",s=e||10,i="abcdefghijklmnopqrstuvwxyz0123456789",n=i.length,a=0;a<s;a++)r+=i[Math.floor(n*Math.random())];return r}},t.accessibility.liveRegion={$region:"",speak:function(e){var r=$("<div></div>");r.text(e),this.$region.html(r)},domListener:function(){var e=new MutationObserver(function(r){r.forEach(function(s){var i=$(s.target);if(i.hasClass("items"))if(i.hasClass("dropdown-active")){t.$control_input.attr("aria-expanded","true");for(var n=t.$dropdown_content[0].children,a=0;a<n.length;a++){var o=n[a].attributes;o.role||n[a].setAttribute("role","option"),o.id||n[a].setAttribute("id",t.accessibility.helpers.randomId())}}else t.$control_input.attr("aria-expanded","false"),t.$control_input.removeAttr("aria-activedescendant");else i.hasClass("active")&&i.attr("data-value")&&(t.$control_input.attr("aria-activedescendant",i.attr("id")),t.accessibility.liveRegion.speak(i.text(),500))})});e.observe(t.$dropdown[0],{attributes:!0,attributeFilter:["class"],subtree:!0,attributeOldValue:!0}),e.observe(t.$control[0],{attributes:!0,attributeFilter:["class"]}),e.observe(t.$control_input[0],{attributes:!0,attributeFilter:["value"]})},setAttributes:function(){this.$region.attr({"aria-live":"assertive",role:"log","aria-relevant":"additions","aria-atomic":"true"})},setStyles:function(){this.$region.css({position:"absolute",width:"1px",height:"1px","margin-top":"-1px",clip:"rect(1px, 1px, 1px, 1px)",overflow:"hidden"})},init:function(){this.$region=$("<div>"),this.setAttributes(),this.setStyles(),$("body").append(this.$region),this.domListener()}},this.setup=function(){var e=t.setup;return function(){e.apply(this,arguments);var r=t.accessibility.helpers.randomId(),s=t.accessibility.helpers.randomId();t.$control.on("keydown",function(i){i.keyCode===l&&(t.settings.openOnFocus?(t.settings.openOnFocus=!1,t.focus(),setTimeout(function(){t.settings.openOnFocus=!0},0)):t.focus())}),t.$control_input.attr({role:"combobox","aria-expanded":"false",haspopup:"listbox","aria-owns":s,"aria-label":t.$wrapper.closest("[data-accessibility-selectize-label]").attr("data-accessibility-selectize-label")}),t.$dropdown_content.attr({role:"listbox",id:s}),t.accessibility.liveRegion.init()}}(),this.destroy=function(){var e=t.destroy;return function(){return t.accessibility.liveRegion.$region.remove(),e.apply(this,arguments)}}()});

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,2 +1,2 @@
/*! shiny 1.9.1 | (c) 2012-2024 RStudio, PBC. | License: GPL-3 | file LICENSE */
/*! shiny 1.10.0.9000 | (c) 2012-2025 Posit Software, PBC. | License: GPL-3 | file LICENSE */
#showcase-well{border-radius:0}.shiny-code{background-color:#fff;margin-bottom:0}.shiny-code code{font-family:Menlo,Consolas,Courier New,monospace}.shiny-code-container{margin-top:20px;clear:both}.shiny-code-container h3{display:inline;margin-right:15px}.showcase-header{font-size:16px;font-weight:400}.showcase-code-link{text-align:right;padding:15px}#showcase-app-container{vertical-align:top}#showcase-code-tabs{margin-right:15px}#showcase-code-tabs pre{border:none;line-height:1em}#showcase-code-tabs .nav,#showcase-code-tabs ul{margin-bottom:0}#showcase-code-tabs .tab-content{border-style:solid;border-color:#e5e5e5;border-width:0px 1px 1px 1px;overflow:auto;border-bottom-right-radius:4px;border-bottom-left-radius:4px}#showcase-app-code{width:100%}#showcase-code-position-toggle{float:right}#showcase-sxs-code{padding-top:20px;vertical-align:top}.showcase-code-license{display:block;text-align:right}#showcase-code-content pre{background-color:#fff}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1,3 +1,3 @@
/*! shiny 1.9.1 | (c) 2012-2024 RStudio, PBC. | License: GPL-3 | file LICENSE */
"use strict";(function(){var a=eval;window.addEventListener("message",function(i){var e=i.data;e.code&&a(e.code)});})();
/*! shiny 1.10.0.9000 | (c) 2012-2025 Posit Software, PBC. | License: GPL-3 | file LICENSE */
"use strict";(()=>{var t=eval;window.addEventListener("message",function(a){let e=a.data;e.code&&t(e.code)});})();
//# sourceMappingURL=shiny-testmode.js.map

View File

@@ -1,7 +1,7 @@
{
"version": 3,
"sources": ["../../../srcts/src/utils/eval.ts", "../../../srcts/extras/shiny-testmode.ts"],
"sourcesContent": ["//esbuild.github.io/content-types/#direct-eval\n//tl/dr;\n// * Direct usage of `eval(\"x\")` is bad with bundled code.\n// * Instead, use indirect calls to `eval` such as `indirectEval(\"x\")`\n// * Even just renaming the function works well enough.\n// > This is known as \"indirect eval\" because eval is not being called directly, and so does not trigger the grammatical special case for direct eval in the JavaScript VM. You can call indirect eval using any syntax at all except for an expression of the exact form eval('x'). For example, var eval2 = eval; eval2('x') and [eval][0]('x') and window.eval('x') are all indirect eval calls.\n// > When you use indirect eval, the code is evaluated in the global scope instead of in the inline scope of the caller.\n\nvar indirectEval = eval;\nexport { indirectEval };", "/* eslint-disable unicorn/filename-case */\nimport { indirectEval } from \"../src/utils/eval\";\n\n// Listen for messages from parent frame. This file is only added when the\n// shiny.testmode option is TRUE.\nwindow.addEventListener(\"message\", function (e) {\n var message = e.data;\n if (message.code) indirectEval(message.code);\n});"],
"mappings": ";yBAQA,IAAIA,EAAe,KCHnB,OAAO,iBAAiB,UAAW,SAAUC,EAAG,CAC9C,IAAIC,EAAUD,EAAE,KACZC,EAAQ,MAAMC,EAAaD,EAAQ,IAAI,CAC7C,CAAC",
"sourcesContent": ["//esbuild.github.io/content-types/#direct-eval\n//tl/dr;\n// * Direct usage of `eval(\"x\")` is bad with bundled code.\n// * Instead, use indirect calls to `eval` such as `indirectEval(\"x\")`\n// * Even just renaming the function works well enough.\n// > This is known as \"indirect eval\" because eval is not being called directly, and so does not trigger the grammatical special case for direct eval in the JavaScript VM. You can call indirect eval using any syntax at all except for an expression of the exact form eval('x'). For example, var eval2 = eval; eval2('x') and [eval][0]('x') and window.eval('x') are all indirect eval calls.\n// > When you use indirect eval, the code is evaluated in the global scope instead of in the inline scope of the caller.\n\nconst indirectEval = eval;\n\nexport { indirectEval };\n", "/* eslint-disable unicorn/filename-case */\nimport { indirectEval } from \"../src/utils/eval\";\n\n// Listen for messages from parent frame. This file is only added when the\n// shiny.testmode option is TRUE.\nwindow.addEventListener(\"message\", function (e: { data: { code: string } }) {\n const message = e.data;\n\n if (message.code) indirectEval(message.code);\n});\n"],
"mappings": ";mBAQA,IAAMA,EAAe,KCHrB,OAAO,iBAAiB,UAAW,SAAUC,EAA+B,CAC1E,IAAMC,EAAUD,EAAE,KAEdC,EAAQ,MAAMC,EAAaD,EAAQ,IAAI,CAC7C,CAAC",
"names": ["indirectEval", "e", "message", "indirectEval"]
}

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -384,6 +384,14 @@ html.autoreload-enabled #shiny-disconnected-overlay.reloading {
width: 100%;
}
/* Styling for inputTextArea(autoresize=TRUE) */
.textarea-autoresize textarea.form-control {
padding: 5px 8px;
resize: none;
overflow-y: hidden;
height: auto;
}
#shiny-notification-panel {
position: fixed;

View File

@@ -45,6 +45,56 @@ is, a function that quickly returns a promise) and allows even that very
session to immediately unblock and carry on with other user interactions.
}
\examples{
\dontshow{if (rlang::is_interactive() && rlang::is_installed("future")) (if (getRversion() >= "3.4") withAutoprint else force)(\{ # examplesIf}
library(shiny)
library(bslib)
library(future)
plan(multisession)
ui <- page_fluid(
titlePanel("Extended Task Demo"),
p(
'Click the button below to perform a "calculation"',
"that takes a while to perform."
),
input_task_button("recalculate", "Recalculate"),
p(textOutput("result"))
)
server <- function(input, output) {
rand_task <- ExtendedTask$new(function() {
future(
{
# Slow operation goes here
Sys.sleep(2)
sample(1:100, 1)
},
seed = TRUE
)
})
# Make button state reflect task.
# If using R >=4.1, you can do this instead:
# rand_task <- ExtendedTask$new(...) |> bind_task_button("recalculate")
bind_task_button(rand_task, "recalculate")
observeEvent(input$recalculate, {
# Invoke the extended in an observer
rand_task$invoke()
})
output$result <- renderText({
# React to updated results when the task completes
number <- rand_task$result()
paste0("Your number is ", number, ".")
})
}
shinyApp(ui, server)
\dontshow{\}) # examplesIf}
}
\section{Methods}{
\subsection{Public methods}{
\itemize{

View File

@@ -182,7 +182,7 @@ If you want to use a cache that is shared across multiple R processes, you
can use a \code{\link[cachem:cache_disk]{cachem::cache_disk()}}. You can create a application-level shared
cache by putting this at the top of your app.R, server.R, or global.R:
\if{html}{\out{<div class="sourceCode">}}\preformatted{shinyOptions(cache = cachem::cache_disk(file.path(dirname(tempdir()), "myapp-cache"))
\if{html}{\out{<div class="sourceCode">}}\preformatted{shinyOptions(cache = cachem::cache_disk(file.path(dirname(tempdir()), "myapp-cache")))
}\if{html}{\out{</div>}}
This will create a subdirectory in your system temp directory named

View File

@@ -60,4 +60,14 @@ server <- function(input, output) {
shinyApp(ui, server)
}
}
\seealso{
\itemize{
\item The download handler, like other outputs, is suspended (disabled) by
default for download buttons and links that are hidden. Use
\code{\link[=outputOptions]{outputOptions()}} to control this behavior, e.g. to set
\code{suspendWhenHidden = FALSE} if the download is initiated by
programmatically clicking on the download button using JavaScript.
}
}

View File

@@ -11,7 +11,9 @@ numericInput(
min = NA,
max = NA,
step = NA,
width = NULL
width = NULL,
...,
updateOn = c("change", "blur")
)
}
\arguments{
@@ -29,6 +31,16 @@ numericInput(
\item{width}{The width of the input, e.g. \code{'400px'}, or \code{'100\%'};
see \code{\link[=validateCssUnit]{validateCssUnit()}}.}
\item{...}{Ignored, included to require named arguments and for future
feature expansion.}
\item{updateOn}{A character vector specifying when the input should be
updated. Options are \code{"change"} (default) and \code{"blur"}. Use \code{"change"} to
update the input immediately whenever the value changes. Use \code{"blur"}to
delay the input update until the input loses focus (the user moves away
from the input), or when Enter is pressed (or Cmd/Ctrl + Enter for
\code{\link[=textAreaInput]{textAreaInput()}}).}
}
\value{
A numeric input control that can be added to a UI definition.

View File

@@ -4,7 +4,15 @@
\alias{passwordInput}
\title{Create a password input control}
\usage{
passwordInput(inputId, label, value = "", width = NULL, placeholder = NULL)
passwordInput(
inputId,
label,
value = "",
width = NULL,
placeholder = NULL,
...,
updateOn = c("change", "blur")
)
}
\arguments{
\item{inputId}{The \code{input} slot that will be used to access the value.}
@@ -19,6 +27,16 @@ see \code{\link[=validateCssUnit]{validateCssUnit()}}.}
\item{placeholder}{A character string giving the user a hint as to what can
be entered into the control. Internet Explorer 8 and 9 do not support this
option.}
\item{...}{Ignored, included to require named arguments and for future
feature expansion.}
\item{updateOn}{A character vector specifying when the input should be
updated. Options are \code{"change"} (default) and \code{"blur"}. Use \code{"change"} to
update the input immediately whenever the value changes. Use \code{"blur"}to
delay the input update until the input loses focus (the user moves away
from the input), or when Enter is pressed (or Cmd/Ctrl + Enter for
\code{\link[=textAreaInput]{textAreaInput()}}).}
}
\value{
A text input control that can be added to a UI definition.

View File

@@ -125,6 +125,9 @@ shinyApp(ui, server)
}
}
\seealso{
For more details on how the images are generated, and how to control
\itemize{
\item For more details on how the images are generated, and how to control
the output, see \code{\link[=plotPNG]{plotPNG()}}.
\item Use \code{\link[=outputOptions]{outputOptions()}} to set general output options for an image output.
}
}

View File

@@ -126,3 +126,6 @@ vecFun()
})
}
\seealso{
\code{\link[=outputOptions]{outputOptions()}}
}

View File

@@ -52,5 +52,5 @@ shinyApp(ui, server)
}
\seealso{
\code{\link[=uiOutput]{uiOutput()}}
\code{\link[=uiOutput]{uiOutput()}}, \code{\link[=outputOptions]{outputOptions()}}
}

View File

@@ -33,7 +33,9 @@ interactive sessions only.}
to the \code{shiny.host} option, if set, or \code{"127.0.0.1"} if not.}
\item{display.mode}{The mode in which to display the example. Defaults to
\code{showcase}, but may be set to \code{normal} to see the example without
\code{"auto"}, which uses the value of \code{DisplayMode} in the example's
\code{DESCRIPTION} file. Set to \code{"showcase"} to show the app code and
description with the running app, or \code{"normal"} to see the example without
code or commentary.}
\item{package}{The package in which to find the example (defaults to

View File

@@ -44,16 +44,20 @@ have the extensions: r, htm, html, js, css, png, jpg, jpeg, gif. If any
changes are detected, all connected Shiny sessions are reloaded. This
allows for fast feedback loops when tweaking Shiny UI.
Since monitoring for changes is expensive (we simply poll for last
modified times), this feature is intended only for development.
Monitoring for changes is no longer expensive, thanks to the \pkg{watcher}
package, but this feature is still intended only for development.
You can customize the file patterns Shiny will monitor by setting the
shiny.autoreload.pattern option. For example, to monitor only ui.R:
\code{options(shiny.autoreload.pattern = glob2rx("ui.R"))}
shiny.autoreload.pattern option. For example, to monitor only \code{ui.R}:
\code{options(shiny.autoreload.pattern = glob2rx("ui.R"))}.
The default polling interval is 500 milliseconds. You can change this
by setting e.g. \code{options(shiny.autoreload.interval = 2000)} (every
two seconds).}
As mentioned above, Shiny no longer polls watched files for changes.
Instead, using \pkg{watcher}, Shiny is notified of file changes as they
occur. These changes are batched together within a customizable latency
period. You can adjust this period by setting
\code{options(shiny.autoreload.interval = 2000)} (in milliseconds). This value
converted to seconds and passed to the \code{latency} argument of
\code{\link[watcher:watcher]{watcher::watcher()}}. The default latency is 250ms.}
\item{shiny.deprecation.messages (defaults to \code{TRUE})}{This controls whether messages for
deprecated functions in Shiny will be printed. See
\code{\link[=shinyDeprecated]{shinyDeprecated()}} for more information.}

View File

@@ -13,7 +13,10 @@ textAreaInput(
cols = NULL,
rows = NULL,
placeholder = NULL,
resize = NULL
resize = NULL,
...,
autoresize = FALSE,
updateOn = c("change", "blur")
)
}
\arguments{
@@ -46,6 +49,19 @@ option.}
\item{resize}{Which directions the textarea box can be resized. Can be one of
\code{"both"}, \code{"none"}, \code{"vertical"}, and \code{"horizontal"}. The default, \code{NULL},
will use the client browser's default setting for resizing textareas.}
\item{...}{Ignored, included to require named arguments and for future
feature expansion.}
\item{autoresize}{If \code{TRUE}, the textarea will automatically resize to fit
the input text.}
\item{updateOn}{A character vector specifying when the input should be
updated. Options are \code{"change"} (default) and \code{"blur"}. Use \code{"change"} to
update the input immediately whenever the value changes. Use \code{"blur"}to
delay the input update until the input loses focus (the user moves away
from the input), or when Enter is pressed (or Cmd/Ctrl + Enter for
\code{\link[=textAreaInput]{textAreaInput()}}).}
}
\value{
A textarea input control that can be added to a UI definition.

View File

@@ -4,7 +4,15 @@
\alias{textInput}
\title{Create a text input control}
\usage{
textInput(inputId, label, value = "", width = NULL, placeholder = NULL)
textInput(
inputId,
label,
value = "",
width = NULL,
placeholder = NULL,
...,
updateOn = c("change", "blur")
)
}
\arguments{
\item{inputId}{The \code{input} slot that will be used to access the value.}
@@ -19,6 +27,16 @@ see \code{\link[=validateCssUnit]{validateCssUnit()}}.}
\item{placeholder}{A character string giving the user a hint as to what can
be entered into the control. Internet Explorer 8 and 9 do not support this
option.}
\item{...}{Ignored, included to require named arguments and for future
feature expansion.}
\item{updateOn}{A character vector specifying when the input should be
updated. Options are \code{"change"} (default) and \code{"blur"}. Use \code{"change"} to
update the input immediately whenever the value changes. Use \code{"blur"}to
delay the input update until the input loses focus (the user moves away
from the input), or when Enter is pressed (or Cmd/Ctrl + Enter for
\code{\link[=textAreaInput]{textAreaInput()}}).}
}
\value{
A text input control that can be added to a UI definition.

View File

@@ -3,7 +3,7 @@
"homepage": "https://shiny.rstudio.com",
"repository": "github:rstudio/shiny",
"name": "@types/rstudio-shiny",
"version": "1.9.1",
"version": "1.10.0-alpha.9000",
"license": "GPL-3.0-only",
"main": "",
"browser": "",
@@ -28,11 +28,6 @@
"lit": "^3.0.0"
},
"devDependencies": {
"@babel/core": "^7.14.3",
"@babel/plugin-proposal-class-properties": "^7.13.0",
"@babel/preset-env": "^7.14.2",
"@babel/preset-typescript": "^7.13.0",
"@babel/runtime": "^7.14.0",
"@deanc/esbuild-plugin-postcss": "^1.0.2",
"@selectize/selectize": "https://github.com/selectize/selectize.js.git#e3f2e0b4aa251375bc21b5fcd8ca7d374a921f08",
"@testing-library/dom": "^7.31.0",
@@ -51,7 +46,6 @@
"caniuse-lite": "^1.0.30001312",
"core-js": "^3.13.0",
"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": "^1.0.1",
"eslint": "^8.24.0",

View File

@@ -1,43 +1,34 @@
# Revdeps
## Failed to check (29)
## Failed to check (20)
|package |version |error |warning |note |
|:---------------|:-------|:-----|:-------|:----|
|alevinQC |? | | | |
|animalEKF |1.2 |1 | | |
|animaltracker |? | | | |
|antaresViz |? | | | |
|AovBay |0.1.0 |1 | | |
|bdclean |? | | | |
|chipPCR |1.0-2 |1 | | |
|ctsem |3.10.0 |1 | | |
|diveR |? | | | |
|DynNom |5.1 |1 | | |
|hydflood |? | | | |
|loon.shiny |? | | | |
|modchart |? | | | |
|MODIStsp |? | | | |
|protGear |? | | | |
|robmedExtra |0.1.0 |1 | | |
|RQuantLib |0.4.23 |1 | | |
|rstanarm |2.32.1 |1 | | |
|scPipe |? | | | |
|sen2r |? | | | |
|SensMap |0.7 |1 | | |
|Seurat |5.1.0 |1 | | |
|shinyTempSignal |0.0.8 |1 | | |
|Signac |1.13.0 |1 | | |
|simplevis |? | | | |
|statsr |0.3.0 |1 | | |
|teachingApps |? | | | |
|tidyvpc |1.5.1 |1 | | |
|TVTB |? | | | |
|package |version |error |warning |note |
|:------------------|:-------|:-----|:-------|:----|
|animalEKF |1.2 |1 | | |
|AovBay |0.1.0 |1 | | |
|Certara.VPCResults |3.0.2 |1 | | |
|chipPCR |1.0-2 |1 | | |
|ctsem |3.10.1 |1 | | |
|dartR.sim |? | | | |
|diveR |? | | | |
|gap |? | | | |
|jsmodule |? | | | |
|loon.shiny |? | | | |
|robmedExtra |0.1.1 |1 | | |
|rstanarm |2.32.1 |1 | | |
|SensMap |0.7 |1 | | |
|Seurat |5.1.0 |1 | |1 |
|shinyTempSignal |0.0.8 |1 | | |
|Signac |1.14.0 |1 | | |
|statsr |0.3.0 |1 | | |
|TestAnaAPP |1.1.2 |1 | | |
|tidyvpc |1.5.2 |1 | | |
|visR |? | | | |
## New problems (2)
|package |version |error |warning |note |
|:-------|:-------|:--------|:-------|:------|
|[EgoCor](problems.md#egocor)|1.2.0 |1 __+1__ | |-1 |
|[mxfda](problems.md#mxfda)|0.2.1 | | |__+1__ |
|package |version |error |warning |note |
|:-------|:-------|:-----|:-------|:------|
|[HH](problems.md#hh)|3.1-52 | | |__+1__ |
|[PopED](problems.md#poped)|0.7.0 | | |__+1__ |

View File

@@ -1,36 +1,39 @@
## revdepcheck results
We checked 1221 reverse dependencies (1208 from CRAN + 13 from Bioconductor), comparing R CMD check results across CRAN and dev versions of this package.
We checked 1278 reverse dependencies (1277 from CRAN + 1 from Bioconductor), comparing R CMD check results across CRAN and dev versions of this package.
* We saw 2 new problems
* We failed to check 16 packages
* We failed to check 19 packages
Issues with CRAN packages are summarised below.
### New problems
(This reports the first line of each new failure)
* EgoCor
checking running R code from vignettes ... ERROR
* HH
checking installed package size ... NOTE
* mxfda
* PopED
checking installed package size ... NOTE
### Failed to check
* animalEKF (NA)
* AovBay (NA)
* chipPCR (NA)
* ctsem (NA)
* diveR (NA)
* DynNom (NA)
* loon.shiny (NA)
* robmedExtra (NA)
* RQuantLib (NA)
* rstanarm (NA)
* SensMap (NA)
* Seurat (NA)
* shinyTempSignal (NA)
* Signac (NA)
* statsr (NA)
* tidyvpc (NA)
* animalEKF (NA)
* AovBay (NA)
* Certara.VPCResults (NA)
* chipPCR (NA)
* ctsem (NA)
* dartR.sim (NA)
* diveR (NA)
* gap (NA)
* jsmodule (NA)
* loon.shiny (NA)
* robmedExtra (NA)
* rstanarm (NA)
* SensMap (NA)
* Seurat (NA)
* shinyTempSignal (NA)
* Signac (NA)
* statsr (NA)
* TestAnaAPP (NA)
* tidyvpc (NA)

View File

@@ -1,84 +1,14 @@
# EgoCor
# HH
<details>
* Version: 1.2.0
* GitHub: https://github.com/julia-dyck/EgoCor
* Source code: https://github.com/cran/EgoCor
* Date/Publication: 2024-03-28 18:10:02 UTC
* Number of recursive dependencies: 74
* Version: 3.1-52
* GitHub: NA
* Source code: https://github.com/cran/HH
* Date/Publication: 2024-02-11 00:00:02 UTC
* Number of recursive dependencies: 165
Run `revdepcheck::cloud_details(, "EgoCor")` for more info
</details>
## Newly broken
* checking running R code from vignettes ... ERROR
```
Errors in running code in vignettes:
when running code in Intro_to_EgoCor.Rmd
...
Warning in gstat::fit.variogram(emp.sv, model = v, fit.sills = TRUE, fit.ranges = TRUE, :
linear model has singular covariance matrix
Warning in gstat::fit.variogram(emp.sv, model = v, fit.sills = TRUE, fit.ranges = TRUE, :
linear model has singular covariance matrix
Warning in gstat::fit.variogram(emp.sv, model = v, fit.sills = TRUE, fit.ranges = TRUE, :
No convergence after 200 iterations: try different initial values?
When sourcing Intro_to_EgoCor.R:
Error: missing value where TRUE/FALSE needed
Execution halted
Intro_to_EgoCor.Rmd using UTF-8... failed
```
## Newly fixed
* checking re-building of vignette outputs ... NOTE
```
Error(s) in re-building vignettes:
--- re-building Intro_to_EgoCor.Rmd using rmarkdown
```
## In both
* checking examples ... ERROR
```
Running examples in EgoCor-Ex.R failed
The error most likely occurred in:
> ### Name: vario.reg.prep
> ### Title: Adjustment for covariates before semi-variogram model fitting
> ### Aliases: vario.reg.prep
>
> ### ** Examples
>
> ## Example 1
...
+ }
Warning in check_dep_version() :
ABI version mismatch:
lme4 was built with Matrix ABI version 1
Current Matrix ABI version is 0
Please re-install lme4 from source or restore original Matrix package
Error in initializePtr() :
function 'cholmod_factor_ldetA' not provided by package 'Matrix'
Calls: <Anonymous> ... initialize -> <Anonymous> -> initializePtr -> .Call
Execution halted
```
# mxfda
<details>
* Version: 0.2.1
* GitHub: https://github.com/julia-wrobel/mxfda
* Source code: https://github.com/cran/mxfda
* Date/Publication: 2024-05-08 11:00:02 UTC
* Number of recursive dependencies: 220
Run `revdepcheck::cloud_details(, "mxfda")` for more info
Run `revdepcheck::cloud_details(, "HH")` for more info
</details>
@@ -86,8 +16,34 @@ Run `revdepcheck::cloud_details(, "mxfda")` for more info
* checking installed package size ... NOTE
```
installed size is 5.6Mb
installed size is 5.1Mb
sub-directories of 1Mb or more:
data 4.0Mb
R 1.5Mb
help 1.5Mb
```
# PopED
<details>
* Version: 0.7.0
* GitHub: https://github.com/andrewhooker/PopED
* Source code: https://github.com/cran/PopED
* Date/Publication: 2024-10-07 19:30:02 UTC
* Number of recursive dependencies: 140
Run `revdepcheck::cloud_details(, "PopED")` for more info
</details>
## Newly broken
* checking installed package size ... NOTE
```
installed size is 5.5Mb
sub-directories of 1Mb or more:
R 1.5Mb
doc 1.4Mb
test 1.1Mb
```

View File

@@ -150,12 +150,6 @@ All config files are located in the root folder to avoid opening two separate VS
* Used by `prettier` to know how to adjust code when a file is saved in VSCode or within `eslint`'s linting process.
* `yarnrc.yml`
* Notifies `yarn` to use `yarn` v2, install `./node_modules` folder for `esbuild`, and any CLI plugins.
* `babel.config.json`
* Used within `babel` transpilation of TypeScript -> JavaScript -> polyfilled JavaScript.
* Noteable options set:
* `"useBuiltIns": "usage"` - `core-js` polyfills are only added as they are _used_.
* `"corejs": "3.9"` - This number should match the installed `core-js` number.
* `"ignore":["node_modules/core-js"]` - The `core-js` library is directly ignored to [avoid being processed by `babel`](https://github.com/zloirock/core-js/issues/743#issuecomment-571983318).
* `jest.config.js`
* Used to configure [`jest` testing](https://jestjs.io/)
* `package.json`
@@ -168,7 +162,7 @@ All config files are located in the root folder to avoid opening two separate VS
* `tsconfig.json` -
* TypeScript config file
* Notable options set:
* `target: ES5` - Compile to es5, so babel has an easier job.
* `target: ES2021` - Compile to es2021.
* `preserveConstEnums: false` - Do no preserve enum values into the final code. (If true, produces bloat / unused code)
* `isolatedModules: true` & `esModuleInterop: true` - Requested by `esbuild`. This [allows for `esbuild`](https://esbuild.github.io/content-types/#typescript) to safely compile the files in parallel

View File

@@ -1,108 +0,0 @@
# Development Rules
* Put Imports at top
* Put Exports at bottom
* File / Folder structure
* Lean towards using more many files / folder vs larger files
* Nest folders inside larger _ideas_
* Each folder should generally have an `**/index.ts` to export most everything for the folder
* Exception: `./src/window` folder. Must call methods directly.
* Any `window.***` calls are done in `./src/window` folder only.
* Add exported values / function if necessary.
* This helps keep each file self contained. Trying not to have random inputs from anywhere
* jQuery
* Always import `./src/jquery` instead of `"jquery"`
* Exeption: Test files. There, you can import `"jquery"` directly as the browser is not available to already have jQuery loaded.
* Prevents from installing local jquery in addition to global jquery
* WAY to many packages exist on the assumption that jquery is available at run time. Therefore, it can not be removed globally. :-(
* Anything that needs to be initialized on start **must** exist in the `./src/initialize` folder.
* No file should produce any side effects.
* To capture initializations, export a `setFoo(foo_)` method that updates a locally defined `foo` variable.
# TODO
* √ Move everything into a single ts file. This will allow for the functions to find themselves
* √ Except the utils.js already converted
* √ es6 shiny.js
* √ Pass in version using esbuild
* √ Move all shiny files in order to main.ts
* √ validate polyfills are working by finding them in the code
* √ Produce minified shiny js
* √ Disable $ from being found without an import
* √ Using a patch with yarn v2
* √ Document `./package.json` scripts
* √ Verify that `babel` is configurable
* √ Use targeting browsers
* √ Verify it works on phantomjs / shinytest
* √ Set up initial jest tests
* √ Use a global shim to avoid importing jquery directly, but make testing easy to test
* Update the /tools/update*.R scripts to produce a version and install node dependencies
* √ jquery
* √ ion range slider
* √ selectize
* √ strftime
* √ bootstrap date picker
* font awesome?
* bootstrap accessibility plugin?
# Round #2
* Convert registered bindings
* √ Input bindings
* √ Output bindings
* Add default value to `subscribe(callback)` callback function of `false`. B/c if the value was not provided, it was not truthy, therefore equivalent to `false`.
* √ radio
* √ checkboxgroup
* √ daterange
* √ actionbutton
* √ bootstraptabinput
* √ snake_case to camelCase conversions.
* √ globally import strftime from `window.strftime`
* Remove `evt` from jQuery.on callbacks where `evt` was not used.
* √ checkbox.subscribe
* √ checkboxgroup.subscribe
* √ radio.subscribe
* √ slider.subscribe
* √ date.subscribe
* √ selectInput.subscribe
* √ actionButton.subscribe
* √ bootstraptabinput.subscribe
* Convert usage of `+x` to `Number(x)`
* https://stackoverflow.com/a/15872631/591574
* √ slider.getValue()
* √ number.getValue()
* √ Adjust tabinput.ts `setValue()` to return either `false | void`, not `false | true`.
* What matters is that `false` is returned, or nothing is returned. Replaced `return true;` with `return;`
* Questions
* Why does `receiveMessage(data)` sometimes have a `label`?
* Should we have a update datatables script?
# Later TODO
* Use --strictNullChecks in tsconfig.json
* Make `_*()` methods `private *()`
* √ Each _file_ will be pulled out as possible into smaller files in separate PRs
* √ Convert `FileProcessor` to a true class definition
* Break up `./utils` into many files
* √ Remove any `: any` types
* √ Make `@typescript-eslint/explicit-module-boundary-types` an error
* √ Fix all `// eslint-disable-next-line no-prototype-builtins` lines
* TypeScript other shiny files (ex: showcasemode)
* √ Completely remove `parcel` from `./package.json` and only use `esbuild`
* √ Delete 'shiny-es5' files
* Delete 'old' folder
* _Uglify_ js files (like in previous Gruntfile.js)
* datepicker
* ionrangeslider
* selectize
# Eventual TODO
* Use yarn PnP
* See `./patch/yarn_pnp.patch`
* Use [esbuild](https://github.com/yarnpkg/berry/tree/master/packages/esbuild-plugin-pnp#yarnpkgesbuild-plugin-pnp)
* Known problems:
* `@yarnpkg/esbuild-plugin-pnp@0.0.1` gives full file paths, not relative file paths
* `@testing-library/jest-dom/extend-expect` can not be found.

View File

@@ -7,17 +7,13 @@ import type {
} from "esbuild";
import { build as esbuildBuild } from "esbuild";
import process from "process";
import { basename } from "path";
import process from "process";
// 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 };
@@ -25,7 +21,7 @@ const shinyDesc = readcontrol.readSync("./DESCRIPTION") as ShinyDesc;
const bannerTxt = [
`/*! ${shinyDesc.package} ${shinyDesc.version}`,
`(c) 2012-${new Date().getFullYear()} RStudio, PBC.`,
`(c) 2012-${new Date().getFullYear()} Posit Software, PBC.`,
`License: ${shinyDesc.license} */`,
].join(" | ");
const banner = {
@@ -79,7 +75,7 @@ async function build(
return esbuildBuild({
incremental: incremental,
watch: watch,
target: "es5",
target: "es2021",
preserveSymlinks: true,
...opts,
}).then((x) => {
@@ -88,4 +84,4 @@ async function build(
});
}
export { outDir, build, shinyDesc, banner, babelPlugin };
export { outDir, build, shinyDesc, banner };

View File

@@ -5,13 +5,13 @@
// - TypeScript -----------------------------------------------------------
import { banner, build, outDir, babelPlugin } from "./_build";
import { banner, build, outDir } from "./_build";
build({
bundle: true,
sourcemap: true,
minify: true,
plugins: [babelPlugin()],
plugins: [],
banner: banner,
entryPoints: [
"srcts/extras/shiny-autoreload.ts",

View File

@@ -3,9 +3,9 @@
// yarn build
// ```
import { banner, build, outDir, shinyDesc, babelPlugin } from "./_build";
import globalsPlugin from "esbuild-plugin-globals";
import type { BuildOptions } from "esbuild";
import globalsPlugin from "esbuild-plugin-globals";
import { banner, build, outDir, shinyDesc } from "./_build";
import { verifyJqueryImport } from "./_jquery";
const opts: BuildOptions = {
@@ -18,7 +18,6 @@ const opts: BuildOptions = {
//// Loaded dynamically. MUST use `window.strftime` within code
// strftime: "window.strftime",
}),
babelPlugin(),
],
define: {
// eslint-disable-next-line @typescript-eslint/naming-convention

View File

@@ -7,6 +7,8 @@
.recalculating {
min-height: var(--shiny-spinner-size, 32px);
&::after {
position: absolute;
content: "";
@@ -45,13 +47,31 @@
transition: opacity 250ms ease var(--shiny-spinner-delay, 1s);
}
/*
When htmlwidget errors are rendered, an inline `visibility:hidden` is put
on the html-widget-output, and the error message (if any) is put in a
sibling element that overlays the output container (this way, the height
of the output container doesn't change). Work around this by making the
output container itself visible and making the children (except the
spinner) invisible.
*/
&.html-widget-output {
visibility: inherit !important;
> * {
visibility: hidden;
}
::after {
visibility: visible;
}
}
/*
Disable spinner on uiOutput() mainly because (for other reasons) it has
`display:contents`, which breaks the ::after positioning.
Note that, even if we could position it, we'd probably want to disable it
if it has recalculating children.
*/
&.shiny-html-output::after {
&.shiny-html-output:not(.shiny-table-output)::after {
display: none;
}
}
@@ -64,15 +84,15 @@
--shiny-pulse-background,
linear-gradient(
120deg,
var(--bs-body-bg, #fff),
transparent,
var(--bs-indigo, #4b00c1),
var(--bs-purple, #74149c),
var(--bs-pink, #bf007f),
var(--bs-body-bg, #fff)
transparent
)
);
--_shiny-pulse-height: var(--shiny-pulse-height, 5px);
--_shiny-pulse-speed: var(--shiny-pulse-speed, 1.85s);
--_shiny-pulse-height: var(--shiny-pulse-height, 3px);
--_shiny-pulse-speed: var(--shiny-pulse-speed, 1.2s);
/* Color, sizing, & positioning */
position: fixed;
@@ -85,6 +105,7 @@
/* Animation */
animation-name: busy-page-pulse;
animation-duration: var(--_shiny-pulse-speed);
animation-direction: alternate;
animation-iteration-count: infinite;
animation-timing-function: ease-in-out;
@@ -99,7 +120,12 @@
&.shiny-busy::after {
@include shiny-page-busy;
}
&.shiny-busy:has(.recalculating)::after {
// Hide the pulse if there are spinners on the page
// (Note: UI outputs don't get spinners)
&.shiny-busy:has(.recalculating:not(.shiny-html-output))::after {
display: none;
}
&.shiny-busy:has(.recalculating.shiny-table-output)::after {
display: none;
}
&.shiny-busy:has(#shiny-disconnected-overlay)::after {
@@ -130,17 +156,23 @@
/* Keyframes for the pulsing banner */
@keyframes busy-page-pulse {
0% {
left: -75%;
width: 75%;
left: -14%;
right: 97%;
}
50% {
left: 100%;
width: 75%;
45% {
left: 0%;
right: 14%;
}
/* Go back */
100% {
left: -75%;
width: 75%;
55% {
left: 14%;
right: 0%;
}
to {
left: 97%;
right: -14%;
}
}

View File

@@ -0,0 +1,6 @@
.textarea-autoresize textarea.form-control {
padding: 5px 8px;
resize: none;
overflow-y: hidden;
height: auto;
}

View File

@@ -1,9 +0,0 @@
# `yarn` Patch files
* `yarn_pnp.patch`
* This file is currently not used and is outdated.
* This provides a good game plan on how to use PnP with Yarn once esbuild can easily be integrated with Yarn PnP.
* Using PnP removes the `node_modules` folder, but adds a zip of each package. I **do not** like Yarn's suggestion to commit these zip files to support their [Zero Installs](https://next.yarnpkg.com/features/zero-installs) philosophy.
* Reference:
* https://next.yarnpkg.com/features/pnp
* https://yarnpkg.com/api/modules/esbuild_plugin_pnp.html

View File

@@ -1,250 +0,0 @@
diff --git a/srcts/TODO.md b/srcts/TODO.md
index dbe275f1..e36ced9e 100644
--- a/srcts/TODO.md
+++ b/srcts/TODO.md
@@ -36,6 +36,7 @@
* √ Verify it works on phantomjs / shinytest
* √ Set up initial jest tests
* √ Use a global shim to avoid importing jquery directly, but make testing easy to test
+* √ Use yarn PnP
# Later TODO
@@ -49,12 +50,3 @@
* Completely remove `parcel` from `./package.json` and only use `esbuild`
* Delete 'shiny-es5' files
* Delete 'old' folder
-
-
-# Eventual TODO
-* Use yarn PnP
- * Use [esbuild](https://github.com/yarnpkg/berry/tree/master/packages/esbuild-plugin-pnp#yarnpkgesbuild-plugin-pnp)
- * Remove `./.yarnrc.yaml` `nodeLinker` key
- * TODO - Figure out how to call the esbuild command with the missing packages. Currently Yarn can't ifnd `esbuild` and suggests `esbuild-X.Y.Z-SHA` (or something other than `esbuild`) which does not make sense.
- * Calling `yarn node esbuild.config.mjs` does not work
- * Calling `yarn pnpify node esbuild.config.mjs` does not work
diff --git a/srcts/esbuild.config.js b/srcts/esbuild.config.js
new file mode 100644
index 00000000..8e4a0801
--- /dev/null
+++ b/srcts/esbuild.config.js
@@ -0,0 +1,54 @@
+/* eslint-disable no-undef */
+/* eslint-disable @typescript-eslint/no-var-requires */
+
+// !! Do not convert this file to a module (aka using `import` statements) as VSCode suggests to do
+// Yarn shims the `require` function to make PnP work. It does not work on import statements
+
+const esbuild = require("esbuild");
+const babel = require("esbuild-plugin-babel");
+const readcontrol = require("readcontrol");
+const pnpPlugin = require("esbuild-plugin-pnp");
+const process = require("process");
+const globalsPlugin = require("esbuild-plugin-globals");
+
+let watch = process.argv.length >= 3 && process.argv[2] == "--watch";
+
+let outdir = "../inst/www/shared/";
+let opts = {
+ entryPoints: ["src/index.ts"],
+ bundle: true,
+ watch: watch,
+ plugins: [
+ globalsPlugin({
+ jquery: "window.jQuery",
+ }),
+ pnpPlugin(),
+ babel(),
+ ],
+ target: "es5",
+ sourcemap: true,
+ define: {
+ "process.env.SHINY_VERSION": `"${
+ readcontrol.readSync("../DESCRIPTION").version
+ }"`,
+ },
+};
+
+console.log("Building shiny.js");
+esbuild
+ .build({
+ ...opts,
+ outfile: outdir + "shiny.js",
+ })
+ .then(() => {
+ console.log("Building shiny.min.js");
+ esbuild.build({
+ ...opts,
+ outfile: outdir + "shiny.min.js",
+ minify: true,
+ });
+ })
+ .catch((reason) => {
+ console.error(reason);
+ process.exit(1);
+ });
diff --git a/srcts/esbuild.config.mjs b/srcts/esbuild.config.mjs
deleted file mode 100644
index ffdb855b..00000000
--- a/srcts/esbuild.config.mjs
+++ /dev/null
@@ -1,40 +0,0 @@
-import esbuild from "esbuild";
-import babel from "esbuild-plugin-babel";
-import readcontrol from "readcontrol";
-import process from "process";
-import globalsPlugin from "esbuild-plugin-globals";
-
-let watch = process.argv.length >= 3 && process.argv[2] == "--watch";
-
-let outdir = "../inst/www/shared/";
-let opts = {
- entryPoints: ["src/index.ts"],
- bundle: true,
- watch: watch,
- plugins: [
- globalsPlugin({
- jquery: "window.jQuery",
- }),
- babel(),
- ],
- target: "es5",
- sourcemap: true,
- define: {
- "process.env.SHINY_VERSION": `"${
- readcontrol.readSync("../DESCRIPTION").version
- }"`,
- },
-};
-
-console.log("Building shiny.js");
-await esbuild.build({
- ...opts,
- outfile: outdir + "shiny.js",
-});
-
-console.log("Building shiny.min.js");
-await esbuild.build({
- ...opts,
- outfile: outdir + "shiny.min.js",
- minify: true,
-});
diff --git a/srcts/package.json b/srcts/package.json
index c7f6b66b..edff5ce9 100644
--- a/srcts/package.json
+++ b/srcts/package.json
@@ -21,8 +21,9 @@
"@typescript-eslint/parser": "^4",
"browserslist": "^4.16.3",
"esbuild": "^0.8.50",
- "esbuild-plugin-babel": "0.2.3",
+ "esbuild-plugin-babel": "patch:esbuild-plugin-babel@0.2.3#./patch/esbuild-plugin-babel.patch",
"esbuild-plugin-globals": "^0.1.1",
+ "esbuild-plugin-pnp": "^0.3.0",
"eslint": "^7",
"eslint-config-prettier": "^7",
"eslint-plugin-jest": "^24",
@@ -45,7 +46,7 @@
"build": "yarn run build_shiny",
"setup_build_shiny": "yarn run lint && yarn run typescript-check",
"build_shiny": "yarn run setup_build_shiny && yarn run bundle_shiny",
- "bundle_shiny": "node esbuild.config.mjs",
+ "bundle_shiny": "node esbuild.config.js",
"bundle_shiny_parcel2": "parcel build -d ../inst/www/shared --no-minify -o shiny.js src/index.ts",
"watch_parcel2": "yarn run setup_build_shiny && parcel run -d ../inst/www/shared -o shiny.js srcjs/index.ts",
"replace_shiny_version2": "replace --silent '\"[^\"]+\"; // @VERSION@' \"\\\"`node -e 'console.log(require(\"readcontrol\").readSync(\"../DESCRIPTION\").version)'`\\\"; // @VERSION@\" src/shiny.ts",
diff --git a/srcts/patch/esbuild-plugin-babel.patch b/srcts/patch/esbuild-plugin-babel.patch
new file mode 100644
index 00000000..24cc9425
--- /dev/null
+++ b/srcts/patch/esbuild-plugin-babel.patch
@@ -0,0 +1,33 @@
+diff --git a/package.json b/package.json
+index 6b9cdb89e2bbf0f5b5ad65adb53951d129f265ca..4e9b0e08e1c9850ff637eb6a47b5f3cfa33bbb55 100644
+--- a/package.json
++++ b/package.json
+@@ -7,7 +7,6 @@
+ "license": "ISC",
+ "exports": "./src/index.js",
+ "main": "src/index.js",
+- "type": "module",
+ "scripts": {
+ "format": "prettier --write --ignore-unknown '**/*'"
+ },
+diff --git a/src/index.js b/src/index.js
+index b3cff90a292daa6cfac0566ee73bab142ac627df..d06ecf8873a45a5ea3cda7edb3814e8034efed32 100644
+--- a/src/index.js
++++ b/src/index.js
+@@ -1,6 +1,7 @@
+-import babel from '@babel/core';
+-import fs from 'fs';
+-import path from 'path';
++const babel = require('@babel/core');
++const fs = require('fs');
++const path = require('path');
++
+
+ const pluginBabel = (options = {}) => ({
+ name: 'babel',
+@@ -41,4 +42,4 @@ const pluginBabel = (options = {}) => ({
+ }
+ });
+
+-export default pluginBabel;
++module.exports = pluginBabel;
diff --git a/srcts/yarn.lock b/srcts/yarn.lock
index 90d3fd04..1ca29eba 100644
--- a/srcts/yarn.lock
+++ b/srcts/yarn.lock
@@ -4334,7 +4334,7 @@ __metadata:
languageName: node
linkType: hard
-"esbuild-plugin-babel@npm:0.2.3":
+esbuild-plugin-babel@0.2.3:
version: 0.2.3
resolution: "esbuild-plugin-babel@npm:0.2.3"
peerDependencies:
@@ -4343,6 +4343,15 @@ __metadata:
languageName: node
linkType: hard
+"esbuild-plugin-babel@patch:esbuild-plugin-babel@0.2.3#./patch/esbuild-plugin-babel.patch::locator=root-workspace-0b6124%40workspace%3A.":
+ version: 0.2.3
+ resolution: "esbuild-plugin-babel@patch:esbuild-plugin-babel@npm%3A0.2.3#./patch/esbuild-plugin-babel.patch::version=0.2.3&hash=80b9d8&locator=root-workspace-0b6124%40workspace%3A."
+ peerDependencies:
+ "@babel/core": ^7.0.0
+ checksum: 91e0a233ed255b4798b3a1d9b2d9fbc8ea3c107561c69b31790f02c556a4687770a13d2b4c58f3dc638198bcddd5c6d7d26496a6578da730cd635dea1dd8450c
+ languageName: node
+ linkType: hard
+
"esbuild-plugin-globals@npm:^0.1.1":
version: 0.1.1
resolution: "esbuild-plugin-globals@npm:0.1.1"
@@ -4350,6 +4359,15 @@ __metadata:
languageName: node
linkType: hard
+"esbuild-plugin-pnp@npm:^0.3.0":
+ version: 0.3.0
+ resolution: "esbuild-plugin-pnp@npm:0.3.0"
+ peerDependencies:
+ esbuild: ^0.8.1
+ checksum: b80ab17bea35ab6eaf9a9adc14c52667c0a5e2c7c8c8e97194c57feb7d14b7247228745a3ad34f69447d6c5241081f38b9786948b879bf0051a479ce74450edc
+ languageName: node
+ linkType: hard
+
"esbuild@npm:^0.8.50":
version: 0.8.50
resolution: "esbuild@npm:0.8.50"
@@ -9295,8 +9313,9 @@ fsevents@^2.1.2:
browserslist: ^4.16.3
core-js: ^3.9
esbuild: ^0.8.50
- esbuild-plugin-babel: 0.2.3
+ esbuild-plugin-babel: "patch:esbuild-plugin-babel@0.2.3#./patch/esbuild-plugin-babel.patch"
esbuild-plugin-globals: ^0.1.1
+ esbuild-plugin-pnp: ^0.3.0
eslint: ^7
eslint-config-prettier: ^7
eslint-plugin-jest: ^24

View File

@@ -40,19 +40,15 @@ class DateInputBindingBase extends InputBinding {
el;
}
subscribe(el: HTMLElement, callback: (x: boolean) => void): void {
$(el).on(
"keyup.dateInputBinding input.dateInputBinding",
// event: Event
function () {
// Use normal debouncing policy when typing
callback(true);
}
);
// Don't update when in the middle of typing; listening on keyup or input
// tends to send spurious values to the server, based on unpredictable
// browser-dependant interpretation of partially-typed date strings.
$(el).on(
"changeDate.dateInputBinding change.dateInputBinding",
// event: Event
function () {
// Send immediately when clicked
// Or if typing, when enter pressed or focus lost
callback(false);
}
);

View File

@@ -161,19 +161,15 @@ class DateRangeInputBinding extends DateInputBindingBase {
this._setMax($endinput[0], $endinput.data("max-date"));
}
subscribe(el: HTMLElement, callback: (x: boolean) => void): void {
$(el).on(
"keyup.dateRangeInputBinding input.dateRangeInputBinding",
// event: Event
function () {
// Use normal debouncing policy when typing
callback(true);
}
);
// Don't update when in the middle of typing; listening on keyup or input
// tends to send spurious values to the server, based on unpredictable
// browser-dependant interpretation of partially-typed date strings.
$(el).on(
"changeDate.dateRangeInputBinding change.dateRangeInputBinding",
// event: Event
function () {
// Send immediately when clicked
// Or if typing, when enter pressed or focus lost
callback(false);
}
);

View File

@@ -1,3 +1,4 @@
import type { EventPriority } from "../../inputPolicies/inputPolicy";
import type { RatePolicyModes } from "../../inputPolicies/inputRateDecorator";
import type { BindScope } from "../../shiny/bind";
@@ -26,10 +27,16 @@ class InputBinding {
el; // unused var
}
// The callback method takes one argument, whose value is boolean. If true,
// allow deferred (debounce or throttle) sending depending on the value of
// getRatePolicy. If false, send value immediately. Default behavior is `false`
subscribe(el: HTMLElement, callback: (value: boolean) => void): void {
// Historically, the callback value could only be boolean. In this case:
// * false: send value immediately (i.e., priority = "immediate")
// * true: send value later (i.e., priority = "deferred")
// * The input rate policy is also consulted on whether to debounce or throttle
// In recent versions, the value can also be "event", meaning that the
// value should be sent regardless of whether it has changed.
subscribe(
el: HTMLElement,
callback: (value: EventPriority | boolean) => void
): void {
// empty
el; // unused var
callback; // unused var

View File

@@ -142,13 +142,13 @@ class SelectInputBinding extends InputBinding {
};
};
// Calling selectize.clear() first works around https://github.com/selectize/selectize.js/issues/2146
// As of selectize.js >= v0.13.1, .clearOptions() clears the selection,
// but does NOT remove the previously-selected options. So unless we call
// .clear() first, the current selection(s) will remain as (deselected)
// options. See #3966 #4142
selectize.clear();
selectize.clearOptions();
// If a new `selected` value is provided, also clear the current selection (otherwise it gets added as an option).
// Note: although the selectize docs suggest otherwise, as of selectize.js >v0.15.2,
// .clearOptions() no longer implicitly .clear()s (see #3967)
if (hasDefinedProperty(data, "value")) {
selectize.clear();
}
let loaded = false;
selectize.settings.load = function (query: string, callback: CallbackFn) {

View File

@@ -1,6 +1,7 @@
import $ from "jquery";
import { $escape, hasDefinedProperty, updateLabel } from "../../utils";
import type { EventPriority } from "../../inputPolicies/inputPolicy";
import { InputBinding } from "./inputBinding";
// interface TextHTMLElement extends NameValueHTMLElement {
@@ -49,22 +50,42 @@ class TextInputBindingBase extends InputBinding {
value;
}
subscribe(el: TextHTMLElement, callback: (x: boolean) => void): void {
$(el).on(
"keyup.textInputBinding input.textInputBinding",
// event: Event
function () {
callback(true);
}
);
$(el).on(
"change.textInputBinding",
// event: Event
function () {
subscribe(
el: TextHTMLElement,
callback: (x: EventPriority | boolean) => void
): void {
const $el = $(el);
const updateOn = $el.data("update-on") || "change";
if (updateOn === "change") {
$el.on(
"keyup.textInputBinding input.textInputBinding",
// event: Event
function () {
callback(true);
}
);
} else if (updateOn === "blur") {
$el.on("blur.textInputBinding", function () {
callback(false);
});
$el.on("keydown.textInputBinding", function (event: JQuery.Event) {
if (event.key !== "Enter") return;
if ($el.is("textarea")) {
if (!(event.ctrlKey || event.metaKey)) return;
}
callback(false);
});
}
$el.on("change.textInputBinding", function () {
if (updateOn === "blur" && $el.is(":focus")) {
return;
}
);
callback(false);
});
}
unsubscribe(el: TextHTMLElement): void {
$(el).off(".textInputBinding");
}

View File

@@ -2,11 +2,96 @@ import $ from "jquery";
import { TextInputBinding } from "./text";
class TextareaInputBinding extends TextInputBinding {
export class TextareaInputBinding extends TextInputBinding {
find(scope: HTMLElement): JQuery<HTMLElement> {
// Inputs now also have the .shiny-input-textarea class
return $(scope).find("textarea");
}
}
export { TextareaInputBinding };
/*************************************************************
* Code below this point is for textAreaInput(autoresize=TRUE)
************************************************************/
interface DOMEvent<T extends EventTarget> extends Event {
readonly target: T;
}
function onDelegatedEvent(
eventName: string,
selector: string,
callback: (target: HTMLTextAreaElement) => void
) {
document.addEventListener(eventName, (e) => {
const e2 = e as DOMEvent<HTMLTextAreaElement>;
if (e2.target.matches(selector)) {
callback(e2.target);
}
});
}
// Use a single intersectionObserver as they are slow to create / use.
let textAreaIntersectionObserver: IntersectionObserver | null = null;
function callUpdateHeightWhenTargetIsVisible(target: HTMLTextAreaElement) {
if (textAreaIntersectionObserver === null) {
// Create a single observer to watch for the textarea becoming visible
textAreaIntersectionObserver = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
// Quit if the entry is not visible
if (!entry.isIntersecting) {
return;
}
// If the entry is visible (even if it's just a single pixel)
// Stop observing the target
textAreaIntersectionObserver?.unobserve(entry.target);
// Update the height of the textarea
updateHeight(entry.target as HTMLTextAreaElement);
});
});
}
textAreaIntersectionObserver.observe(target);
}
function updateHeight(target: HTMLTextAreaElement) {
if (target.scrollHeight > 0) {
// Automatically resize the textarea to fit its content.
target.style.height = "auto";
target.style.height = target.scrollHeight + "px";
} else {
// The textarea is not visible on the page, therefore it has a 0 scroll height.
// If we should autoresize the text area height, then we can wait for the textarea to
// become visible and call `updateHeight` again. Hopefully the scroll height is no
// longer 0
callUpdateHeightWhenTargetIsVisible(target);
}
}
// Update on change
onDelegatedEvent(
"input",
"textarea.textarea-autoresize",
(target: HTMLTextAreaElement) => {
updateHeight(target);
}
);
// Update on load
function updateOnLoad() {
if (document.readyState === "loading") {
// Document still loading, wait 10ms to check again.
setTimeout(updateOnLoad, 10);
return;
}
// document.readyState in ["interactive", "complete"];\
const textAreas = document.querySelectorAll(
"textarea.textarea-autoresize"
) as NodeListOf<HTMLTextAreaElement>;
textAreas.forEach(updateHeight);
}
updateOnLoad();

View File

@@ -200,6 +200,42 @@ class ShinyErrorConsole extends LitElement {
});
}
static createClientMessageElement({ headline, message }: ShinyClientMessage) {
const msg = document.createElement("shiny-error-message");
msg.setAttribute("headline", headline || "");
msg.setAttribute("message", message);
return msg;
}
appendConsoleMessage({ headline, message }: ShinyClientMessage) {
const content =
this.shadowRoot?.querySelector<HTMLSlotElement>("slot.content");
if (content) {
const nodeKey = (node: Element) => {
const headline = node.getAttribute("headline") || "";
const message = node.getAttribute("message") || "";
return `${headline}::${message}`;
};
const newKey = `${headline}::${message}`;
for (const node of content.assignedElements()) {
if (node.tagName.toLowerCase() === "shiny-error-message") {
if (nodeKey(node) === newKey) {
// Do nothing, this message is already in the console
// TODO: Increase count of message here
return;
}
}
}
}
this.appendChild(
ShinyErrorConsole.createClientMessageElement({ headline, message })
);
return;
}
render() {
return html` <div class="header">
<span class="title"> Shiny Client Errors </span>
@@ -307,6 +343,7 @@ export class ShinyErrorMessage extends LitElement {
.error-message {
font-family: "Courier New", Courier, monospace;
white-space: pre-wrap;
}
.decoration-container {
@@ -489,6 +526,48 @@ export class ShinyErrorMessage extends LitElement {
customElements.define("shiny-error-message", ShinyErrorMessage);
export type ShinyClientMessage = {
message: string;
headline?: string;
status?: "error" | "info" | "warning";
};
function showShinyClientMessage({
headline = "",
message,
status = "warning",
}: ShinyClientMessage): void {
const consoleMessage = `[shiny] ${headline}${
headline ? " - " : ""
}${message}`;
switch (status) {
case "error":
console.error(consoleMessage);
break;
case "warning":
console.warn(consoleMessage);
break;
default:
console.log(consoleMessage);
break;
}
if (!Shiny.inDevMode()) {
return;
}
// Check to see if an Error Console Container element already exists. If it
// doesn't we need to add it before putting an error on the screen
let sec = document.querySelector<ShinyErrorConsole>("shiny-error-console");
if (!sec) {
sec = document.createElement("shiny-error-console") as ShinyErrorConsole;
document.body.appendChild(sec);
}
sec.appendConsoleMessage({ headline, message });
}
/**
* Function to show an error message to user in shiny-error-message web
* component. Only shows the error if we're in development mode.
@@ -497,11 +576,6 @@ customElements.define("shiny-error-message", ShinyErrorMessage);
* object.
*/
export function showErrorInClientConsole(e: unknown): void {
if (!Shiny.inDevMode()) {
// If we're in production, don't show the error to the user
return;
}
let errorMsg: string | null = null;
let headline = "Error on client while running Shiny app";
@@ -516,17 +590,24 @@ export function showErrorInClientConsole(e: unknown): void {
errorMsg = "Unknown error";
}
// Check to see if an Error Console Container element already exists. If it
// doesn't we need to add it before putting an error on the screen
let errorConsoleContainer = document.querySelector("shiny-error-console");
if (!errorConsoleContainer) {
errorConsoleContainer = document.createElement("shiny-error-console");
document.body.appendChild(errorConsoleContainer);
}
const errorConsole = document.createElement("shiny-error-message");
errorConsole.setAttribute("headline", headline || "");
errorConsole.setAttribute("message", errorMsg);
errorConsoleContainer.appendChild(errorConsole);
showShinyClientMessage({ headline, message: errorMsg, status: "error" });
}
export class ShinyClientMessageEvent extends CustomEvent<ShinyClientMessage> {
constructor(detail: ShinyClientMessage) {
super("shiny:client-message", { detail, bubbles: true, cancelable: true });
}
}
window.addEventListener("shiny:client-message", (ev: Event) => {
if (!(ev instanceof CustomEvent)) {
throw new Error("[shiny] shiny:client-message expected a CustomEvent");
}
const { headline, message, status } = ev.detail;
if (!message) {
throw new Error(
"[shiny] shiny:client-message expected a `message` property in `event.detail`."
);
}
showShinyClientMessage({ headline, message, status });
});

View File

@@ -3,22 +3,32 @@ import { Shiny } from "..";
import type { InputBinding, OutputBinding } from "../bindings";
import { OutputBindingAdapter } from "../bindings/outputAdapter";
import type { BindingRegistry } from "../bindings/registry";
import { ShinyClientMessageEvent } from "../components/errorConsole";
import type {
InputRateDecorator,
InputValidateDecorator,
} from "../inputPolicies";
import { ShinyClientError } from "./error";
import type { InputPolicyOpts } from "../inputPolicies/inputPolicy";
import { shinyAppBindOutput, shinyAppUnbindOutput } from "./initedMethods";
import { sendImageSizeFns } from "./sendImageSize";
type BindScope = HTMLElement | JQuery<HTMLElement>;
/**
* Type guard to check if a value is a jQuery object containing HTMLElements
* @param value The value to check
* @returns A type predicate indicating if the value is a jQuery<HTMLElement>
*/
function isJQuery<T = HTMLElement>(value: unknown): value is JQuery<T> {
return Boolean(value && (value as any).jquery);
}
// todo make sure allowDeferred can NOT be supplied and still work
function valueChangeCallback(
inputs: InputValidateDecorator,
binding: InputBinding,
el: HTMLElement,
allowDeferred: boolean
priority: InputPolicyOpts["priority"]
) {
let id = binding.getId(el);
@@ -28,17 +38,7 @@ function valueChangeCallback(
if (type) id = id + ":" + type;
const opts: {
priority: "deferred" | "immediate";
binding: typeof binding;
el: typeof el;
} = {
priority: allowDeferred ? "deferred" : "immediate",
binding: binding,
el: el,
};
inputs.setInput(id, value, opts);
inputs.setInput(id, value, { priority, binding, el });
}
}
@@ -75,14 +75,17 @@ const bindingsRegistry = (() => {
* accessibility and other reasons. However, in practice our bindings still
* work as long as inputs the IDs within a binding type don't overlap.
*
* @returns ShinyClientError if current ID bindings are invalid, otherwise
* returns an ok status.
* @returns ShinyClientMessageEvent if current ID bindings are invalid,
* otherwise returns an ok status.
*/
function checkValidity():
| { status: "error"; error: ShinyClientError }
| { status: "ok" } {
function checkValidity(scope: BindScope): void {
if (!isJQuery(scope) && !(scope instanceof HTMLElement)) {
return;
}
type BindingCounts = { [T in BindingTypes]: number };
const duplicateIds = new Map<string, BindingCounts>();
const problems: Set<string> = new Set();
// count duplicate IDs of each binding type
bindings.forEach((idTypes, id) => {
@@ -90,22 +93,30 @@ const bindingsRegistry = (() => {
idTypes.forEach((type) => (counts[type] += 1));
// If there's a single duplication of ids across both binding types, then
// when we're not in devmode, we allow this to pass because a good amount of
// existing applications use this pattern even though its invalid. Eventually
// this behavior should be removed.
if (counts.input === 1 && counts.output === 1 && !Shiny.inDevMode()) {
if (counts.input + counts.output < 2) {
return;
}
// We have duplicated IDs, add them to the set of duplicated IDs to be
// reported to the user.
duplicateIds.set(id, counts);
// If we have duplicated IDs, then add them to the set of duplicated IDs
// to be reported to the user.
if (counts.input + counts.output > 1) {
duplicateIds.set(id, counts);
if (counts.input > 1) {
problems.add("input");
}
if (counts.output > 1) {
problems.add("output");
}
if (counts.input >= 1 && counts.output >= 1) {
problems.add("shared");
}
});
if (duplicateIds.size === 0) return { status: "ok" };
if (duplicateIds.size === 0) return;
// Duplicated IDs are now always a warning. Before the ShinyClient console
// was added duplicate output IDs were errors in "production" mode. After
// the Shiny Client console was introduced, duplicate IDs were no longer
// production errors but *would* break apps in dev mode. Now, in v1.10+,
// duplicate IDs are always warnings in all modes for consistency.
const duplicateIdMsg = Array.from(duplicateIds.entries())
.map(([id, counts]) => {
@@ -120,15 +131,27 @@ const bindingsRegistry = (() => {
})
.join("\n");
return {
status: "error",
error: new ShinyClientError({
headline: "Duplicate input/output IDs found",
message: `The following ${
duplicateIds.size === 1 ? "ID was" : "IDs were"
} repeated:\n${duplicateIdMsg}`,
}),
};
let txtVerb = "Duplicate";
let txtNoun = "input/output";
if (problems.has("input") && problems.has("output")) {
// base case
} else if (problems.has("input")) {
txtNoun = "input";
} else if (problems.has("output")) {
txtNoun = "output";
} else if (problems.has("shared")) {
txtVerb = "Shared";
}
const txtIdsWere = duplicateIds.size == 1 ? "ID was" : "IDs were";
const headline = `${txtVerb} ${txtNoun} ${txtIdsWere} found`;
const message = `The following ${txtIdsWere} used for more than one ${
problems.has("shared") ? "input/output" : txtNoun
}:\n${duplicateIdMsg}`;
const event = new ShinyClientMessageEvent({ headline, message });
const scopeElement = isJQuery(scope) ? scope.get(0) : scope;
(scopeElement || window).dispatchEvent(event);
}
/**
@@ -240,8 +263,17 @@ function bindInputs(
const thisBinding = binding;
const thisEl = el;
return function (allowDeferred: boolean) {
valueChangeCallback(inputs, thisBinding, thisEl, allowDeferred);
// Historically speaking, this callback has only accepted a boolean value,
// but in recent versions it can also accept a input priority.
return function (priority: InputPolicyOpts["priority"] | boolean) {
const normalizedPriority =
typeof priority !== "boolean"
? priority
: priority
? "deferred"
: "immediate";
valueChangeCallback(inputs, thisBinding, thisEl, normalizedPriority);
};
})();
@@ -423,15 +455,7 @@ async function _bindAll(
// complete error message that contains everything they will need to fix. If
// we threw as we saw collisions then the user would fix the first collision,
// re-run, and then see the next collision, etc.
const bindingValidity = bindingsRegistry.checkValidity();
if (bindingValidity.status === "error") {
// Only throw if we're in dev mode. Otherwise, just log a warning.
if (Shiny.inDevMode()) {
throw bindingValidity.error;
} else {
console.warn("[shiny] " + bindingValidity.error.message);
}
}
bindingsRegistry.checkValidity(scope);
return currentInputs;
}

View File

@@ -21,6 +21,7 @@ import { debounce, Debouncer } from "../time";
import {
$escape,
compareVersion,
getBoundingClientSizeBeforeZoom,
getComputedLinkColor,
getStyle,
hasDefinedProperty,
@@ -176,12 +177,12 @@ class ShinyClass {
let target: InputPolicy;
if ($('input[type="submit"], button[type="submit"]').length > 0) {
if (document.querySelector(".submit-button")) {
// If there is a submit button on the page, use defer decorator
target = inputsDefer;
$('input[type="submit"], button[type="submit"]').each(function () {
$(this).click(function (event) {
document.querySelectorAll(".submit-button").forEach(function (x) {
x.addEventListener("click", function (event) {
event.preventDefault();
inputsDefer.submit();
});
@@ -289,7 +290,7 @@ class ShinyClass {
$(".shiny-image-output, .shiny-plot-output, .shiny-report-size").each(
function () {
const id = getIdFromEl(this),
rect = this.getBoundingClientRect();
rect = getBoundingClientSizeBeforeZoom(this);
if (rect.width !== 0 || rect.height !== 0) {
initialValues[".clientdata_output_" + id + "_width"] = rect.width;
@@ -425,7 +426,7 @@ class ShinyClass {
$(".shiny-image-output, .shiny-plot-output, .shiny-report-size").each(
function () {
const id = getIdFromEl(this),
rect = this.getBoundingClientRect();
rect = getBoundingClientSizeBeforeZoom(this);
if (rect.width !== 0 || rect.height !== 0) {
inputs.setInput(".clientdata_output_" + id + "_width", rect.width);

View File

@@ -71,8 +71,12 @@ function remove(): void {
// Look for a Bootstrap modal and if present, trigger hide event. This will
// trigger the hidden.bs.modal callback that we set in show(), which unbinds
// and removes the element.
if ($modal.find(".modal").length > 0) {
$modal.find(".modal").modal("hide");
const $bsModal = $modal.find(".modal");
if ($bsModal.length > 0) {
// We both hide the modal when its shown and also immediately; the immediate
// version is a no-op in Bootstrap if called before the modal is fully shown
$bsModal.on("shown.bs.modal", () => $bsModal.modal("hide"));
$bsModal.modal("hide");
} else {
// If not a Bootstrap modal dialog, simply unbind and remove it.
shinyUnbindAll($modal);

View File

@@ -18,6 +18,7 @@ import {
getShinyCreateWebsocket,
getShinyOnCustomMessage,
setShinyUser,
shinyBindAll,
shinyForgetLastInputValue,
shinyUnbindAll,
} from "./initedMethods";
@@ -614,7 +615,7 @@ class ShinyApp {
const nsPrefix = el.attr("data-ns-prefix") as string;
const nsScope = this._narrowScope(scope, nsPrefix);
const show = condFunc(nsScope);
const show = Boolean(condFunc(nsScope));
const showing = el.css("display") !== "none";
if (show !== showing) {
@@ -1053,8 +1054,13 @@ class ShinyApp {
const $tabContent = getTabContent($tabset);
let tabsetId = $parentTabset.attr("data-tabsetid");
const $divTag = $(message.divTag.html);
const $liTag = $(message.liTag.html);
// Create a virtual element where we'll temporarily hold the rendered
// nav controls so we can rewrite some attributes and choose where to
// insert the new controls.
const $fragLi = $("<div>");
await renderContentAsync($fragLi, message.liTag, "afterBegin");
const $liTag = $($fragLi).find("> li");
const $aTag = $liTag.find("> a");
// Unless the item is being prepended/appended, the target tab
@@ -1097,13 +1103,16 @@ class ShinyApp {
// text items (which function as dividers and headers inside
// navbarMenus) and whole navbarMenus (since those get
// constructed from scratch on the R side and therefore
// there are no ids that need matching)
// there are no ids that need matching). In other words, we're
// guaranteed to be inserting only one `nav_panel()`.
let fixupDivId = "";
if ($aTag.attr("data-toggle") === "tab") {
const index = getTabIndex($tabset, tabsetId);
const tabId = "tab-" + tabsetId + "-" + index;
$liTag.find("> a").attr("href", "#" + tabId);
$divTag.attr("id", tabId);
// We'll fixup the div ID after we insert it
fixupDivId = tabId;
}
// actually insert the item into the right place
@@ -1120,11 +1129,8 @@ class ShinyApp {
$tabset.append($liTag);
}
}
await shinyBindAll($targetLiTag?.parent() || $tabset);
await renderContentAsync($liTag[0], {
html: $liTag.html(),
deps: message.liTag.deps,
});
// jcheng 2017-07-28: This next part might look a little insane versus the
// more obvious `$tabContent.append($divTag);`, but there's a method to the
// madness.
@@ -1152,40 +1158,30 @@ class ShinyApp {
// In theory the same problem exists for $liTag but since that content is
// much less likely to include arbitrary scripts, we're skipping it.
//
// This code could be nicer if we didn't use renderContent, but rather the
// lower-level functions that renderContent uses. Like if we pre-process
// the value of message.divTag.html for singletons, we could do that, then
// render dependencies, then do $tabContent.append($divTag).
await renderContentAsync(
$tabContent[0],
{ html: "", deps: message.divTag.deps },
// @ts-expect-error; TODO-barret; There is no usage of beforeend
"beforeend"
);
for (const el of $divTag.get()) {
// Must not use jQuery for appending el to the doc, we don't want any
// scripts to run (since they will run when renderContent takes a crack).
$tabContent[0].appendChild(el);
// If `el` itself is a script tag, this approach won't work (the script
// won't be run), since we're only sending innerHTML through renderContent
// and not the whole tag. That's fine in this case because we control the
// R code that generates this HTML, and we know that the element is not
// a script tag.
await renderContentAsync(el, el.innerHTML || el.textContent);
// garrick 2025-01-23: Keeping in mind the above, the `shiny-insert-tab`
// method was re-written to avoid adding the nav controls (liTag) and
// the nav panel contents (divTag) twice. Now, we use
// renderContentAsync() to add both sections to the DOM only once.
await renderContentAsync($tabContent[0], message.divTag, "beforeEnd");
if (fixupDivId) {
// We're inserting one nav_panel() and need to fixup the content ID
$tabContent.find('[id="tab-tsid-id"]').attr("id", fixupDivId);
}
if (message.select) {
$liTag.find("a").tab("show");
}
/* Barbara -- August 2017
Note: until now, the number of tabs in a tabsetPanel (or navbarPage
or navlistPanel) was always fixed. So, an easy way to give an id to
a tab was simply incrementing a counter. (Just like it was easy to
give a random 4-digit number to identify the tabsetPanel). Now that
we're introducing dynamic tabs, we must retrieve these numbers and
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")
*/
* Note: until now, the number of tabs in a tabsetPanel (or navbarPage
* or navlistPanel) was always fixed. So, an easy way to give an id to
* a tab was simply incrementing a counter. (Just like it was easy to
* give a random 4-digit number to identify the tabsetPanel). Now that
* we're introducing dynamic tabs, we must retrieve these numbers and
* 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: JQuery<HTMLElement>,
tabsetId: string | undefined

View File

@@ -144,11 +144,25 @@ function pixelRatio(): number {
}
}
function getBoundingClientSizeBeforeZoom(el: HTMLElement): {
width: number;
height: number;
} {
const rect = el.getBoundingClientRect();
// Cast to any because currentCSSZoom isn't in the type def of HTMLElement
// TODO: typescript >= 5.5.2 added this property to the type definition
const zoom = (el as any).currentCSSZoom || 1;
return {
width: rect.width / zoom,
height: rect.height / zoom,
};
}
// Takes a string expression and returns a function that takes an argument.
//
// When the function is executed, it will evaluate that expression using
// "with" on the argument value, and return the result.
function scopeExprToFunc(expr: string): (scope: unknown) => boolean {
function scopeExprToFunc(expr: string): (scope: unknown) => unknown {
/*jshint evil: true */
const exprEscaped = expr
.replace(/[\\"']/g, "\\$&")
@@ -159,7 +173,7 @@ function scopeExprToFunc(expr: string): (scope: unknown) => boolean {
// \b has a special meaning; need [\b] to match backspace char.
.replace(/[\b]/g, "\\b");
let func: () => boolean;
let func: () => unknown;
try {
// @ts-expect-error; Do not know how to type this _dangerous_ situation
@@ -178,7 +192,7 @@ function scopeExprToFunc(expr: string): (scope: unknown) => boolean {
throw e;
}
return function (scope: unknown): boolean {
return function (scope: unknown): unknown {
return func.call(scope);
};
}
@@ -398,6 +412,7 @@ export {
formatDateUTC,
makeResizeFilter,
pixelRatio,
getBoundingClientSizeBeforeZoom,
scopeExprToFunc,
asArray,
mergeSort,

View File

@@ -1,3 +1,4 @@
import type { EventPriority } from "../../inputPolicies/inputPolicy";
import type { RatePolicyModes } from "../../inputPolicies/inputRateDecorator";
import type { BindScope } from "../../shiny/bind";
declare class InputBinding {
@@ -6,7 +7,7 @@ declare class InputBinding {
getId(el: HTMLElement): string;
getType(el: HTMLElement): string | null;
getValue(el: HTMLElement): any;
subscribe(el: HTMLElement, callback: (value: boolean) => void): void;
subscribe(el: HTMLElement, callback: (value: EventPriority | boolean) => void): void;
unsubscribe(el: HTMLElement): void;
receiveMessage(el: HTMLElement, data: unknown): Promise<void> | void;
getState(el: HTMLElement): unknown;

View File

@@ -1,3 +1,4 @@
import type { EventPriority } from "../../inputPolicies/inputPolicy";
import { InputBinding } from "./inputBinding";
type TextHTMLElement = HTMLInputElement;
type TextReceiveMessageData = {
@@ -10,7 +11,7 @@ declare class TextInputBindingBase extends InputBinding {
getId(el: TextHTMLElement): string;
getValue(el: TextHTMLElement): unknown;
setValue(el: TextHTMLElement, value: unknown): void;
subscribe(el: TextHTMLElement, callback: (x: boolean) => void): void;
subscribe(el: TextHTMLElement, callback: (x: EventPriority | boolean) => void): void;
unsubscribe(el: TextHTMLElement): void;
receiveMessage(el: TextHTMLElement, data: unknown): void;
getState(el: TextHTMLElement): unknown;

View File

@@ -1,5 +1,4 @@
import { TextInputBinding } from "./text";
declare class TextareaInputBinding extends TextInputBinding {
export declare class TextareaInputBinding extends TextInputBinding {
find(scope: HTMLElement): JQuery<HTMLElement>;
}
export { TextareaInputBinding };

View File

@@ -0,0 +1,18 @@
import type { EventPriority } from "../../inputPolicies/inputPolicy";
import { InputBinding } from "./inputBinding";
type TextHTMLElement = HTMLInputElement;
type TextSubmitReceiveMessageData = {
value?: string;
placeholder?: string;
submit?: boolean;
focus?: boolean;
};
declare class TextSubmitInputBinding extends InputBinding {
find(scope: HTMLElement): JQuery<HTMLElement>;
getValue(el: TextHTMLElement): string;
setValue(el: TextHTMLElement, value: string): void;
subscribe(el: TextHTMLElement, callback: (x: EventPriority | boolean) => void): void;
unsubscribe(el: HTMLElement): void;
receiveMessage(el: TextHTMLElement, data: TextSubmitReceiveMessageData): void;
}
export { TextSubmitInputBinding };

View File

@@ -10,6 +10,11 @@ export declare class ShinyErrorMessage extends LitElement {
copyErrorToClipboard(): Promise<void>;
render(): import("lit-html").TemplateResult<1>;
}
export type ShinyClientMessage = {
message: string;
headline?: string;
status?: "error" | "info" | "warning";
};
/**
* Function to show an error message to user in shiny-error-message web
* component. Only shows the error if we're in development mode.
@@ -18,3 +23,6 @@ export declare class ShinyErrorMessage extends LitElement {
* object.
*/
export declare function showErrorInClientConsole(e: unknown): void;
export declare class ShinyClientMessageEvent extends CustomEvent<ShinyClientMessage> {
constructor(detail: ShinyClientMessage);
}

View File

@@ -10,7 +10,11 @@ declare function parseDate(dateString: string): Date;
declare function formatDateUTC(x: Date): string;
declare function makeResizeFilter(el: HTMLElement, func: (width: HTMLElement["offsetWidth"], height: HTMLElement["offsetHeight"]) => void): () => void;
declare function pixelRatio(): number;
declare function scopeExprToFunc(expr: string): (scope: unknown) => boolean;
declare function getBoundingClientSizeBeforeZoom(el: HTMLElement): {
width: number;
height: number;
};
declare function scopeExprToFunc(expr: string): (scope: unknown) => unknown;
declare function asArray<T>(value: T | T[] | null | undefined): T[];
declare function mergeSort<Item>(list: Item[], sortfunc: (a: Item, b: Item) => boolean | number): Item[];
declare function $escape(val: undefined): undefined;
@@ -26,4 +30,4 @@ declare function updateLabel(labelTxt: string | undefined, labelNode: JQuery<HTM
declare function getComputedLinkColor(el: HTMLElement): string;
declare function isBS3(): boolean;
declare function toLowerCase<T extends string>(str: T): Lowercase<T>;
export { escapeHTML, randomId, strToBool, getStyle, padZeros, roundSignif, parseDate, formatDateUTC, makeResizeFilter, pixelRatio, scopeExprToFunc, asArray, mergeSort, $escape, mapValues, isnan, _equal, equal, compareVersion, updateLabel, getComputedLinkColor, hasOwnProperty, hasDefinedProperty, isBS3, toLowerCase, };
export { escapeHTML, randomId, strToBool, getStyle, padZeros, roundSignif, parseDate, formatDateUTC, makeResizeFilter, pixelRatio, getBoundingClientSizeBeforeZoom, scopeExprToFunc, asArray, mergeSort, $escape, mapValues, isnan, _equal, equal, compareVersion, updateLabel, getComputedLinkColor, hasOwnProperty, hasDefinedProperty, isBS3, toLowerCase, };

View File

@@ -0,0 +1,685 @@
# deep stack capturing
Code
cat(sep = "\n", formatError(err))
Output
Error in onFinally: boom
: stop
: onFinally [test-stacks-deep.R#XXX]
: onFulfilled
: callback
: <Anonymous>
: onFulfilled
: handleFulfill
: <Anonymous>
: execCallbacks
: later::run_now
: wait_for_it
: eval [test-stacks-deep.R#XXX]
: eval
: test_code
: test_that
: eval [test-stacks-deep.R#XXX]
: eval
: test_code
: source_file
: FUN
: lapply
: test_files_serial
: test_files
From earlier call:
: domain$wrapOnFulfilled
: promiseDomain$onThen
: action
: promise
: self$then
: promise$finally
: finally
: onRejected [test-stacks-deep.R#XXX]
: callback
: <Anonymous>
: onRejected
: handleReject
: <Anonymous>
: execCallbacks
: later::run_now
: wait_for_it
: eval [test-stacks-deep.R#XXX]
: eval
: test_code
: test_that
: eval [test-stacks-deep.R#XXX]
: eval
: test_code
: source_file
: FUN
: lapply
: test_files_serial
: test_files
From earlier call:
: domain$wrapOnRejected
: promiseDomain$onThen
: action
: promise
: self$then
: promise$catch
: catch
: %...!%
: onFulfilled [test-stacks-deep.R#XXX]
: callback
: <Anonymous>
: onFulfilled
: handleFulfill
: <Anonymous>
: execCallbacks
: later::run_now
: wait_for_it
: eval [test-stacks-deep.R#XXX]
: eval
: test_code
: test_that
: eval [test-stacks-deep.R#XXX]
: eval
: test_code
: source_file
: FUN
: lapply
: test_files_serial
: test_files
From earlier call:
: domain$wrapOnFulfilled
: promiseDomain$onThen
: action
: promise
: promise$then
: then
: %...>%
: eval [test-stacks-deep.R#XXX]
: eval
: test_code
: test_that
: eval [test-stacks-deep.R#XXX]
: eval
: test_code
: source_file
: FUN
: lapply
: test_files_serial
: test_files
---
Code
cat(sep = "\n", formatError(err, full = TRUE))
Output
Error in onFinally: boom
: h
: .handleSimpleError
: stop
: onFinally [test-stacks-deep.R#XXX]
: onFulfilled
: withCallingHandlers
: callback
: force
: reenter_promise_domain
: <Anonymous>
: onFulfilled
: withVisible
: private$doResolve
: withCallingHandlers
: doTryCatch
: tryCatchOne
: tryCatchList
: base::tryCatch
: tryCatch
: resolve
: handleFulfill
: <Anonymous>
: execCallbacks
: later::run_now
: wait_for_it
: eval [test-stacks-deep.R#XXX]
: eval
: withCallingHandlers
: doTryCatch
: tryCatchOne
: tryCatchList
: doTryCatch
: tryCatchOne
: tryCatchList
: tryCatch
: test_code
: test_that
: eval [test-stacks-deep.R#XXX]
: eval
: withCallingHandlers
: doTryCatch
: tryCatchOne
: tryCatchList
: doTryCatch
: tryCatchOne
: tryCatchList
: tryCatch
: test_code
: source_file
: FUN
: lapply
: doTryCatch
: tryCatchOne
: tryCatchList
: tryCatch
: with_reporter
: test_files_serial
: test_files
From earlier call:
: domain$wrapOnFulfilled
: promiseDomain$onThen
: action
: withCallingHandlers
: doTryCatch
: tryCatchOne
: tryCatchList
: base::tryCatch
: tryCatch
: promise
: self$then
: promise$finally
: finally
: onRejected [test-stacks-deep.R#XXX]
: withCallingHandlers
: callback
: force
: reenter_promise_domain
: <Anonymous>
: onRejected
: withVisible
: private$doResolve
: withCallingHandlers
: doTryCatch
: tryCatchOne
: tryCatchList
: base::tryCatch
: tryCatch
: resolve
: handleReject
: <Anonymous>
: execCallbacks
: later::run_now
: wait_for_it
: eval [test-stacks-deep.R#XXX]
: eval
: withCallingHandlers
: doTryCatch
: tryCatchOne
: tryCatchList
: doTryCatch
: tryCatchOne
: tryCatchList
: tryCatch
: test_code
: test_that
: eval [test-stacks-deep.R#XXX]
: eval
: withCallingHandlers
: doTryCatch
: tryCatchOne
: tryCatchList
: doTryCatch
: tryCatchOne
: tryCatchList
: tryCatch
: test_code
: source_file
: FUN
: lapply
: doTryCatch
: tryCatchOne
: tryCatchList
: tryCatch
: with_reporter
: test_files_serial
: test_files
From earlier call:
: domain$wrapOnRejected
: promiseDomain$onThen
: action
: withCallingHandlers
: doTryCatch
: tryCatchOne
: tryCatchList
: base::tryCatch
: tryCatch
: promise
: self$then
: promise$catch
: catch
: %...!%
: onFulfilled [test-stacks-deep.R#XXX]
: withCallingHandlers
: callback
: force
: reenter_promise_domain
: <Anonymous>
: onFulfilled
: withVisible
: private$doResolve
: withCallingHandlers
: doTryCatch
: tryCatchOne
: tryCatchList
: base::tryCatch
: tryCatch
: resolve
: handleFulfill
: <Anonymous>
: execCallbacks
: later::run_now
: wait_for_it
: eval [test-stacks-deep.R#XXX]
: eval
: withCallingHandlers
: doTryCatch
: tryCatchOne
: tryCatchList
: doTryCatch
: tryCatchOne
: tryCatchList
: tryCatch
: test_code
: test_that
: eval [test-stacks-deep.R#XXX]
: eval
: withCallingHandlers
: doTryCatch
: tryCatchOne
: tryCatchList
: doTryCatch
: tryCatchOne
: tryCatchList
: tryCatch
: test_code
: source_file
: FUN
: lapply
: doTryCatch
: tryCatchOne
: tryCatchList
: tryCatch
: with_reporter
: test_files_serial
: test_files
From earlier call:
: domain$wrapOnFulfilled
: promiseDomain$onThen
: action
: withCallingHandlers
: doTryCatch
: tryCatchOne
: tryCatchList
: base::tryCatch
: tryCatch
: promise
: promise$then
: then
: %...>%
: withCallingHandlers [test-stacks-deep.R#XXX]
: domain$wrapSync
: promises::with_promise_domain
: captureStackTraces
: as.promise
: catch
: %...!%
: eval [test-stacks-deep.R#XXX]
: eval
: withCallingHandlers
: doTryCatch
: tryCatchOne
: tryCatchList
: doTryCatch
: tryCatchOne
: tryCatchList
: tryCatch
: test_code
: test_that
: eval [test-stacks-deep.R#XXX]
: eval
: withCallingHandlers
: doTryCatch
: tryCatchOne
: tryCatchList
: doTryCatch
: tryCatchOne
: tryCatchList
: tryCatch
: test_code
: source_file
: FUN
: lapply
: doTryCatch
: tryCatchOne
: tryCatchList
: tryCatch
: with_reporter
: test_files_serial
: test_files
# deep stacks long chain
Code
cat(sep = "\n", stacktrace <- formatError(dserr))
Output
Error in onFulfilled: boom
: stop
: onFulfilled [test-stacks-deep.R#XXX]
: callback
: <Anonymous>
: onFulfilled
: handleFulfill
: <Anonymous>
: execCallbacks
: later::run_now
: wait_for_it
: eval [test-stacks-deep.R#XXX]
: eval
: test_code
: test_that
: eval [test-stacks-deep.R#XXX]
: eval
: test_code
: source_file
: FUN
: lapply
: test_files_serial
: test_files
From earlier call:
: domain$wrapOnFulfilled
: promiseDomain$onThen
: action
: promise
: promise$then
: then
: %...>%
: J__ [test-stacks-deep.R#XXX]
: onFulfilled
: callback
: <Anonymous>
: onFulfilled
: handleFulfill
: <Anonymous>
: execCallbacks
: later::run_now
: wait_for_it
: eval [test-stacks-deep.R#XXX]
: eval
: test_code
: test_that
: eval [test-stacks-deep.R#XXX]
: eval
: test_code
: source_file
: FUN
: lapply
: test_files_serial
: test_files
From earlier call:
: domain$wrapOnFulfilled
: promiseDomain$onThen
: action
: promise
: promise$then
: then
: %...>%
: I__ [test-stacks-deep.R#XXX]
: onFulfilled
: callback
: <Anonymous>
: onFulfilled
: handleFulfill
: <Anonymous>
: execCallbacks
: later::run_now
: wait_for_it
: eval [test-stacks-deep.R#XXX]
: eval
: test_code
: test_that
: eval [test-stacks-deep.R#XXX]
: eval
: test_code
: source_file
: FUN
: lapply
: test_files_serial
: test_files
From earlier call:
: domain$wrapOnFulfilled
: promiseDomain$onThen
: action
: promise
: promise$then
: then
: %...>%
: H__ [test-stacks-deep.R#XXX]
: onFulfilled
: callback
: <Anonymous>
: onFulfilled
: handleFulfill
: <Anonymous>
: execCallbacks
: later::run_now
: wait_for_it
: eval [test-stacks-deep.R#XXX]
: eval
: test_code
: test_that
: eval [test-stacks-deep.R#XXX]
: eval
: test_code
: source_file
: FUN
: lapply
: test_files_serial
: test_files
From earlier call:
: domain$wrapOnFulfilled
: promiseDomain$onThen
: action
: promise
: promise$then
: then
: %...>%
: G__ [test-stacks-deep.R#XXX]
: onFulfilled
: callback
: <Anonymous>
: onFulfilled
: handleFulfill
: <Anonymous>
: execCallbacks
: later::run_now
: wait_for_it
: eval [test-stacks-deep.R#XXX]
: eval
: test_code
: test_that
: eval [test-stacks-deep.R#XXX]
: eval
: test_code
: source_file
: FUN
: lapply
: test_files_serial
: test_files
From earlier call:
: domain$wrapOnFulfilled
: promiseDomain$onThen
: action
: promise
: promise$then
: then
: %...>%
: F__ [test-stacks-deep.R#XXX]
: onFulfilled
: callback
: <Anonymous>
: onFulfilled
: handleFulfill
: <Anonymous>
: execCallbacks
: later::run_now
: wait_for_it
: eval [test-stacks-deep.R#XXX]
: eval
: test_code
: test_that
: eval [test-stacks-deep.R#XXX]
: eval
: test_code
: source_file
: FUN
: lapply
: test_files_serial
: test_files
From earlier call:
: domain$wrapOnFulfilled
: promiseDomain$onThen
: action
: promise
: promise$then
: then
: %...>%
: E__ [test-stacks-deep.R#XXX]
: onFulfilled
: callback
: <Anonymous>
: onFulfilled
: handleFulfill
: <Anonymous>
: execCallbacks
: later::run_now
: wait_for_it
: eval [test-stacks-deep.R#XXX]
: eval
: test_code
: test_that
: eval [test-stacks-deep.R#XXX]
: eval
: test_code
: source_file
: FUN
: lapply
: test_files_serial
: test_files
From earlier call:
: domain$wrapOnFulfilled
: promiseDomain$onThen
: action
: promise
: promise$then
: then
: %...>%
: D__ [test-stacks-deep.R#XXX]
: onFulfilled
: callback
: <Anonymous>
: onFulfilled
: handleFulfill
: <Anonymous>
: execCallbacks
: later::run_now
: wait_for_it
: eval [test-stacks-deep.R#XXX]
: eval
: test_code
: test_that
: eval [test-stacks-deep.R#XXX]
: eval
: test_code
: source_file
: FUN
: lapply
: test_files_serial
: test_files
From earlier call:
: domain$wrapOnFulfilled
: promiseDomain$onThen
: action
: promise
: promise$then
: then
: %...>%
: C__ [test-stacks-deep.R#XXX]
: onFulfilled
: callback
: <Anonymous>
: onFulfilled
: handleFulfill
: <Anonymous>
: execCallbacks
: later::run_now
: wait_for_it
: eval [test-stacks-deep.R#XXX]
: eval
: test_code
: test_that
: eval [test-stacks-deep.R#XXX]
: eval
: test_code
: source_file
: FUN
: lapply
: test_files_serial
: test_files
From earlier call:
: domain$wrapOnFulfilled
: promiseDomain$onThen
: action
: promise
: promise$then
: then
: %...>%
: B__ [test-stacks-deep.R#XXX]
: onFulfilled
: callback
: <Anonymous>
: onFulfilled
: handleFulfill
: <Anonymous>
: execCallbacks
: later::run_now
: wait_for_it
: eval [test-stacks-deep.R#XXX]
: eval
: test_code
: test_that
: eval [test-stacks-deep.R#XXX]
: eval
: test_code
: source_file
: FUN
: lapply
: test_files_serial
: test_files
From earlier call:
: domain$wrapOnFulfilled
: promiseDomain$onThen
: action
: promise
: promise$then
: then
: %...>%
: A__ [test-stacks-deep.R#XXX]
: eval [test-stacks-deep.R#XXX]
: eval
: test_code
: test_that
: eval [test-stacks-deep.R#XXX]
: eval
: test_code
: source_file
: FUN
: lapply
: test_files_serial
: test_files

View File

@@ -0,0 +1,90 @@
# integration tests
Code
df
Output
num call loc
1 64 A [test-stacks.R#3]
2 63 B [test-stacks.R#7]
3 62 <reactive:C> [test-stacks.R#11]
4 42 C
5 41 renderTable [test-stacks.R#18]
6 40 func
7 39 force
8 38 withVisible
9 37 withCallingHandlers
---
Code
df
Output
num call loc
1 67 h
2 66 .handleSimpleError
3 65 stop
4 64 A [test-stacks.R#3]
5 63 B [test-stacks.R#7]
6 62 <reactive:C> [test-stacks.R#11]
7 61 ..stacktraceon..
8 60 .func
9 59 withVisible
10 58 withCallingHandlers
11 57 contextFunc
12 56 env$runWith
13 55 withCallingHandlers
14 54 domain$wrapSync
15 53 promises::with_promise_domain
16 52 captureStackTraces
17 51 force
18 50 domain$wrapSync
19 49 promises::with_promise_domain
20 48 withReactiveDomain
21 47 domain$wrapSync
22 46 promises::with_promise_domain
23 45 ctx$run
24 44 self$.updateValue
25 43 ..stacktraceoff..
26 42 C
27 41 renderTable [test-stacks.R#18]
28 40 func
29 39 force
30 38 withVisible
31 37 withCallingHandlers
32 36 domain$wrapSync
33 35 promises::with_promise_domain
34 34 captureStackTraces
35 33 doTryCatch
36 32 tryCatchOne
37 31 tryCatchList
38 30 tryCatch
39 29 do
40 28 hybrid_chain
41 27 renderFunc
42 26 renderTable({ C() }, server = FALSE)
43 25 ..stacktraceon.. [test-stacks.R#17]
44 24 contextFunc
45 23 env$runWith
46 22 withCallingHandlers
47 21 domain$wrapSync
48 20 promises::with_promise_domain
49 19 captureStackTraces
50 18 force
51 17 domain$wrapSync
52 16 promises::with_promise_domain
53 15 withReactiveDomain
54 14 domain$wrapSync
55 13 promises::with_promise_domain
56 12 ctx$run
57 11 ..stacktraceoff..
58 10 isolate
59 9 withCallingHandlers [test-stacks.R#16]
60 8 domain$wrapSync
61 7 promises::with_promise_domain
62 6 captureStackTraces
63 5 doTryCatch [test-stacks.R#15]
64 4 tryCatchOne
65 3 tryCatchList
66 2 tryCatch
67 1 try

View File

@@ -183,7 +183,7 @@
nav_page
Output
<body class="bslib-page-navbar">
<nav class="navbar navbar-default navbar-static-top" role="navigation">
<nav class="navbar navbar-default navbar-static-top" role="navigation" data-bs-theme="light">
<div class="container-fluid">
<div class="navbar-header">
<span class="navbar-brand">Title</span>
@@ -227,7 +227,7 @@
bslib_tags(x)
Output
<body class="bslib-page-navbar">
<nav class="navbar navbar-default navbar-static-top" role="navigation">
<nav class="navbar navbar-default navbar-static-top" role="navigation" data-bs-theme="light">
<div class="container-fluid">
<div class="navbar-header">
<span class="navbar-brand">Title</span>

View File

@@ -21,10 +21,11 @@ test_that("Repeated names for selectInput and radioButtons choices", {
# Select input
x <- selectInput('id','label', choices = c(a='x1', a='x2', b='x3'), selectize = FALSE)
expect_true(grepl(fixed = TRUE,
expect_match(
format(x),
'<select class="shiny-input-select form-control" id="id"><option value="x1" selected>a</option>\n<option value="x2">a</option>\n<option value="x3">b</option></select>',
format(x)
))
fixed = TRUE
)
# Radio buttons using choices
x <- radioButtons('id','label', choices = c(a='x1', a='x2', b='x3'))
@@ -248,10 +249,11 @@ test_that("selectInput selects items by default", {
))
# Nothing selected when choices=NULL
expect_true(grepl(fixed = TRUE,
expect_match(
format(selectInput('x', NULL, NULL, selectize = FALSE)),
'<select class="shiny-input-select form-control" id="x"></select>',
format(selectInput('x', NULL, NULL, selectize = FALSE))
))
fixed = TRUE
)
# None specified as selected. With multiple=TRUE, none selected by default.
expect_true(grepl(fixed = TRUE,

View File

@@ -48,7 +48,7 @@ test_that("busyIndicatorOptions()", {
test_that("Can provide svg file for busyIndicatorOptions(spinner_type)", {
skip_if(.Platform$OS.type == "windows")
skip_on_os("windows")
tmpsvg <- tempfile(fileext = ".svg")
writeLines("<svg></svg>", tmpsvg)

View File

@@ -11,18 +11,18 @@ test_that("HTML has correct attributes", {
})
test_that("Github extensions are on by default", {
html <- markdown("a ~paragraph~ with a link: https://example.com")
html <- markdown("a ~~paragraph~~ with a link: https://example.com")
expect_equal(html, HTML("<p>a <del>paragraph</del> with a link: <a href=\"https://example.com\">https://example.com</a></p>\n"))
})
test_that("Github extensions can be disabled", {
html <- markdown("a ~paragraph~", extensions = FALSE)
expect_equal(html, HTML("<p>a ~paragraph~</p>\n"))
html <- markdown("a ~~paragraph~~", extensions = FALSE)
expect_equal(html, HTML("<p>a ~~paragraph~~</p>\n"))
})
test_that("Additional options are respected", {
html <- markdown("a ~paragraph~", extensions = FALSE, sourcepos = TRUE)
expect_equal(html, HTML("<p data-sourcepos=\"1:1-1:13\">a ~paragraph~</p>\n"))
html <- markdown("a ~~paragraph~~", extensions = FALSE, sourcepos = TRUE)
expect_equal(html, HTML("<p data-sourcepos=\"1:1-1:15\">a ~~paragraph~~</p>\n"))
})
test_that("Multiline markdown works properly", {

View File

@@ -1,10 +1,10 @@
test_that("performance warning works", {
pattern <- "consider using server-side selectize"
expect_warning(selectInput("x", "x", as.character(1:999)), NA)
expect_warning(selectInput("x", "x", as.character(1:999), selectize = TRUE), NA)
expect_warning(selectInput("x", "x", as.character(1:999), selectize = FALSE), NA)
expect_warning(selectizeInput("x", "x", as.character(1:999)), NA)
expect_no_warning(selectInput("x", "x", as.character(1:999)))
expect_no_warning(selectInput("x", "x", as.character(1:999), selectize = TRUE))
expect_no_warning(selectInput("x", "x", as.character(1:999), selectize = FALSE))
expect_no_warning(selectizeInput("x", "x", as.character(1:999)))
expect_warning(selectInput("x", "x", as.character(1:1000)), pattern)
expect_warning(selectInput("x", "x", as.character(1:1000), selectize = TRUE), pattern)
@@ -17,9 +17,9 @@ test_that("performance warning works", {
session <- MockShinySession$new()
expect_warning(updateSelectInput(session, "x", choices = as.character(1:999)), NA)
expect_warning(updateSelectizeInput(session, "x", choices = as.character(1:999)), NA)
expect_warning(updateSelectizeInput(session, "x", choices = as.character(1:999), server = FALSE), NA)
expect_no_warning(updateSelectInput(session, "x", choices = as.character(1:999)))
expect_no_warning(updateSelectizeInput(session, "x", choices = as.character(1:999)))
expect_no_warning(updateSelectizeInput(session, "x", choices = as.character(1:999), server = FALSE))
expect_warning(updateSelectInput(session, "x", choices = as.character(1:1000)), pattern)
expect_warning(updateSelectizeInput(session, "x", choices = as.character(1:1000)), pattern)
@@ -28,9 +28,9 @@ test_that("performance warning works", {
expect_warning(updateSelectizeInput(session, "x", choices = as.character(1:2000)), pattern)
expect_warning(updateSelectizeInput(session, "x", choices = as.character(1:2000), server = FALSE), pattern)
expect_warning(updateSelectizeInput(session, "x", choices = as.character(1:999), server = TRUE), NA)
expect_warning(updateSelectizeInput(session, "x", choices = as.character(1:1000), server = TRUE), NA)
expect_warning(updateSelectizeInput(session, "x", choices = as.character(1:2000), server = TRUE), NA)
expect_no_warning(updateSelectizeInput(session, "x", choices = as.character(1:999), server = TRUE))
expect_no_warning(updateSelectizeInput(session, "x", choices = as.character(1:1000), server = TRUE))
expect_no_warning(updateSelectizeInput(session, "x", choices = as.character(1:2000), server = TRUE))
})
@@ -55,9 +55,9 @@ test_that("selectInput options are properly escaped", {
))
si_str <- as.character(si)
expect_true(any(grepl("<option value=\"&quot;\">", si_str, fixed = TRUE)))
expect_true(any(grepl("<option value=\"&#39;\">", si_str, fixed = TRUE)))
expect_true(any(grepl("<optgroup label=\"&quot;Separators&quot;\">", si_str, fixed = TRUE)))
expect_match(si_str, "<option value=\"&quot;\">", fixed = TRUE, all = FALSE)
expect_match(si_str, "<option value=\"&#39;\">", fixed = TRUE, all = FALSE)
expect_match(si_str, "<optgroup label=\"&quot;Separators&quot;\">", fixed = TRUE, all = FALSE)
})
@@ -75,10 +75,10 @@ test_that("selectInputUI has a select at an expected location", {
)
# if this getter is changed, varSelectInput getter needs to be changed
selectHtml <- selectInputVal$children[[2]]$children[[1]]
expect_true(inherits(selectHtml, "shiny.tag"))
expect_s3_class(selectHtml, "shiny.tag")
expect_equal(selectHtml$name, "select")
if (!is.null(selectHtml$attribs$class)) {
expect_false(grepl(selectHtml$attribs$class, "symbol"))
expect_no_match(selectHtml$attribs$class, "symbol")
}
varSelectInputVal <- varSelectInput(
@@ -91,9 +91,9 @@ test_that("selectInputUI has a select at an expected location", {
)
# if this getter is changed, varSelectInput getter needs to be changed
varSelectHtml <- varSelectInputVal$children[[2]]$children[[1]]
expect_true(inherits(varSelectHtml, "shiny.tag"))
expect_s3_class(varSelectHtml, "shiny.tag")
expect_equal(varSelectHtml$name, "select")
expect_true(grepl("symbol", varSelectHtml$attribs$class, fixed = TRUE))
expect_match(varSelectHtml$attribs$class, "symbol", fixed = TRUE)
}
}
}

View File

@@ -2,5 +2,5 @@ test_that("plotPNG()/startPNG() ignores NULL dimensions", {
f <- plotPNG(function() plot(1), width = NULL, height = NULL)
on.exit(unlink(f))
bits <- readBin(f, "raw", file.info(f)$size)
expect_true(length(bits) > 0)
expect_gt(length(bits), 0)
})

View File

@@ -0,0 +1,49 @@
with_several_promise_domains <- function(expr) {
withReactiveDomain(MockShinySession$new(), {
promises::with_promise_domain(reactivePromiseDomain(), {
captureStackTraces({
expr
})
})
})
}
recursive_promise <- function(n, callback = identity) {
if (n <= 0) {
return(promise_resolve(0))
}
p <- promises::promise_resolve(TRUE)
promises::then(p, ~{
callback(n)
recursive_promise(n - 1, callback = callback)
})
}
test_that("Stack trace doesn't grow (resolution within domain)", {
depths <- list()
with_several_promise_domains({
recursive_promise(10, function(n) {
depths <<- c(depths, list(length(sys.calls())))
})
while (!later::loop_empty()) {
later::run_now()
}
})
expect_equal(diff(range(depths)), 0)
})
test_that("Stack trace doesn't grow (resolution outside domain)", {
depths <- list()
with_several_promise_domains({
recursive_promise(10, function(n) {
depths <<- c(depths, list(length(sys.calls())))
})
})
while (!later::loop_empty()) {
later::run_now()
}
expect_equal(diff(range(depths)), 0)
})

View File

@@ -9,7 +9,7 @@ test_that("ReactiveVal", {
val <- reactiveVal()
isolate({
expect_true(is.null(val()))
expect_null(val())
# Set to a simple value
val(1)
@@ -99,12 +99,12 @@ test_that("ReactiveValues", {
values <- reactiveValues(a=NULL, b=2)
# a should exist and be NULL
expect_setequal(isolate(names(values)), c("a", "b"))
expect_true(is.null(isolate(values$a)))
expect_null(isolate(values$a))
# Assigning NULL should keep object (not delete it), and set value to NULL
values$b <- NULL
expect_setequal(isolate(names(values)), c("a", "b"))
expect_true(is.null(isolate(values$b)))
expect_null(isolate(values$b))
# Errors -----------------------------------------------------------------
@@ -960,8 +960,8 @@ test_that("classes of reactive object", {
})
test_that("{} and NULL also work in reactive()", {
expect_error(reactive({}), NA)
expect_error(reactive(NULL), NA)
expect_no_error(reactive({}))
expect_no_error(reactive(NULL))
})
test_that("shiny.suppressMissingContextError option works", {
@@ -1232,162 +1232,85 @@ test_that("event handling helpers take correct dependencies", {
})
test_that("debounce/throttle work properly (with priming)", {
do_priming <- TRUE
# Some of the CRAN test machines are heavily loaded and so the timing for
# these tests isn't reliable. https://github.com/rstudio/shiny/pull/2789
skip_on_cran()
for (do_priming in c(TRUE, FALSE)) {
label <- if (do_priming) "with priming" else "without priming"
test_that(sprintf("debounce/throttle work properly (%s)", label), {
# Some of the CRAN test machines are heavily loaded and so the timing for
# these tests isn't reliable. https://github.com/rstudio/shiny/pull/2789
skip_on_cran()
# The changing of rv$a will be the (chatty) source of reactivity.
rv <- reactiveValues(a = 0)
# The changing of rv$a will be the (chatty) source of reactivity.
rv <- reactiveValues(a = 0)
# This observer will be what changes rv$a.
src <- observe({
invalidateLater(100)
rv$a <- isolate(rv$a) + 1
})
on.exit(src$destroy(), add = TRUE)
# This observer will be what changes rv$a.
src <- observe({
invalidateLater(300)
rv$a <- isolate(rv$a) + 1
})
on.exit(src$destroy(), add = TRUE)
# Make a debounced reactive to test.
dr <- debounce(reactive(rv$a), 500)
# Make a debounced reactive to test.
dr <- debounce(reactive(rv$a), 500)
# Make a throttled reactive to test.
tr <- throttle(reactive(rv$a), 500)
# Make a throttled reactive to test.
tr <- throttle(reactive(rv$a), 500)
# Keep track of how often dr/tr are fired
dr_fired <- 0
dr_monitor <- observeEvent(dr(), {
dr_fired <<- dr_fired + 1
})
on.exit(dr_monitor$destroy(), add = TRUE)
# Keep track of how often dr/tr are fired
dr_fired <- 0
dr_monitor <- observeEvent(dr(), {
dr_fired <<- dr_fired + 1
})
on.exit(dr_monitor$destroy(), add = TRUE)
tr_fired <- 0
tr_monitor <- observeEvent(tr(), {
tr_fired <<- tr_fired + 1
})
on.exit(tr_monitor$destroy(), add = TRUE)
tr_fired <- 0
tr_monitor <- observeEvent(tr(), {
tr_fired <<- tr_fired + 1
})
on.exit(tr_monitor$destroy(), add = TRUE)
# Starting values are both 0. Earlier I found that the tests behaved
# differently if I accessed the values of dr/tr before the first call to
# flushReact(). That bug was fixed, but to ensure that similar bugs don't
# appear undetected, we run this test with and without do_priming.
if (do_priming) {
# Starting values are both 0. Earlier I found that the tests behaved
# differently if I accessed the values of dr/tr before the first call to
# flushReact(). That bug was fixed, but to ensure that similar bugs don't
# appear undetected, we run this test with and without do_priming.
if (do_priming) {
expect_identical(isolate(dr()), 0)
expect_identical(isolate(tr()), 0)
}
# Pump timer and reactives for about 1.3 seconds
stopAt <- Sys.time() + 1.3
while (Sys.time() < stopAt) {
timerCallbacks$executeElapsed()
flushReact()
Sys.sleep(0.001)
}
# dr() should not have had time to fire, other than the initial run, since
# there haven't been long enough gaps between invalidations.
expect_identical(dr_fired, 1)
# The value of dr() should not have updated either.
expect_identical(isolate(dr()), 0)
expect_identical(isolate(tr()), 0)
}
# Pump timer and reactives for about 1.3 seconds
stopAt <- Sys.time() + 1.3
while (Sys.time() < stopAt) {
timerCallbacks$executeElapsed()
flushReact()
Sys.sleep(0.001)
}
# tr() however, has had time to fire multiple times and update its value.
expect_identical(tr_fired, 3)
expect_identical(isolate(tr()), 4)
# dr() should not have had time to fire, other than the initial run, since
# there haven't been long enough gaps between invalidations.
expect_identical(dr_fired, 1)
# The value of dr() should not have updated either.
expect_identical(isolate(dr()), 0)
# Now let some time pass without any more updates.
src$destroy() # No more updates
stopAt <- Sys.time() + 1
while (Sys.time() < stopAt) {
timerCallbacks$executeElapsed()
flushReact()
Sys.sleep(0.001)
}
# tr() however, has had time to fire multiple times and update its value.
expect_identical(tr_fired, 3)
expect_identical(isolate(tr()), 10)
# Now let some time pass without any more updates.
src$destroy() # No more updates
stopAt <- Sys.time() + 1
while (Sys.time() < stopAt) {
timerCallbacks$executeElapsed()
flushReact()
Sys.sleep(0.001)
}
# dr should've fired, and we should have converged on the right answer.
expect_identical(dr_fired, 2)
isolate(expect_identical(rv$a, dr()))
expect_identical(tr_fired, 4)
isolate(expect_identical(rv$a, tr()))
})
# Identical to test block above, but with do_priming set to FALSE.
test_that("debounce/throttle work properly (without priming)", {
do_priming <- FALSE
# Some of the CRAN test machines are heavily loaded and so the timing for
# these tests isn't reliable. https://github.com/rstudio/shiny/pull/2789
skip_on_cran()
# The changing of rv$a will be the (chatty) source of reactivity.
rv <- reactiveValues(a = 0)
# This observer will be what changes rv$a.
src <- observe({
invalidateLater(100)
rv$a <- isolate(rv$a) + 1
# dr should've fired, and we should have converged on the right answer.
expect_identical(dr_fired, 2)
isolate(expect_identical(rv$a, dr()))
expect_identical(tr_fired, 4)
isolate(expect_identical(rv$a, tr()))
})
on.exit(src$destroy(), add = TRUE)
# Make a debounced reactive to test.
dr <- debounce(reactive(rv$a), 500)
# Make a throttled reactive to test.
tr <- throttle(reactive(rv$a), 500)
# Keep track of how often dr/tr are fired
dr_fired <- 0
dr_monitor <- observeEvent(dr(), {
dr_fired <<- dr_fired + 1
})
on.exit(dr_monitor$destroy(), add = TRUE)
tr_fired <- 0
tr_monitor <- observeEvent(tr(), {
tr_fired <<- tr_fired + 1
})
on.exit(tr_monitor$destroy(), add = TRUE)
# Starting values are both 0. Earlier I found that the tests behaved
# differently if I accessed the values of dr/tr before the first call to
# flushReact(). That bug was fixed, but to ensure that similar bugs don't
# appear undetected, we run this test with and without do_priming.
if (do_priming) {
expect_identical(isolate(dr()), 0)
expect_identical(isolate(tr()), 0)
}
# Pump timer and reactives for about 1.3 seconds
stopAt <- Sys.time() + 1.3
while (Sys.time() < stopAt) {
timerCallbacks$executeElapsed()
flushReact()
Sys.sleep(0.001)
}
# dr() should not have had time to fire, other than the initial run, since
# there haven't been long enough gaps between invalidations.
expect_identical(dr_fired, 1)
# The value of dr() should not have updated either.
expect_identical(isolate(dr()), 0)
# tr() however, has had time to fire multiple times and update its value.
expect_identical(tr_fired, 3)
expect_identical(isolate(tr()), 10)
# Now let some time pass without any more updates.
src$destroy() # No more updates
stopAt <- Sys.time() + 1
while (Sys.time() < stopAt) {
timerCallbacks$executeElapsed()
flushReact()
Sys.sleep(0.001)
}
# dr should've fired, and we should have converged on the right answer.
expect_identical(dr_fired, 2)
isolate(expect_identical(rv$a, dr()))
expect_identical(tr_fired, 4)
isolate(expect_identical(rv$a, tr()))
})
}
test_that("reactive domain works across async handlers", {
obj <- new.env()

View File

@@ -129,13 +129,9 @@ test_that("message logger appears", {
test_that("reactlog_version is as expected", {
suggests <- strsplit(packageDescription("shiny")$Suggests, ",")[[1]]
reactlog <- trimws(
grep("reactlog", suggests, value = TRUE)
)
expect_length(reactlog, 1)
expect_equal(
reactlog,
sprintf("reactlog (>= %s)", reactlog_min_version)
expect_match(
packageDescription("shiny")$Suggests,
# The space between reactlog and the version number can include \n
sprintf("\\breactlog\\s+\\Q(>= %s)\\E", reactlog_min_version)
)
})

View File

@@ -29,8 +29,8 @@ test_that("Render functions correctly handle quosures", {
r1 <- inject(renderTable({ pressure[!!a, ] }, digits = 1))
r2 <- renderTable({ eval_tidy(quo(pressure[!!a, ])) }, digits = 1)
a <- 2
expect_true(grepl("0\\.0", r1()))
expect_true(grepl("20\\.0", r2()))
expect_match(r1(), "0\\.0")
expect_match(r2(), "20\\.0")
})
test_that("functionLabel returns static value when the label can not be assigned to", {

View File

@@ -1,3 +1,47 @@
formatError <- function(err, full = FALSE, offset = TRUE, cleanPaths = TRUE) {
# This complicated capturing code is necessary because printStackTrace uses a
# combination of `message()` and `cat(file=stderr())` to print the error,
# stack traces, and stack trace boundaries ("From earlier call:"). We want to
# treat all of it as part of the same string.
str <- noquote(capture.output(
suppressWarnings(
suppressMessages(
withCallingHandlers(
printError(err, full = full, offset = offset),
warning = function(cnd) {
cat(conditionMessage(cnd), "\n", sep = "", file = stderr())
},
message = function(cnd) {
cat(conditionMessage(cnd), file = stderr())
}
)
)
),
type = "message"
))
# Remove directories and line numbers from file/line references, e.g.
# 53: callback [/Users/jcheng/Development/rstudio/shiny/R/conditions.R#155]
# becomes
# 53: callback [conditions.R#XXX]
#
# This is to make the snapshot tests more stable across different machines and
# ignores benign code movement within a file.
str <- sub("#\\d+\\]$", "#XXX]", str, perl = TRUE)
# Remove any file/line number reference that's not test-stacks-deep.R. These
# are just too inconsistent across different ways of invoking testthat--not
# relative vs. absolute paths, but whether the file/line number is included at
# all!
str <- sub(" \\[(?!test-stacks-deep.R)[^[]+#XXX\\]", "", str, perl = TRUE)
# The frame numbers vary too much between different ways of invoking testthat
# ("Run Tests" editor toolbar button and "Test" Build tab button in RStudio,
# devtools::test(), etc.) so we blank them out.
str <- sub("^[ \\d]+:", " :", str, perl = TRUE)
str
}
describe("deep stack trace filtering", {
it("passes smoke test", {
st <- list(
@@ -43,3 +87,170 @@ describe("deep stack trace filtering", {
)
})
})
test_that("deep stack capturing", {
`%...>%` <- promises::`%...>%`
`%...!%` <- promises::`%...!%`
finally <- promises::finally
err <- NULL
captureStackTraces({
promise_resolve("one") %...>% {
promise_reject("error") %...!% {
finally(promise_resolve("two"), ~{
stop("boom")
})
}
}
}) %...!% (function(err) {
err <<- err
})
wait_for_it()
expect_s3_class(err, "error", exact = FALSE)
expect_snapshot(cat(sep="\n", formatError(err)))
expect_snapshot(cat(sep="\n", formatError(err, full = TRUE)))
})
test_that("deep stack capturing within reactives", {
rerr <- NULL
observe({
promise_resolve("one") %...>% {
promise_resolve("two") %...>% {
stop("boom")
}
} %...!% (function(err) {
rerr <<- err
})
})
flushReact()
wait_for_it()
expect_s3_class(rerr, "error", exact = FALSE)
expect_length(attr(rerr, "deep.stack.trace"), 2)
})
test_that("deep stacks long chain", {
op <- options(shiny.deepstacktrace = 3L)
on.exit(options(op), add = TRUE, after = FALSE)
# Without deep stack traces, the stack trace would give no clue that the error
# originally started from a call to `A__()`. With deep stack traces, we can
# see that the error originated from `A__` and passed through `I__` and `J__`.
# But due to culling, we don't see `B__` through `H__`--these are omitted for
# brevity and to prevent unbounded growth of the accounting we do.
A__ <- function() promise_resolve(TRUE) %...>% B__()
B__ <- function(x) promise_resolve(TRUE) %...>% C__()
C__ <- function(x) promise_resolve(TRUE) %...>% D__()
D__ <- function(x) promise_resolve(TRUE) %...>% E__()
E__ <- function(x) promise_resolve(TRUE) %...>% F__()
F__ <- function(x) promise_resolve(TRUE) %...>% G__()
G__ <- function(x) promise_resolve(TRUE) %...>% H__()
H__ <- function(x) promise_resolve(TRUE) %...>% I__()
I__ <- function(x) promise_resolve(TRUE) %...>% J__()
J__ <- function(x) promise_resolve(TRUE) %...>% { stop("boom") }
dserr <- NULL
captureStackTraces(
A__()
) %...!% (function(err) {
dserr <<- err
})
wait_for_it()
expect_s3_class(dserr, "error", exact = FALSE)
expect_snapshot(cat(sep="\n", stacktrace <- formatError(dserr)))
# Ensure we dropTrivialTestFrames only when snapshotting
expect_false(length(stacktrace) == length(formatError(dserr)))
# Ensure that A__ through J__ are present in the traces
for (letter in LETTERS[1:10]) {
expect_length(which(grepl(paste0(letter, "__"), stacktrace)), 1L)
}
})
test_that("Deep stack deduplication", {
recursive_promise <- function(n) {
if (n <= 0) {
stop("boom")
}
p <- promises::promise_resolve(TRUE)
promises::then(p, ~{
recursive_promise(n - 1)
})
}
op <- options(shiny.deepstacktrace = TRUE)
on.exit(options(op), add = TRUE, after = FALSE)
uerr <- NULL
captureStackTraces(recursive_promise(100)) %...!% (function(err) {
uerr <<- err
})
wait_for_it()
expect_s3_class(uerr, "error", exact = FALSE)
# Even though we traveled through 100 promises recursively, we only retained
# the unique ones
expect_identical(length(attr(uerr, "deep.stack.trace", exact = TRUE)), 2L)
})
test_that("stack trace stripping works", {
A__ <- function() promise_resolve(TRUE) %...>% B__()
B__ <- function(x) promise_resolve(TRUE) %...>% { ..stacktraceoff..(C__()) }
C__ <- function(x) promise_resolve(TRUE) %...>% D__()
D__ <- function(x) promise_resolve(TRUE) %...>% { ..stacktraceon..(E__()) }
E__ <- function(x) promise_resolve(TRUE) %...>% { stop("boom") }
strperr <- NULL
captureStackTraces(A__()) %...!% (function(err) {
strperr <<- err
})
..stacktracefloor..(
wait_for_it()
)
expect_s3_class(strperr, "error", exact = FALSE)
str <- formatError(strperr)
expect_length(which(grepl("A__", str)), 1L)
expect_length(which(grepl("B__", str)), 1L)
expect_length(which(grepl("C__", str)), 0L)
expect_length(which(grepl("D__", str)), 0L)
expect_length(which(grepl("E__", str)), 1L)
str_full <- formatError(strperr, full = TRUE)
expect_length(which(grepl("A__", str_full)), 1L)
expect_length(which(grepl("B__", str_full)), 1L)
expect_length(which(grepl("C__", str_full)), 1L)
expect_length(which(grepl("D__", str_full)), 1L)
expect_length(which(grepl("E__", str_full)), 1L)
})
test_that("coro async generator deep stack count is low", {
gen <- coro::async_generator(function() {
for (i in 1:50) {
await(coro::async_sleep(0.001))
yield(i)
}
stop("boom")
})
cgerr <- NULL
captureStackTraces(
coro::async_collect(gen()) %...!% (function(err) {
cgerr <<- err
})
)
wait_for_it()
expect_s3_class(cgerr, "error", exact = FALSE)
expect_length(attr(cgerr, "deep.stack.trace"), 2L)
})

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