@@ -50,7 +50,7 @@ bootstrapPage <- function(..., title = NULL, theme = NULL, lang = NULL) {
5050 # the tagList() contents to avoid breaking user code that makes assumptions
5151 # about the return value https://github.com/rstudio/shiny/issues/3235
5252 if (is_bs_theme(theme )) {
53- args <- c (bootstrapLib(theme ), args )
53+ args <- list (bootstrapLib(theme ), args )
5454 ui <- do.call(tagList , args )
5555 } else {
5656 ui <- do.call(tagList , args )
@@ -82,53 +82,25 @@ getLang <- function(ui) {
8282# ' @inheritParams bootstrapPage
8383# ' @export
8484bootstrapLib <- function (theme = NULL ) {
85- tagFunction(function () {
86- if (isRunning()) {
85+ # To support static rendering of version dependent markup (e.g.,
86+ # tabsetPanel()), we setCurrentTheme() at the start of the render (since
87+ # bootstrapLib() comes first in bootstrapPage()), and cleanup afterwards if
88+ # shiny isn't running (in that case, since setCurrentTheme() uses
89+ # shinyOptions(), state will be automatically be restored when the app exits)
90+ oldTheme <- NULL
91+ tagList(
92+ .renderHook = function (x ) {
93+ oldTheme <<- getCurrentTheme()
8794 setCurrentTheme(theme )
95+ # For refreshing Bootstrap CSS when session$setCurrentTheme() happens
96+ if (isRunning()) registerThemeDependency(bs_theme_deps )
97+ bslib :: bs_theme_dependencies(theme )
98+ },
99+ .postRenderHook = function (x ) {
100+ if (! isRunning()) setCurrentTheme(oldTheme )
101+ NULL
88102 }
89-
90- # If we're not compiling Bootstrap Sass (from bslib), return the
91- # static Bootstrap build.
92- if (! is_bs_theme(theme )) {
93- # We'll enter here if `theme` is the path to a .css file, like that
94- # provided by `shinythemes::shinytheme("darkly")`.
95- return (bootstrapDependency(theme ))
96- }
97-
98- # Make bootstrap Sass available so other tagFunction()s (e.g.,
99- # sliderInput() et al) can resolve their HTML dependencies at render time
100- # using getCurrentTheme(). Note that we're making an implicit assumption
101- # that this tagFunction() executes *before* all other tagFunction()s; but
102- # that should be fine considering that, DOM tree order is preorder,
103- # depth-first traversal, and at least in the bootstrapPage(theme) case, we
104- # have control over the relative ordering.
105- # https://dom.spec.whatwg.org/#concept-tree
106- # https://stackoverflow.com/a/16113998/1583084
107- #
108- # Note also that since this is shinyOptions() (and not options()), the
109- # option is automatically reset when the app (or session) exits
110- if (isRunning()) {
111- registerThemeDependency(bs_theme_deps )
112-
113- } else {
114- # Technically, this a potential issue (someone trying to execute/render
115- # bootstrapLib outside of a Shiny app), but it seems that, in that case,
116- # you likely have other problems, since sliderInput() et al. already assume
117- # that Shiny is the one doing the rendering
118- # warning(
119- # "It appears `shiny::bootstrapLib()` was rendered outside of an Shiny ",
120- # "application context, likely by calling `as.tags()`, `as.character()`, ",
121- # "or `print()` directly on `bootstrapLib()` or UI components that may ",
122- # "depend on it (e.g., `fluidPage()`, etc). For 'themable' UI components ",
123- # "(e.g., `sliderInput()`, `selectInput()`, `dateInput()`, etc) to style ",
124- # "themselves based on the Bootstrap theme, make sure `bootstrapLib()` is ",
125- # "provided directly to the UI and that the UI is provided direction to ",
126- # "`shinyApp()` (or `runApp()`)", call. = FALSE
127- # )
128- }
129-
130- bslib :: bs_theme_dependencies(theme )
131- })
103+ )
132104}
133105
134106# This is defined outside of bootstrapLib() because registerThemeDependency()
0 commit comments