From 2a7adf2c2751f718a40540ba143d4df27fa140f3 Mon Sep 17 00:00:00 2001 From: Carson Date: Fri, 13 Dec 2024 10:45:42 -0600 Subject: [PATCH 1/6] Make sure spinner is visible when htmlwidget errors are visible --- .../extras/busy-indicators/busy-indicators.scss | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/srcts/extras/busy-indicators/busy-indicators.scss b/srcts/extras/busy-indicators/busy-indicators.scss index 98f556c1f..29065148b 100644 --- a/srcts/extras/busy-indicators/busy-indicators.scss +++ b/srcts/extras/busy-indicators/busy-indicators.scss @@ -45,6 +45,23 @@ 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 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:has( + .shiny-output-error) { + 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. From 22d13df61f03dd2f05e6ce8611f4b11dd2cc683b Mon Sep 17 00:00:00 2001 From: Carson Date: Fri, 13 Dec 2024 10:47:05 -0600 Subject: [PATCH 2/6] Give recalculating outputs a min-height large enough to show the spinner --- srcts/extras/busy-indicators/busy-indicators.scss | 2 ++ 1 file changed, 2 insertions(+) diff --git a/srcts/extras/busy-indicators/busy-indicators.scss b/srcts/extras/busy-indicators/busy-indicators.scss index 29065148b..9a1fea227 100644 --- a/srcts/extras/busy-indicators/busy-indicators.scss +++ b/srcts/extras/busy-indicators/busy-indicators.scss @@ -7,6 +7,8 @@ .recalculating { + min-height: var(--shiny-spinner-size, 32px); + &::after { position: absolute; content: ""; From 3b0097d745c7f2c2643913b03b84921e49d013be Mon Sep 17 00:00:00 2001 From: Carson Date: Fri, 13 Dec 2024 10:49:15 -0600 Subject: [PATCH 3/6] tableOutput() now gets the spinner treatment --- R/bootstrap.R | 2 +- srcts/extras/busy-indicators/busy-indicators.scss | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/R/bootstrap.R b/R/bootstrap.R index 970b43400..23aeb05be 100644 --- a/R/bootstrap.R +++ b/R/bootstrap.R @@ -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( diff --git a/srcts/extras/busy-indicators/busy-indicators.scss b/srcts/extras/busy-indicators/busy-indicators.scss index 9a1fea227..f431b9a6f 100644 --- a/srcts/extras/busy-indicators/busy-indicators.scss +++ b/srcts/extras/busy-indicators/busy-indicators.scss @@ -70,7 +70,7 @@ 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; } } @@ -124,6 +124,9 @@ &.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 { display: none; } From 99e1327a8b035ce2c93acd15c57fed322fca099f Mon Sep 17 00:00:00 2001 From: Carson Date: Fri, 13 Dec 2024 10:49:32 -0600 Subject: [PATCH 4/6] yarn run bundle_extras --- inst/www/shared/busy-indicators/busy-indicators.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/inst/www/shared/busy-indicators/busy-indicators.css b/inst/www/shared/busy-indicators/busy-indicators.css index f29303d16..c4794eb2a 100644 --- a/inst/www/shared/busy-indicators/busy-indicators.css +++ b/inst/www/shared/busy-indicators/busy-indicators.css @@ -1,2 +1,2 @@ /*! shiny 1.9.1.9000 | (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, 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(#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} +: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:has(+.shiny-output-error){visibility:inherit!important}[data-shiny-busy-spinners] .recalculating.html-widget-output:has(+.shiny-output-error)>*{visibility:hidden}[data-shiny-busy-spinners] .recalculating.html-widget-output:has(+.shiny-output-error) :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} From 7904545fa4f893b1626631690fad614e151129cd Mon Sep 17 00:00:00 2001 From: Carson Date: Fri, 13 Dec 2024 11:17:42 -0600 Subject: [PATCH 5/6] Forward visibility hidden for all recalculating widgets, not just those with a error message (otherwise spinner won't be visible after a req()) --- inst/www/shared/busy-indicators/busy-indicators.css | 2 +- srcts/extras/busy-indicators/busy-indicators.scss | 13 +++++++------ 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/inst/www/shared/busy-indicators/busy-indicators.css b/inst/www/shared/busy-indicators/busy-indicators.css index c4794eb2a..f6f58d327 100644 --- a/inst/www/shared/busy-indicators/busy-indicators.css +++ b/inst/www/shared/busy-indicators/busy-indicators.css @@ -1,2 +1,2 @@ /*! shiny 1.9.1.9000 | (c) 2012-2024 RStudio, 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:has(+.shiny-output-error){visibility:inherit!important}[data-shiny-busy-spinners] .recalculating.html-widget-output:has(+.shiny-output-error)>*{visibility:hidden}[data-shiny-busy-spinners] .recalculating.html-widget-output:has(+.shiny-output-error) :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} +: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} diff --git a/srcts/extras/busy-indicators/busy-indicators.scss b/srcts/extras/busy-indicators/busy-indicators.scss index f431b9a6f..78a5040e4 100644 --- a/srcts/extras/busy-indicators/busy-indicators.scss +++ b/srcts/extras/busy-indicators/busy-indicators.scss @@ -48,13 +48,14 @@ } /* - When htmlwidget errors are rendered, an inline `visibility:hidden` is put on the - html-widget-output, and the error 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. + 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:has( + .shiny-output-error) { + &.html-widget-output { visibility: inherit !important; > * { visibility: hidden; From 75832be3bc1a44442242a7002ef7113ffe43f121 Mon Sep 17 00:00:00 2001 From: Carson Date: Fri, 13 Dec 2024 11:25:43 -0600 Subject: [PATCH 6/6] Update news --- NEWS.md | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/NEWS.md b/NEWS.md index 93bab763a..e71e31244 100644 --- a/NEWS.md +++ b/NEWS.md @@ -2,9 +2,12 @@ ## New features and improvements -* Small improvements to the default pulse busy indicator to better blend with any background. It's also now slightly smaller by default. (#4122) - -* When spinners and the pulse busy indicators are enabled, Shiny now shows the pulse indicator when dynamic UI elements are recalculating if no other spinners are present in the app. (#4137) +* 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) + * Shows the pulse indicator when dynamic UI elements are recalculating if no other spinners are present in the app. (#4137) + * The pulse indicator now blends in better with any background color. It's also slightly smaller by default. (#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)