mirror of
https://github.com/rstudio/shiny.git
synced 2026-01-10 07:28:01 -05:00
Various improvements to tab panels (#3315)
* 'Native' Bootstrap 4 tabset panel support * downgrade error to warning; improve the messaging * Make tab anchor selectors more a bit more sensible and consistent across versions * More of the same * fix silly bug * Be more careful about unpacking a .nav-item into a .dropdown-item * Keep refactoring R logic to make it cleaner and easier to reuse elsewhere * Go back to the purely class based CSS selectors for BS4 tab input * Keep supporting off-label behavior of shiny.tag getting transformed into 'empty' nav/tab * Add header and footer args to tabsetPanel()/navlistPanel() since there is precedence in navbarPage() and mention them in the warning * Drop NULLs instead of creating an empty nav from them, closes #1928 * Remove tabPanelMenu() alias * Add a card argument for wrapping content in a card * Throw an error if card=T is used outside of a BS4+ context * No more tabPanelMenu() alias * Document (GitHub Actions) * Port JS changes to TypeScript * Allow liTag to be assigned a new value * abort() is no longer being used * Add some unit tests * Document the new card argument * Get tests passing on older R versions * Get tests passing on older R versions * Get tests passing on older R versions * Skip snapshots on R < 3.6 * require dev version of htmltools * remove card argument (at least for now) * Document (GitHub Actions) * Update tests/testthat/test-tabPanel.R Co-authored-by: Winston Chang <winston@stdout.org> * Have processDeps() call renderTags() on tagFunction() objects Co-authored-by: cpsievert <cpsievert@users.noreply.github.com> Co-authored-by: Winston Chang <winston@stdout.org>
This commit is contained in:
@@ -408,6 +408,7 @@ importFrom(rlang,is_false)
|
||||
importFrom(rlang,is_missing)
|
||||
importFrom(rlang,is_na)
|
||||
importFrom(rlang,is_quosure)
|
||||
importFrom(rlang,list2)
|
||||
importFrom(rlang,maybe_missing)
|
||||
importFrom(rlang,missing_arg)
|
||||
importFrom(rlang,new_function)
|
||||
|
||||
344
R/bootstrap.R
344
R/bootstrap.R
@@ -163,6 +163,15 @@ getCurrentTheme <- function() {
|
||||
getShinyOption("bootstrapTheme", default = NULL)
|
||||
}
|
||||
|
||||
getCurrentVersion <- function() {
|
||||
theme <- getCurrentTheme()
|
||||
if (bslib::is_bs_theme(theme)) {
|
||||
bslib::theme_version(theme)
|
||||
} else {
|
||||
strsplit(bootstrapVersion, ".", fixed = TRUE)[[1]][[1]]
|
||||
}
|
||||
}
|
||||
|
||||
setCurrentTheme <- function(theme) {
|
||||
shinyOptions(bootstrapTheme = theme)
|
||||
}
|
||||
@@ -211,7 +220,7 @@ registerThemeDependency <- function(func) {
|
||||
|
||||
bootstrapDependency <- function(theme) {
|
||||
htmlDependency(
|
||||
"bootstrap", "3.4.1",
|
||||
"bootstrap", bootstrapVersion,
|
||||
c(
|
||||
href = "shared/bootstrap",
|
||||
file = system.file("www/shared/bootstrap", package = "shiny")
|
||||
@@ -230,6 +239,8 @@ bootstrapDependency <- function(theme) {
|
||||
)
|
||||
}
|
||||
|
||||
bootstrapVersion <- "3.4.1"
|
||||
|
||||
|
||||
#' @rdname bootstrapPage
|
||||
#' @export
|
||||
@@ -436,10 +447,11 @@ navbarPage <- function(title,
|
||||
pageTitle <- title
|
||||
|
||||
# navbar class based on options
|
||||
# TODO: tagFunction() the navbar logic?
|
||||
navbarClass <- "navbar navbar-default"
|
||||
position <- match.arg(position)
|
||||
if (!is.null(position))
|
||||
navbarClass <- paste(navbarClass, " navbar-", position, sep = "")
|
||||
navbarClass <- paste0(navbarClass, " navbar-", position)
|
||||
if (inverse)
|
||||
navbarClass <- paste(navbarClass, "navbar-inverse")
|
||||
|
||||
@@ -447,21 +459,14 @@ navbarPage <- function(title,
|
||||
selected <- restoreInput(id = id, default = selected)
|
||||
|
||||
# build the tabset
|
||||
tabs <- list(...)
|
||||
tabset <- buildTabset(tabs, "nav navbar-nav", NULL, id, selected)
|
||||
tabset <- buildTabset(..., ulClass = "nav navbar-nav", id = id, selected = selected)
|
||||
|
||||
# function to return plain or fluid class name
|
||||
className <- function(name) {
|
||||
if (fluid)
|
||||
paste(name, "-fluid", sep="")
|
||||
else
|
||||
name
|
||||
}
|
||||
containerClass <- paste0("container", if (fluid) "-fluid")
|
||||
|
||||
# built the container div dynamically to support optional collapsibility
|
||||
if (collapsible) {
|
||||
navId <- paste("navbar-collapse-", p_randomInt(1000, 10000), sep="")
|
||||
containerDiv <- div(class=className("container"),
|
||||
navId <- paste0("navbar-collapse-", p_randomInt(1000, 10000))
|
||||
containerDiv <- div(class=containerClass,
|
||||
div(class="navbar-header",
|
||||
tags$button(type="button", class="navbar-toggle collapsed",
|
||||
`data-toggle`="collapse", `data-target`=paste0("#", navId),
|
||||
@@ -475,7 +480,7 @@ navbarPage <- function(title,
|
||||
div(class="navbar-collapse collapse", id=navId, tabset$navList)
|
||||
)
|
||||
} else {
|
||||
containerDiv <- div(class=className("container"),
|
||||
containerDiv <- div(class=containerClass,
|
||||
div(class="navbar-header",
|
||||
span(class="navbar-brand", pageTitle)
|
||||
),
|
||||
@@ -484,7 +489,7 @@ navbarPage <- function(title,
|
||||
}
|
||||
|
||||
# build the main tab content div
|
||||
contentDiv <- div(class=className("container"))
|
||||
contentDiv <- div(class=containerClass)
|
||||
if (!is.null(header))
|
||||
contentDiv <- tagAppendChild(contentDiv, div(class="row", header))
|
||||
contentDiv <- tagAppendChild(contentDiv, tabset$content)
|
||||
@@ -511,11 +516,15 @@ navbarPage <- function(title,
|
||||
navbarMenu <- function(title, ..., menuName = title, icon = NULL) {
|
||||
structure(list(title = title,
|
||||
menuName = menuName,
|
||||
tabs = list(...),
|
||||
tabs = list2(...),
|
||||
iconClass = iconClass(icon)),
|
||||
class = "shiny.navbarmenu")
|
||||
}
|
||||
|
||||
isNavbarMenu <- function(x) {
|
||||
inherits(x, "shiny.navbarmenu")
|
||||
}
|
||||
|
||||
#' Create a well panel
|
||||
#'
|
||||
#' Creates a panel with a slightly inset border and grey background. Equivalent
|
||||
@@ -656,6 +665,13 @@ tabPanel <- function(title, ..., value = title, icon = NULL) {
|
||||
...
|
||||
)
|
||||
}
|
||||
|
||||
isTabPanel <- function(x) {
|
||||
if (!inherits(x, "shiny.tag")) return(FALSE)
|
||||
class <- tagGetAttribute(x, "class") %||% ""
|
||||
"tab-pane" %in% strsplit(class, "\\s+")[[1]]
|
||||
}
|
||||
|
||||
#' @export
|
||||
#' @describeIn tabPanel Create a tab panel that drops the title argument.
|
||||
#' This function should be used within `tabsetPanel(type = "hidden")`. See [tabsetPanel()] for example usage.
|
||||
@@ -693,6 +709,7 @@ tabPanelBody <- function(value, ..., icon = NULL) {
|
||||
#' }
|
||||
#' @param position This argument is deprecated; it has been discontinued in
|
||||
#' Bootstrap 3.
|
||||
#' @inheritParams navbarPage
|
||||
#' @return A tabset that can be passed to [mainPanel()]
|
||||
#'
|
||||
#' @seealso [tabPanel()], [updateTabsetPanel()],
|
||||
@@ -742,6 +759,8 @@ tabsetPanel <- function(...,
|
||||
id = NULL,
|
||||
selected = NULL,
|
||||
type = c("tabs", "pills", "hidden"),
|
||||
header = NULL,
|
||||
footer = NULL,
|
||||
position = deprecated()) {
|
||||
if (lifecycle::is_present(position)) {
|
||||
shinyDeprecated(
|
||||
@@ -753,18 +772,18 @@ tabsetPanel <- function(...,
|
||||
if (!is.null(id))
|
||||
selected <- restoreInput(id = id, default = selected)
|
||||
|
||||
# build the tabset
|
||||
tabs <- list(...)
|
||||
type <- match.arg(type)
|
||||
tabset <- buildTabset(..., ulClass = paste0("nav nav-", type), id = id, selected = selected)
|
||||
|
||||
tabset <- buildTabset(tabs, paste0("nav nav-", type), NULL, id, selected)
|
||||
|
||||
# create the content
|
||||
first <- tabset$navList
|
||||
second <- tabset$content
|
||||
|
||||
# create the tab div
|
||||
tags$div(class = "tabbable", first, second)
|
||||
tags$div(
|
||||
class = "tabbable",
|
||||
!!!dropNulls(list(
|
||||
tabset$navList,
|
||||
header,
|
||||
tabset$content,
|
||||
footer
|
||||
))
|
||||
)
|
||||
}
|
||||
|
||||
#' Create a navigation list panel
|
||||
@@ -784,8 +803,10 @@ tabsetPanel <- function(...,
|
||||
#' navigation list.
|
||||
#' @param fluid `TRUE` to use fluid layout; `FALSE` to use fixed
|
||||
#' layout.
|
||||
#' @param widths Column withs of the navigation list and tabset content areas
|
||||
#' @param widths Column widths of the navigation list and tabset content areas
|
||||
#' respectively.
|
||||
#' @inheritParams tabsetPanel
|
||||
#' @inheritParams navbarPage
|
||||
#'
|
||||
#' @details You can include headers within the `navlistPanel` by including
|
||||
#' plain text elements in the list. Versions of Shiny before 0.11 supported
|
||||
@@ -812,37 +833,30 @@ tabsetPanel <- function(...,
|
||||
navlistPanel <- function(...,
|
||||
id = NULL,
|
||||
selected = NULL,
|
||||
header = NULL,
|
||||
footer = NULL,
|
||||
well = TRUE,
|
||||
fluid = TRUE,
|
||||
widths = c(4, 8)) {
|
||||
|
||||
# text filter for headers
|
||||
textFilter <- function(text) {
|
||||
tags$li(class="navbar-brand", text)
|
||||
}
|
||||
|
||||
if (!is.null(id))
|
||||
selected <- restoreInput(id = id, default = selected)
|
||||
|
||||
# build the tabset
|
||||
tabs <- list(...)
|
||||
tabset <- buildTabset(tabs,
|
||||
"nav nav-pills nav-stacked",
|
||||
textFilter,
|
||||
id,
|
||||
selected)
|
||||
|
||||
# create the columns
|
||||
columns <- list(
|
||||
column(widths[[1]], class=ifelse(well, "well", ""), tabset$navList),
|
||||
column(widths[[2]], tabset$content)
|
||||
tabset <- buildTabset(
|
||||
..., ulClass = "nav nav-pills nav-stacked",
|
||||
textFilter = function(text) tags$li(class = "navbar-brand", text),
|
||||
id = id, selected = selected
|
||||
)
|
||||
|
||||
# return the row
|
||||
if (fluid)
|
||||
fluidRow(columns)
|
||||
else
|
||||
fixedRow(columns)
|
||||
row <- if (fluid) fluidRow else fixedRow
|
||||
|
||||
row(
|
||||
column(widths[[1]], class = if (well) "well", tabset$navList),
|
||||
column(
|
||||
widths[[2]],
|
||||
!!!dropNulls(list(header, tabset$content, footer))
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
# Helpers to build tabsetPanels (& Co.) and their elements
|
||||
@@ -860,14 +874,14 @@ containsSelectedTab <- function(tabs) {
|
||||
}
|
||||
|
||||
findAndMarkSelectedTab <- function(tabs, selected, foundSelected) {
|
||||
tabs <- lapply(tabs, function(div) {
|
||||
if (foundSelected || is.character(div)) {
|
||||
tabs <- lapply(tabs, function(x) {
|
||||
if (foundSelected || is.character(x)) {
|
||||
# Strings are not selectable items
|
||||
|
||||
} else if (inherits(div, "shiny.navbarmenu")) {
|
||||
} else if (isNavbarMenu(x)) {
|
||||
# Recur for navbarMenus
|
||||
res <- findAndMarkSelectedTab(div$tabs, selected, foundSelected)
|
||||
div$tabs <- res$tabs
|
||||
res <- findAndMarkSelectedTab(x$tabs, selected, foundSelected)
|
||||
x$tabs <- res$tabs
|
||||
foundSelected <<- res$foundSelected
|
||||
|
||||
} else {
|
||||
@@ -876,16 +890,16 @@ findAndMarkSelectedTab <- function(tabs, selected, foundSelected) {
|
||||
# mark first available item as selected
|
||||
if (is.null(selected)) {
|
||||
foundSelected <<- TRUE
|
||||
div <- markTabAsSelected(div)
|
||||
x <- markTabAsSelected(x)
|
||||
} else {
|
||||
tabValue <- div$attribs$`data-value` %||% div$attribs$title
|
||||
tabValue <- x$attribs$`data-value` %||% x$attribs$title
|
||||
if (identical(selected, tabValue)) {
|
||||
foundSelected <<- TRUE
|
||||
div <- markTabAsSelected(div)
|
||||
x <- markTabAsSelected(x)
|
||||
}
|
||||
}
|
||||
}
|
||||
return(div)
|
||||
return(x)
|
||||
})
|
||||
return(list(tabs = tabs, foundSelected = foundSelected))
|
||||
}
|
||||
@@ -911,9 +925,10 @@ navbarMenuTextFilter <- function(text) {
|
||||
|
||||
# This function is called internally by navbarPage, tabsetPanel
|
||||
# and navlistPanel
|
||||
buildTabset <- function(tabs, ulClass, textFilter = NULL, id = NULL,
|
||||
buildTabset <- function(..., ulClass, textFilter = NULL, id = NULL,
|
||||
selected = NULL, foundSelected = FALSE) {
|
||||
|
||||
tabs <- dropNulls(list2(...))
|
||||
res <- findAndMarkSelectedTab(tabs, selected, foundSelected)
|
||||
tabs <- res$tabs
|
||||
foundSelected <- res$foundSelected
|
||||
@@ -934,10 +949,10 @@ buildTabset <- function(tabs, ulClass, textFilter = NULL, id = NULL,
|
||||
tabs = tabs, textFilter = textFilter)
|
||||
|
||||
tabNavList <- tags$ul(class = ulClass, id = id,
|
||||
`data-tabsetid` = tabsetId, lapply(tabs, "[[", 1))
|
||||
`data-tabsetid` = tabsetId, !!!lapply(tabs, "[[", "liTag"))
|
||||
|
||||
tabContent <- tags$div(class = "tab-content",
|
||||
`data-tabsetid` = tabsetId, lapply(tabs, "[[", 2))
|
||||
`data-tabsetid` = tabsetId, !!!lapply(tabs, "[[", "divTag"))
|
||||
|
||||
list(navList = tabNavList, content = tabContent)
|
||||
}
|
||||
@@ -949,56 +964,173 @@ buildTabset <- function(tabs, ulClass, textFilter = NULL, id = NULL,
|
||||
buildTabItem <- function(index, tabsetId, foundSelected, tabs = NULL,
|
||||
divTag = NULL, textFilter = NULL) {
|
||||
|
||||
divTag <- if (!is.null(divTag)) divTag else tabs[[index]]
|
||||
divTag <- divTag %||% tabs[[index]]
|
||||
|
||||
# Handles navlistPanel() headers and dropdown dividers
|
||||
if (is.character(divTag) && !is.null(textFilter)) {
|
||||
# text item: pass it to the textFilter if it exists
|
||||
liTag <- textFilter(divTag)
|
||||
divTag <- NULL
|
||||
|
||||
} else if (inherits(divTag, "shiny.navbarmenu")) {
|
||||
# navbarMenu item: build the child tabset
|
||||
tabset <- buildTabset(divTag$tabs, "dropdown-menu",
|
||||
navbarMenuTextFilter, foundSelected = foundSelected)
|
||||
|
||||
# if this navbarMenu contains a selected item, mark it active
|
||||
containsSelected <- containsSelectedTab(divTag$tabs)
|
||||
liTag <- tags$li(
|
||||
class = paste0("dropdown", if (containsSelected) " active"),
|
||||
tags$a(href = "#",
|
||||
class = "dropdown-toggle", `data-toggle` = "dropdown",
|
||||
`data-value` = divTag$menuName,
|
||||
getIcon(iconClass = divTag$iconClass),
|
||||
divTag$title, tags$b(class = "caret")
|
||||
),
|
||||
tabset$navList # inner tabPanels items
|
||||
)
|
||||
# list of tab content divs from the child tabset
|
||||
divTag <- tabset$content$children
|
||||
|
||||
} else {
|
||||
# tabPanel item: create the tab's liTag and divTag
|
||||
tabId <- paste("tab", tabsetId, index, sep = "-")
|
||||
liTag <- tags$li(
|
||||
tags$a(
|
||||
href = paste("#", tabId, sep = ""),
|
||||
`data-toggle` = "tab",
|
||||
`data-value` = divTag$attribs$`data-value`,
|
||||
getIcon(iconClass = divTag$attribs$`data-icon-class`),
|
||||
divTag$attribs$title
|
||||
)
|
||||
)
|
||||
# if this tabPanel is selected item, mark it active
|
||||
if (isTabSelected(divTag)) {
|
||||
liTag$attribs$class <- "active"
|
||||
divTag$attribs$class <- "tab-pane active"
|
||||
}
|
||||
divTag$attribs$id <- tabId
|
||||
divTag$attribs$title <- NULL
|
||||
return(list(liTag = textFilter(divTag), divTag = NULL))
|
||||
}
|
||||
return(list(liTag = liTag, divTag = divTag))
|
||||
|
||||
if (isNavbarMenu(divTag)) {
|
||||
# tabPanelMenu item: build the child tabset
|
||||
tabset <- buildTabset(
|
||||
!!!divTag$tabs, ulClass = "dropdown-menu",
|
||||
textFilter = navbarMenuTextFilter,
|
||||
foundSelected = foundSelected
|
||||
)
|
||||
return(buildDropdown(divTag, tabset))
|
||||
}
|
||||
|
||||
if (isTabPanel(divTag)) {
|
||||
return(buildNavItem(divTag, tabsetId, index))
|
||||
}
|
||||
|
||||
# The behavior is undefined at this point, so construct a condition message
|
||||
msg <- paste0(
|
||||
"Expected a collection `tabPanel()`s",
|
||||
if (is.null(textFilter)) " and `navbarMenu()`.",
|
||||
if (!is.null(textFilter)) ", `navbarMenu()`, and/or character strings.",
|
||||
" Consider using `header` or `footer` if you wish to place content above (or below) every panel's contents"
|
||||
)
|
||||
|
||||
# Luckily this case has never worked, so it's safe to throw here
|
||||
# https://github.com/rstudio/shiny/issues/3313
|
||||
if (!inherits(divTag, "shiny.tag")) {
|
||||
stop(msg, call. = FALSE)
|
||||
}
|
||||
|
||||
# Unfortunately, this 'off-label' use case creates an 'empty' nav and includes
|
||||
# the divTag content on every tab. There shouldn't be any reason to be relying on
|
||||
# this behavior since we now have pre/post arguments, so throw a warning, but still
|
||||
# support the use case since we don't make breaking changes
|
||||
warning(msg, call. = FALSE)
|
||||
|
||||
return(buildNavItem(divTag, tabsetId, index))
|
||||
}
|
||||
|
||||
buildNavItem <- function(divTag, tabsetId, index) {
|
||||
id <- paste("tab", tabsetId, index, sep = "-")
|
||||
title <- tagGetAttribute(divTag, "title")
|
||||
value <- tagGetAttribute(divTag, "data-value")
|
||||
icon <- getIcon(iconClass = tagGetAttribute(divTag, "data-icon-class"))
|
||||
active <- isTabSelected(divTag)
|
||||
divTag <- tagAppendAttributes(divTag, class = if (active) "active")
|
||||
divTag$attribs$id <- id
|
||||
divTag$attribs$title <- NULL
|
||||
list(
|
||||
divTag = divTag,
|
||||
liTag = tagFunction(function() {
|
||||
navItem <- if ("3" %in% getCurrentVersion()) bs3NavItem else bs4NavItem
|
||||
navItem(id, title, value, icon, active)
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
buildDropdown <- function(divTag, tabset) {
|
||||
title <- divTag$title
|
||||
value <- divTag$menuName
|
||||
icon <- getIcon(iconClass = divTag$iconClass)
|
||||
active <- containsSelectedTab(divTag$tabs)
|
||||
list(
|
||||
# list of tab content divs from the child tabset
|
||||
divTag = tabset$content$children,
|
||||
liTag = tagFunction(function() {
|
||||
if ("3" %in% getCurrentVersion()) {
|
||||
bs3NavItemDropdown(title, value, icon, active, tabset$navList)
|
||||
} else {
|
||||
# In BS4, dropdown nav anchors can't be wrapped in a <li> tag
|
||||
# and also need .nav-link replaced with .dropdown-item to be
|
||||
# styled sensibly
|
||||
items <- tabset$navList
|
||||
items$children <- lapply(items$children, function(x) {
|
||||
# x should be a tagFunction() due to the else block below
|
||||
x <- if (inherits(x, "shiny.tag.function")) x() else x
|
||||
# Replace <li class="nav-item"><a class="nav-link"></a></li>
|
||||
# with <a class="dropdown-item"></a>
|
||||
if (tagHasClass(x, "nav-item")) {
|
||||
x <- x$children[[1]]
|
||||
x$attribs$class <- "dropdown-item"
|
||||
}
|
||||
x
|
||||
})
|
||||
bs4NavItemDropdown(title, value, icon, active, items)
|
||||
}
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
bs3NavItemDropdown <- function(title, value, icon, active, items) {
|
||||
tags$li(
|
||||
class = "dropdown",
|
||||
class = if (active) "active", # BS3
|
||||
tags$a(
|
||||
href = "#",
|
||||
class = "dropdown-toggle",
|
||||
`data-toggle` = "dropdown",
|
||||
`data-value` = value,
|
||||
icon,
|
||||
title, tags$b(class = "caret")
|
||||
),
|
||||
items
|
||||
)
|
||||
}
|
||||
|
||||
bs3NavItem <- function(id, title, value, icon, active) {
|
||||
tags$li(
|
||||
class = if (active) "active",
|
||||
tags$a(
|
||||
href = paste0("#", id),
|
||||
`data-toggle` = "tab",
|
||||
`data-value` = value,
|
||||
icon,
|
||||
title
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
bs4NavItemDropdown <- function(title, value, icon, active, items) {
|
||||
tags$li(
|
||||
class = "dropdown",
|
||||
class = "nav-item",
|
||||
tags$a(
|
||||
href = "#",
|
||||
class = "dropdown-toggle",
|
||||
class = "nav-link",
|
||||
class = if (active) "active",
|
||||
`data-toggle` = "dropdown",
|
||||
`data-value` = value,
|
||||
icon,
|
||||
title,
|
||||
tags$b(class = "caret") # TODO: can be removed?
|
||||
),
|
||||
items
|
||||
)
|
||||
}
|
||||
|
||||
bs4NavItem <- function(id, title, value, icon, active) {
|
||||
tags$li(
|
||||
class = "nav-item",
|
||||
tags$a(
|
||||
class = "nav-link",
|
||||
class = if (active) "active",
|
||||
href = paste0("#", id),
|
||||
`data-toggle` = "tab",
|
||||
`data-value` = value,
|
||||
icon,
|
||||
title
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
# TODO: something like this should exist in htmltools
|
||||
tagHasClass <- function(x, class) {
|
||||
if (!inherits(x, "shiny.tag")) return(FALSE)
|
||||
classes <- unlist(x$attribs[names(x$attribs) %in% "class"], use.names = FALSE)
|
||||
if (!length(classes)) return(FALSE)
|
||||
classes <- unlist(strsplit(classes, split = "\\s+"), use.names = FALSE)
|
||||
isTRUE(class %in% classes)
|
||||
}
|
||||
|
||||
#' Create a text output element
|
||||
#'
|
||||
|
||||
@@ -49,6 +49,12 @@ processDeps <- function(tags, session) {
|
||||
)
|
||||
names(dependencies) <- NULL
|
||||
|
||||
# If ui is a tagFunction() (e.g., insertTab() et al),
|
||||
# then doRenderTags() won't work...
|
||||
if (inherits(ui, "shiny.tag.function")) {
|
||||
ui <- renderTags(ui)$html
|
||||
}
|
||||
|
||||
list(
|
||||
html = doRenderTags(ui),
|
||||
deps = dependencies
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
#' quo enquo as_function get_expr get_env new_function enquos
|
||||
#' eval_tidy expr pairlist2 new_quosure enexpr as_quosure is_quosure inject
|
||||
#' enquos0 zap_srcref %||% is_na
|
||||
#' is_false
|
||||
#' is_false list2
|
||||
#' missing_arg is_missing maybe_missing
|
||||
#' @importFrom ellipsis
|
||||
#' check_dots_empty check_dots_unnamed
|
||||
|
||||
@@ -1198,6 +1198,26 @@
|
||||
};
|
||||
});
|
||||
|
||||
// node_modules/core-js/internals/regexp-exec-abstract.js
|
||||
var require_regexp_exec_abstract = __commonJS(function(exports2, module2) {
|
||||
var classof2 = require_classof_raw();
|
||||
var regexpExec2 = require_regexp_exec();
|
||||
module2.exports = function(R, S) {
|
||||
var exec = R.exec;
|
||||
if (typeof exec === "function") {
|
||||
var result = exec.call(R, S);
|
||||
if (typeof result !== "object") {
|
||||
throw TypeError("RegExp exec method returned something other than an Object or null");
|
||||
}
|
||||
return result;
|
||||
}
|
||||
if (classof2(R) !== "RegExp") {
|
||||
throw TypeError("RegExp#exec called on incompatible receiver");
|
||||
}
|
||||
return regexpExec2.call(R, S);
|
||||
};
|
||||
});
|
||||
|
||||
// node_modules/core-js/internals/get-substitution.js
|
||||
var require_get_substitution = __commonJS(function(exports2, module2) {
|
||||
var toObject4 = require_to_object();
|
||||
@@ -1246,26 +1266,6 @@
|
||||
};
|
||||
});
|
||||
|
||||
// node_modules/core-js/internals/regexp-exec-abstract.js
|
||||
var require_regexp_exec_abstract = __commonJS(function(exports2, module2) {
|
||||
var classof2 = require_classof_raw();
|
||||
var regexpExec2 = require_regexp_exec();
|
||||
module2.exports = function(R, S) {
|
||||
var exec = R.exec;
|
||||
if (typeof exec === "function") {
|
||||
var result = exec.call(R, S);
|
||||
if (typeof result !== "object") {
|
||||
throw TypeError("RegExp exec method returned something other than an Object or null");
|
||||
}
|
||||
return result;
|
||||
}
|
||||
if (classof2(R) !== "RegExp") {
|
||||
throw TypeError("RegExp#exec called on incompatible receiver");
|
||||
}
|
||||
return regexpExec2.call(R, S);
|
||||
};
|
||||
});
|
||||
|
||||
// node_modules/core-js/internals/is-regexp.js
|
||||
var require_is_regexp = __commonJS(function(exports2, module2) {
|
||||
var isObject3 = require_is_object();
|
||||
@@ -2996,7 +2996,9 @@
|
||||
function getTargetTabs($tabset, $tabContent, target) {
|
||||
var dataValue = "[data-value='" + $escape(target) + "']";
|
||||
var $aTag = $tabset.find("a" + dataValue);
|
||||
var $liTag = $aTag.parent();
|
||||
var $liTag = $aTag.parent("li");
|
||||
if ($liTag.length === 0)
|
||||
$liTag = $aTag;
|
||||
if ($liTag.length === 0) {
|
||||
throw "There is no tabPanel (or navbarMenu) with value (or menuName) equal to '" + target + "'";
|
||||
}
|
||||
@@ -3005,7 +3007,10 @@
|
||||
if ($aTag.attr("data-toggle") === "dropdown") {
|
||||
var $dropdownTabset = $aTag.find("+ ul.dropdown-menu");
|
||||
var dropdownId = $dropdownTabset.attr("data-tabsetid");
|
||||
var $dropdownLiTags = $dropdownTabset.find("a[data-toggle='tab']").parent("li");
|
||||
var $dropdownLiTags = $dropdownTabset.find("a[data-toggle='tab']");
|
||||
if ($dropdownLiTags.parent("li").length > 0) {
|
||||
$dropdownLiTags = $dropdownLiTags.parent("li");
|
||||
}
|
||||
$dropdownLiTags.each(function(i, el) {
|
||||
$liTags.push(import_jquery6.default(el));
|
||||
});
|
||||
@@ -3036,6 +3041,9 @@
|
||||
if (message.target !== null) {
|
||||
target = getTargetTabs($tabset, $tabContent, message.target);
|
||||
$targetLiTag = target.$liTag;
|
||||
if ($targetLiTag.hasClass("dropdown-item")) {
|
||||
$liTag = $aTag.removeClass("nav-link").addClass("dropdown-item");
|
||||
}
|
||||
}
|
||||
var dropdown = getDropdown();
|
||||
if (dropdown !== null) {
|
||||
@@ -3047,7 +3055,10 @@
|
||||
if ($aTag.attr("data-toggle") === "tab") {
|
||||
var index = getTabIndex($tabset, tabsetId);
|
||||
var tabId = "tab-" + tabsetId + "-" + index;
|
||||
$liTag.find("> a").attr("href", "#" + tabId);
|
||||
var anchor = $liTag.find("> a");
|
||||
if (anchor.length === 0)
|
||||
anchor = $liTag;
|
||||
anchor.attr("href", "#" + tabId);
|
||||
$divTag.attr("id", tabId);
|
||||
}
|
||||
if (message.position === "before") {
|
||||
@@ -3080,8 +3091,8 @@
|
||||
}
|
||||
function getTabIndex($tabset2, tabsetId2) {
|
||||
var existingTabIds = [0];
|
||||
$tabset2.find("> li").each(function() {
|
||||
var $tab = import_jquery6.default(this).find("> a[data-toggle='tab']");
|
||||
$tabset2.find("a[data-toggle='tab']").each(function() {
|
||||
var $tab = import_jquery6.default(this);
|
||||
if ($tab.length > 0) {
|
||||
var href = $tab.attr("href").replace(/.*(?=#[^\s]+$)/, "");
|
||||
var _index = href.replace("#tab-" + tabsetId2 + "-", "");
|
||||
@@ -6084,7 +6095,7 @@
|
||||
return import_jquery6.default(scope).find("ul.nav.shiny-tab-input");
|
||||
},
|
||||
getValue: function getValue(el) {
|
||||
var anchor = import_jquery6.default(el).find("li:not(.dropdown).active").children("a");
|
||||
var anchor = isBS3() ? import_jquery6.default(el).find("li:not(.dropdown).active > a") : import_jquery6.default(el).find(".nav-link:not(.dropdown-toggle).active, .dropdown-menu > .dropdown-item.active");
|
||||
if (anchor.length === 1)
|
||||
return this._getTabName(anchor);
|
||||
return null;
|
||||
@@ -6093,7 +6104,7 @@
|
||||
var self2 = this;
|
||||
var success = false;
|
||||
if (value) {
|
||||
var anchors = import_jquery6.default(el).find("li:not(.dropdown)").children("a");
|
||||
var anchors = isBS3() ? import_jquery6.default(el).find("li:not(.dropdown) > a") : import_jquery6.default(el).find(".nav-link:not(.dropdown-toggle), .dropdown-menu > .dropdown-item");
|
||||
anchors.each(function() {
|
||||
if (self2._getTabName(import_jquery6.default(this)) === value) {
|
||||
import_jquery6.default(this).tab("show");
|
||||
@@ -7259,28 +7270,68 @@
|
||||
}, {unsafe: true});
|
||||
}
|
||||
|
||||
// node_modules/core-js/modules/es.string.replace.js
|
||||
// node_modules/core-js/modules/es.string.match.js
|
||||
"use strict";
|
||||
var fixRegExpWellKnownSymbolLogic = require_fix_regexp_well_known_symbol_logic();
|
||||
var anObject2 = require_an_object();
|
||||
var toLength4 = require_to_length();
|
||||
var toInteger2 = require_to_integer();
|
||||
var requireObjectCoercible = require_require_object_coercible();
|
||||
var advanceStringIndex = require_advance_string_index();
|
||||
var getSubstitution = require_get_substitution();
|
||||
var regExpExec = require_regexp_exec_abstract();
|
||||
fixRegExpWellKnownSymbolLogic("match", 1, function(MATCH, nativeMatch, maybeCallNative) {
|
||||
return [
|
||||
function match(regexp) {
|
||||
var O = requireObjectCoercible(this);
|
||||
var matcher = regexp == void 0 ? void 0 : regexp[MATCH];
|
||||
return matcher !== void 0 ? matcher.call(regexp, O) : new RegExp(regexp)[MATCH](String(O));
|
||||
},
|
||||
function(regexp) {
|
||||
var res = maybeCallNative(nativeMatch, regexp, this);
|
||||
if (res.done)
|
||||
return res.value;
|
||||
var rx = anObject2(regexp);
|
||||
var S = String(this);
|
||||
if (!rx.global)
|
||||
return regExpExec(rx, S);
|
||||
var fullUnicode = rx.unicode;
|
||||
rx.lastIndex = 0;
|
||||
var A = [];
|
||||
var n = 0;
|
||||
var result;
|
||||
while ((result = regExpExec(rx, S)) !== null) {
|
||||
var matchStr = String(result[0]);
|
||||
A[n] = matchStr;
|
||||
if (matchStr === "")
|
||||
rx.lastIndex = advanceStringIndex(S, toLength4(rx.lastIndex), fullUnicode);
|
||||
n++;
|
||||
}
|
||||
return n === 0 ? null : A;
|
||||
}
|
||||
];
|
||||
});
|
||||
|
||||
// node_modules/core-js/modules/es.string.replace.js
|
||||
"use strict";
|
||||
var fixRegExpWellKnownSymbolLogic2 = require_fix_regexp_well_known_symbol_logic();
|
||||
var anObject3 = require_an_object();
|
||||
var toLength5 = require_to_length();
|
||||
var toInteger2 = require_to_integer();
|
||||
var requireObjectCoercible2 = require_require_object_coercible();
|
||||
var advanceStringIndex2 = require_advance_string_index();
|
||||
var getSubstitution = require_get_substitution();
|
||||
var regExpExec2 = require_regexp_exec_abstract();
|
||||
var max3 = Math.max;
|
||||
var min2 = Math.min;
|
||||
var maybeToString = function(it) {
|
||||
return it === void 0 ? it : String(it);
|
||||
};
|
||||
fixRegExpWellKnownSymbolLogic("replace", 2, function(REPLACE, nativeReplace, maybeCallNative, reason) {
|
||||
fixRegExpWellKnownSymbolLogic2("replace", 2, function(REPLACE, nativeReplace, maybeCallNative, reason) {
|
||||
var REGEXP_REPLACE_SUBSTITUTES_UNDEFINED_CAPTURE = reason.REGEXP_REPLACE_SUBSTITUTES_UNDEFINED_CAPTURE;
|
||||
var REPLACE_KEEPS_$0 = reason.REPLACE_KEEPS_$0;
|
||||
var UNSAFE_SUBSTITUTE = REGEXP_REPLACE_SUBSTITUTES_UNDEFINED_CAPTURE ? "$" : "$0";
|
||||
return [
|
||||
function replace(searchValue, replaceValue) {
|
||||
var O = requireObjectCoercible(this);
|
||||
var O = requireObjectCoercible2(this);
|
||||
var replacer = searchValue == void 0 ? void 0 : searchValue[REPLACE];
|
||||
return replacer !== void 0 ? replacer.call(searchValue, O, replaceValue) : nativeReplace.call(String(O), searchValue, replaceValue);
|
||||
},
|
||||
@@ -7290,7 +7341,7 @@
|
||||
if (res.done)
|
||||
return res.value;
|
||||
}
|
||||
var rx = anObject2(regexp);
|
||||
var rx = anObject3(regexp);
|
||||
var S = String(this);
|
||||
var functionalReplace = typeof replaceValue === "function";
|
||||
if (!functionalReplace)
|
||||
@@ -7302,7 +7353,7 @@
|
||||
}
|
||||
var results = [];
|
||||
while (true) {
|
||||
var result = regExpExec(rx, S);
|
||||
var result = regExpExec2(rx, S);
|
||||
if (result === null)
|
||||
break;
|
||||
results.push(result);
|
||||
@@ -7310,7 +7361,7 @@
|
||||
break;
|
||||
var matchStr = String(result[0]);
|
||||
if (matchStr === "")
|
||||
rx.lastIndex = advanceStringIndex(S, toLength4(rx.lastIndex), fullUnicode);
|
||||
rx.lastIndex = advanceStringIndex2(S, toLength5(rx.lastIndex), fullUnicode);
|
||||
}
|
||||
var accumulatedResult = "";
|
||||
var nextSourcePosition = 0;
|
||||
@@ -7342,13 +7393,13 @@
|
||||
|
||||
// node_modules/core-js/modules/es.string.split.js
|
||||
"use strict";
|
||||
var fixRegExpWellKnownSymbolLogic2 = require_fix_regexp_well_known_symbol_logic();
|
||||
var fixRegExpWellKnownSymbolLogic3 = require_fix_regexp_well_known_symbol_logic();
|
||||
var isRegExp = require_is_regexp();
|
||||
var anObject3 = require_an_object();
|
||||
var requireObjectCoercible2 = require_require_object_coercible();
|
||||
var anObject4 = require_an_object();
|
||||
var requireObjectCoercible3 = require_require_object_coercible();
|
||||
var speciesConstructor = require_species_constructor();
|
||||
var advanceStringIndex2 = require_advance_string_index();
|
||||
var toLength5 = require_to_length();
|
||||
var advanceStringIndex3 = require_advance_string_index();
|
||||
var toLength6 = require_to_length();
|
||||
var callRegExpExec = require_regexp_exec_abstract();
|
||||
var regexpExec = require_regexp_exec();
|
||||
var fails5 = require_fails();
|
||||
@@ -7358,11 +7409,11 @@
|
||||
var SUPPORTS_Y = !fails5(function() {
|
||||
return !RegExp(MAX_UINT32, "y");
|
||||
});
|
||||
fixRegExpWellKnownSymbolLogic2("split", 2, function(SPLIT, nativeSplit, maybeCallNative) {
|
||||
fixRegExpWellKnownSymbolLogic3("split", 2, function(SPLIT, nativeSplit, maybeCallNative) {
|
||||
var internalSplit;
|
||||
if ("abbc".split(/(b)*/)[1] == "c" || "test".split(/(?:)/, -1).length != 4 || "ab".split(/(?:ab)*/).length != 2 || ".".split(/(.?)(.?)/).length != 4 || ".".split(/()()/).length > 1 || "".split(/.?/).length) {
|
||||
internalSplit = function(separator, limit) {
|
||||
var string = String(requireObjectCoercible2(this));
|
||||
var string = String(requireObjectCoercible3(this));
|
||||
var lim = limit === void 0 ? MAX_UINT32 : limit >>> 0;
|
||||
if (lim === 0)
|
||||
return [];
|
||||
@@ -7405,7 +7456,7 @@
|
||||
internalSplit = nativeSplit;
|
||||
return [
|
||||
function split(separator, limit) {
|
||||
var O = requireObjectCoercible2(this);
|
||||
var O = requireObjectCoercible3(this);
|
||||
var splitter = separator == void 0 ? void 0 : separator[SPLIT];
|
||||
return splitter !== void 0 ? splitter.call(separator, O, limit) : internalSplit.call(String(O), separator, limit);
|
||||
},
|
||||
@@ -7413,7 +7464,7 @@
|
||||
var res = maybeCallNative(internalSplit, regexp, this, limit, internalSplit !== nativeSplit);
|
||||
if (res.done)
|
||||
return res.value;
|
||||
var rx = anObject3(regexp);
|
||||
var rx = anObject4(regexp);
|
||||
var S = String(this);
|
||||
var C = speciesConstructor(rx, RegExp);
|
||||
var unicodeMatching = rx.unicode;
|
||||
@@ -7431,8 +7482,8 @@
|
||||
splitter.lastIndex = SUPPORTS_Y ? q : 0;
|
||||
var z = callRegExpExec(splitter, SUPPORTS_Y ? S : S.slice(q));
|
||||
var e;
|
||||
if (z === null || (e = min3(toLength5(splitter.lastIndex + (SUPPORTS_Y ? 0 : q)), S.length)) === p) {
|
||||
q = advanceStringIndex2(S, q, unicodeMatching);
|
||||
if (z === null || (e = min3(toLength6(splitter.lastIndex + (SUPPORTS_Y ? 0 : q)), S.length)) === p) {
|
||||
q = advanceStringIndex3(S, q, unicodeMatching);
|
||||
} else {
|
||||
A.push(S.slice(p, q));
|
||||
if (A.length === lim)
|
||||
@@ -7725,6 +7776,12 @@
|
||||
el.removeChild(div);
|
||||
return linkColor;
|
||||
}
|
||||
function isBS3() {
|
||||
if (!import_jquery5.default.fn.tooltip) {
|
||||
return false;
|
||||
}
|
||||
return import_jquery5.default.fn.tooltip.Constructor.VERSION.match(/^3\./);
|
||||
}
|
||||
|
||||
// src/shiny.ts
|
||||
var Shiny;
|
||||
@@ -7855,9 +7912,9 @@
|
||||
var $22 = require_export();
|
||||
var fails6 = require_fails();
|
||||
var ArrayBufferModule = require_array_buffer();
|
||||
var anObject4 = require_an_object();
|
||||
var anObject5 = require_an_object();
|
||||
var toAbsoluteIndex3 = require_to_absolute_index();
|
||||
var toLength6 = require_to_length();
|
||||
var toLength7 = require_to_length();
|
||||
var speciesConstructor2 = require_species_constructor();
|
||||
var ArrayBuffer3 = ArrayBufferModule.ArrayBuffer;
|
||||
var DataView2 = ArrayBufferModule.DataView;
|
||||
@@ -7868,12 +7925,12 @@
|
||||
$22({target: "ArrayBuffer", proto: true, unsafe: true, forced: INCORRECT_SLICE}, {
|
||||
slice: function slice2(start, end) {
|
||||
if (nativeArrayBufferSlice !== void 0 && end === void 0) {
|
||||
return nativeArrayBufferSlice.call(anObject4(this), start);
|
||||
return nativeArrayBufferSlice.call(anObject5(this), start);
|
||||
}
|
||||
var length = anObject4(this).byteLength;
|
||||
var length = anObject5(this).byteLength;
|
||||
var first = toAbsoluteIndex3(start, length);
|
||||
var fin = toAbsoluteIndex3(end === void 0 ? length : end, length);
|
||||
var result = new (speciesConstructor2(this, ArrayBuffer3))(toLength6(fin - first));
|
||||
var result = new (speciesConstructor2(this, ArrayBuffer3))(toLength7(fin - first));
|
||||
var viewSource = new DataView2(this);
|
||||
var viewTarget = new DataView2(result);
|
||||
var index = 0;
|
||||
@@ -7991,46 +8048,6 @@
|
||||
var j;
|
||||
var key;
|
||||
|
||||
// node_modules/core-js/modules/es.string.match.js
|
||||
"use strict";
|
||||
var fixRegExpWellKnownSymbolLogic3 = require_fix_regexp_well_known_symbol_logic();
|
||||
var anObject5 = require_an_object();
|
||||
var toLength7 = require_to_length();
|
||||
var requireObjectCoercible3 = require_require_object_coercible();
|
||||
var advanceStringIndex3 = require_advance_string_index();
|
||||
var regExpExec2 = require_regexp_exec_abstract();
|
||||
fixRegExpWellKnownSymbolLogic3("match", 1, function(MATCH, nativeMatch, maybeCallNative) {
|
||||
return [
|
||||
function match(regexp) {
|
||||
var O = requireObjectCoercible3(this);
|
||||
var matcher = regexp == void 0 ? void 0 : regexp[MATCH];
|
||||
return matcher !== void 0 ? matcher.call(regexp, O) : new RegExp(regexp)[MATCH](String(O));
|
||||
},
|
||||
function(regexp) {
|
||||
var res = maybeCallNative(nativeMatch, regexp, this);
|
||||
if (res.done)
|
||||
return res.value;
|
||||
var rx = anObject5(regexp);
|
||||
var S = String(this);
|
||||
if (!rx.global)
|
||||
return regExpExec2(rx, S);
|
||||
var fullUnicode = rx.unicode;
|
||||
rx.lastIndex = 0;
|
||||
var A = [];
|
||||
var n = 0;
|
||||
var result;
|
||||
while ((result = regExpExec2(rx, S)) !== null) {
|
||||
var matchStr = String(result[0]);
|
||||
A[n] = matchStr;
|
||||
if (matchStr === "")
|
||||
rx.lastIndex = advanceStringIndex3(S, toLength7(rx.lastIndex), fullUnicode);
|
||||
n++;
|
||||
}
|
||||
return n === 0 ? null : A;
|
||||
}
|
||||
];
|
||||
});
|
||||
|
||||
// node_modules/core-js/modules/es.string.search.js
|
||||
"use strict";
|
||||
var fixRegExpWellKnownSymbolLogic4 = require_fix_regexp_well_known_symbol_logic();
|
||||
|
||||
File diff suppressed because one or more lines are too long
2
inst/www/shared/shiny.min.js
vendored
2
inst/www/shared/shiny.min.js
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -8,6 +8,8 @@ navlistPanel(
|
||||
...,
|
||||
id = NULL,
|
||||
selected = NULL,
|
||||
header = NULL,
|
||||
footer = NULL,
|
||||
well = TRUE,
|
||||
fluid = TRUE,
|
||||
widths = c(4, 8)
|
||||
@@ -25,13 +27,19 @@ value will correspond to the \code{value} argument that is passed to
|
||||
of the navigation item that should be selected by default. If \code{NULL},
|
||||
the first navigation will be selected.}
|
||||
|
||||
\item{header}{Tag or list of tags to display as a common header above all
|
||||
tabPanels.}
|
||||
|
||||
\item{footer}{Tag or list of tags to display as a common footer below all
|
||||
tabPanels}
|
||||
|
||||
\item{well}{\code{TRUE} to place a well (gray rounded rectangle) around the
|
||||
navigation list.}
|
||||
|
||||
\item{fluid}{\code{TRUE} to use fluid layout; \code{FALSE} to use fixed
|
||||
layout.}
|
||||
|
||||
\item{widths}{Column withs of the navigation list and tabset content areas
|
||||
\item{widths}{Column widths of the navigation list and tabset content areas
|
||||
respectively.}
|
||||
}
|
||||
\description{
|
||||
|
||||
@@ -9,6 +9,8 @@ tabsetPanel(
|
||||
id = NULL,
|
||||
selected = NULL,
|
||||
type = c("tabs", "pills", "hidden"),
|
||||
header = NULL,
|
||||
footer = NULL,
|
||||
position = deprecated()
|
||||
)
|
||||
}
|
||||
@@ -32,6 +34,12 @@ conjunction with \code{\link[=tabPanelBody]{tabPanelBody()}} and \code{\link[=up
|
||||
active tab via other input controls. (See example below)}
|
||||
}}
|
||||
|
||||
\item{header}{Tag or list of tags to display as a common header above all
|
||||
tabPanels.}
|
||||
|
||||
\item{footer}{Tag or list of tags to display as a common footer below all
|
||||
tabPanels}
|
||||
|
||||
\item{position}{This argument is deprecated; it has been discontinued in
|
||||
Bootstrap 3.}
|
||||
}
|
||||
|
||||
@@ -34,6 +34,7 @@ import {
|
||||
updateLabel,
|
||||
getComputedLinkColor,
|
||||
makeBlob,
|
||||
isBS3,
|
||||
} from "./utils";
|
||||
|
||||
import { isQt, isIE, IEVersion } from "./utils/browser";
|
||||
@@ -1241,8 +1242,10 @@ function main(): void {
|
||||
function getTargetTabs($tabset, $tabContent, target) {
|
||||
const dataValue = "[data-value='" + $escape(target) + "']";
|
||||
const $aTag = $tabset.find("a" + dataValue);
|
||||
const $liTag = $aTag.parent();
|
||||
let $liTag = $aTag.parent("li");
|
||||
// BS3 dropdown anchors are wrapped in <li>, but they can't be in BS4
|
||||
|
||||
if ($liTag.length === 0) $liTag = $aTag;
|
||||
if ($liTag.length === 0) {
|
||||
throw (
|
||||
"There is no tabPanel (or navbarMenu) with value" +
|
||||
@@ -1259,10 +1262,12 @@ function main(): void {
|
||||
const $dropdownTabset = $aTag.find("+ ul.dropdown-menu");
|
||||
const dropdownId = $dropdownTabset.attr("data-tabsetid");
|
||||
|
||||
const $dropdownLiTags = $dropdownTabset
|
||||
.find("a[data-toggle='tab']")
|
||||
.parent("li");
|
||||
let $dropdownLiTags = $dropdownTabset.find("a[data-toggle='tab']");
|
||||
// BS3 dropdown anchors are wrapped in <li>, but they can't be in BS4
|
||||
|
||||
if ($dropdownLiTags.parent("li").length > 0) {
|
||||
$dropdownLiTags = $dropdownLiTags.parent("li");
|
||||
}
|
||||
$dropdownLiTags.each(function (i, el) {
|
||||
$liTags.push($(el));
|
||||
});
|
||||
@@ -1286,7 +1291,7 @@ function main(): void {
|
||||
let tabsetId = $parentTabset.attr("data-tabsetid");
|
||||
|
||||
const $divTag = $(message.divTag.html);
|
||||
const $liTag = $(message.liTag.html);
|
||||
let $liTag = $(message.liTag.html);
|
||||
const $aTag = $liTag.find("> a");
|
||||
|
||||
// Unless the item is being prepended/appended, the target tab
|
||||
@@ -1297,6 +1302,12 @@ function main(): void {
|
||||
if (message.target !== null) {
|
||||
target = getTargetTabs($tabset, $tabContent, message.target);
|
||||
$targetLiTag = target.$liTag;
|
||||
// If the target is a (BS4) .dropdown-item, then we can't insert
|
||||
// <li class='nav-item'><a class='nav-link'>...</a></li>,
|
||||
// instead, we need <a class='dropdown-item'>...</a>
|
||||
if ($targetLiTag.hasClass("dropdown-item")) {
|
||||
$liTag = $aTag.removeClass("nav-link").addClass("dropdown-item");
|
||||
}
|
||||
}
|
||||
|
||||
// If the item is to be placed inside a navbarMenu (dropdown),
|
||||
@@ -1321,7 +1332,11 @@ function main(): void {
|
||||
const index = getTabIndex($tabset, tabsetId);
|
||||
const tabId = "tab-" + tabsetId + "-" + index;
|
||||
|
||||
$liTag.find("> a").attr("href", "#" + tabId);
|
||||
let anchor = $liTag.find("> a");
|
||||
// BS3 dropdown anchors are wrapped in <li>, but they can't be in BS4
|
||||
|
||||
if (anchor.length === 0) anchor = $liTag;
|
||||
anchor.attr("href", "#" + tabId);
|
||||
$divTag.attr("id", tabId);
|
||||
}
|
||||
|
||||
@@ -1411,8 +1426,8 @@ function main(): void {
|
||||
// loop through all existing tabs, find the one with highest id
|
||||
// (since this is based on a numeric counter), and increment
|
||||
|
||||
$tabset.find("> li").each(function () {
|
||||
const $tab = $(this).find("> a[data-toggle='tab']");
|
||||
$tabset.find("a[data-toggle='tab']").each(function () {
|
||||
const $tab = $(this);
|
||||
|
||||
if ($tab.length > 0) {
|
||||
// remove leading url if it exists. (copy of bootstrap url stripper)
|
||||
@@ -5881,7 +5896,12 @@ function main(): void {
|
||||
return $(scope).find("ul.nav.shiny-tab-input");
|
||||
},
|
||||
getValue: function (el) {
|
||||
const anchor = $(el).find("li:not(.dropdown).active").children("a");
|
||||
// prettier-ignore
|
||||
let anchor = isBS3()
|
||||
? $(el).find("li:not(.dropdown).active > a")
|
||||
: $(el).find(
|
||||
".nav-link:not(.dropdown-toggle).active, .dropdown-menu > .dropdown-item.active"
|
||||
);
|
||||
|
||||
if (anchor.length === 1) return this._getTabName(anchor);
|
||||
|
||||
@@ -5892,7 +5912,12 @@ function main(): void {
|
||||
let success = false;
|
||||
|
||||
if (value) {
|
||||
const anchors = $(el).find("li:not(.dropdown)").children("a");
|
||||
// prettier-ignore
|
||||
let anchors = isBS3()
|
||||
? $(el).find("li:not(.dropdown) > a")
|
||||
: $(el).find(
|
||||
".nav-link:not(.dropdown-toggle), .dropdown-menu > .dropdown-item"
|
||||
);
|
||||
|
||||
anchors.each(function () {
|
||||
if (self._getTabName($(this)) === value) {
|
||||
|
||||
@@ -361,6 +361,17 @@ function getComputedLinkColor(el: HTMLElement): string {
|
||||
return linkColor;
|
||||
}
|
||||
|
||||
function isBS3(): boolean {
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
if (!$.fn.tooltip) {
|
||||
return false;
|
||||
}
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
return $.fn.tooltip.Constructor.VERSION.match(/^3\./);
|
||||
}
|
||||
|
||||
export {
|
||||
escapeHTML,
|
||||
randomId,
|
||||
@@ -384,4 +395,5 @@ export {
|
||||
updateLabel,
|
||||
getComputedLinkColor,
|
||||
makeBlob,
|
||||
isBS3,
|
||||
};
|
||||
|
||||
339
tests/testthat/_snaps/tabPanel.md
Normal file
339
tests/testthat/_snaps/tabPanel.md
Normal file
@@ -0,0 +1,339 @@
|
||||
# tabsetPanel() markup is correct
|
||||
|
||||
Code
|
||||
default
|
||||
Output
|
||||
<div class="tabbable">
|
||||
<ul class="nav nav-tabs" data-tabsetid="4785">
|
||||
<li class="active">
|
||||
<a href="#tab-4785-1" data-toggle="tab" data-value="A">A</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#tab-4785-2" data-toggle="tab" data-value="B">
|
||||
<i class=" fab fa-github fa-fw" role="presentation" aria-label=" icon"></i>
|
||||
B
|
||||
</a>
|
||||
</li>
|
||||
<li class="dropdown">
|
||||
<a href="#" class="dropdown-toggle" data-toggle="dropdown" data-value="Menu">
|
||||
Menu
|
||||
<b class="caret"></b>
|
||||
</a>
|
||||
<ul class="dropdown-menu" data-tabsetid="1502">
|
||||
<li>
|
||||
<a href="#tab-1502-1" data-toggle="tab" data-value="C">C</a>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="tab-content" data-tabsetid="4785">
|
||||
<div class="tab-pane active" data-value="A" id="tab-4785-1">a</div>
|
||||
<div class="tab-pane" data-value="B" data-icon-class="fab fa-github" id="tab-4785-2">b</div>
|
||||
<div class="tab-pane" data-value="C" id="tab-1502-1">c</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
---
|
||||
|
||||
Code
|
||||
pills
|
||||
Output
|
||||
<div class="tabbable">
|
||||
<ul class="nav nav-pills" data-tabsetid="4785">
|
||||
<li>
|
||||
<a href="#tab-4785-1" data-toggle="tab" data-value="A">A</a>
|
||||
</li>
|
||||
<li class="active">
|
||||
<a href="#tab-4785-2" data-toggle="tab" data-value="B">
|
||||
<i class=" fab fa-github fa-fw" role="presentation" aria-label=" icon"></i>
|
||||
B
|
||||
</a>
|
||||
</li>
|
||||
<li class="dropdown">
|
||||
<a href="#" class="dropdown-toggle" data-toggle="dropdown" data-value="Menu">
|
||||
Menu
|
||||
<b class="caret"></b>
|
||||
</a>
|
||||
<ul class="dropdown-menu" data-tabsetid="1502">
|
||||
<li>
|
||||
<a href="#tab-1502-1" data-toggle="tab" data-value="C">C</a>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="content-header"></div>
|
||||
<div class="tab-content" data-tabsetid="4785">
|
||||
<div class="tab-pane" data-value="A" id="tab-4785-1">a</div>
|
||||
<div class="tab-pane active" data-icon-class="fab fa-github" data-value="B" id="tab-4785-2">b</div>
|
||||
<div class="tab-pane" data-value="C" id="tab-1502-1">c</div>
|
||||
</div>
|
||||
<div class="content-footer"></div>
|
||||
</div>
|
||||
|
||||
---
|
||||
|
||||
Code
|
||||
bslib_tags(x)
|
||||
Output
|
||||
<div class="tabbable">
|
||||
<ul class="nav nav-tabs" data-tabsetid="4785">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link active" data-toggle="tab" data-value="A" href="#tab-4785-1">A</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="#tab-4785-2" data-toggle="tab" data-value="B">
|
||||
<i class=" fab fa-github fa-fw" role="presentation" aria-label=" icon"></i>
|
||||
B
|
||||
</a>
|
||||
</li>
|
||||
<li class="dropdown nav-item">
|
||||
<a class="dropdown-toggle nav-link" data-toggle="dropdown" data-value="Menu" href="#">
|
||||
Menu
|
||||
<b class="caret"></b>
|
||||
</a>
|
||||
<ul class="dropdown-menu" data-tabsetid="1502">
|
||||
<a class="dropdown-item" href="#tab-1502-1" data-toggle="tab" data-value="C">C</a>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="tab-content" data-tabsetid="4785">
|
||||
<div class="tab-pane active" data-value="A" id="tab-4785-1">a</div>
|
||||
<div class="tab-pane" data-value="B" data-icon-class="fab fa-github" id="tab-4785-2">b</div>
|
||||
<div class="tab-pane" data-value="C" id="tab-1502-1">c</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
---
|
||||
|
||||
Code
|
||||
bslib_tags(x)
|
||||
Output
|
||||
<div class="tabbable">
|
||||
<ul class="nav nav-pills" data-tabsetid="4785">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="#tab-4785-1" data-toggle="tab" data-value="A">A</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link active" data-toggle="tab" data-value="B" href="#tab-4785-2">
|
||||
<i class=" fab fa-github fa-fw" role="presentation" aria-label=" icon"></i>
|
||||
B
|
||||
</a>
|
||||
</li>
|
||||
<li class="dropdown nav-item">
|
||||
<a class="dropdown-toggle nav-link" data-toggle="dropdown" data-value="Menu" href="#">
|
||||
Menu
|
||||
<b class="caret"></b>
|
||||
</a>
|
||||
<ul class="dropdown-menu" data-tabsetid="1502">
|
||||
<a class="dropdown-item" href="#tab-1502-1" data-toggle="tab" data-value="C">C</a>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="content-header"></div>
|
||||
<div class="tab-content" data-tabsetid="4785">
|
||||
<div class="tab-pane" data-value="A" id="tab-4785-1">a</div>
|
||||
<div class="tab-pane active" data-icon-class="fab fa-github" data-value="B" id="tab-4785-2">b</div>
|
||||
<div class="tab-pane" data-value="C" id="tab-1502-1">c</div>
|
||||
</div>
|
||||
<div class="content-footer"></div>
|
||||
</div>
|
||||
|
||||
# navbarPage() markup is correct
|
||||
|
||||
Code
|
||||
nav_page
|
||||
Output
|
||||
<nav class="navbar navbar-default navbar-static-top" role="navigation">
|
||||
<div class="container-fluid">
|
||||
<div class="navbar-header">
|
||||
<span class="navbar-brand">Title</span>
|
||||
</div>
|
||||
<ul class="nav navbar-nav" data-tabsetid="4785">
|
||||
<li class="active">
|
||||
<a href="#tab-4785-1" data-toggle="tab" data-value="A">A</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#tab-4785-2" data-toggle="tab" data-value="B">
|
||||
<i class=" fab fa-github fa-fw" role="presentation" aria-label=" icon"></i>
|
||||
B
|
||||
</a>
|
||||
</li>
|
||||
<li class="dropdown">
|
||||
<a href="#" class="dropdown-toggle" data-toggle="dropdown" data-value="Menu">
|
||||
Menu
|
||||
<b class="caret"></b>
|
||||
</a>
|
||||
<ul class="dropdown-menu" data-tabsetid="1502">
|
||||
<li>
|
||||
<a href="#tab-1502-1" data-toggle="tab" data-value="C">C</a>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</nav>
|
||||
<div class="container-fluid">
|
||||
<div class="tab-content" data-tabsetid="4785">
|
||||
<div class="tab-pane active" data-value="A" id="tab-4785-1">a</div>
|
||||
<div class="tab-pane" data-value="B" data-icon-class="fab fa-github" id="tab-4785-2">b</div>
|
||||
<div class="tab-pane" data-value="C" id="tab-1502-1">c</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
---
|
||||
|
||||
Code
|
||||
bslib_tags(x)
|
||||
Output
|
||||
<nav class="navbar navbar-default navbar-static-top" role="navigation">
|
||||
<div class="container-fluid">
|
||||
<div class="navbar-header">
|
||||
<span class="navbar-brand">Title</span>
|
||||
</div>
|
||||
<ul class="nav navbar-nav" data-tabsetid="4785">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link active" data-toggle="tab" data-value="A" href="#tab-4785-1">A</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="#tab-4785-2" data-toggle="tab" data-value="B">
|
||||
<i class=" fab fa-github fa-fw" role="presentation" aria-label=" icon"></i>
|
||||
B
|
||||
</a>
|
||||
</li>
|
||||
<li class="dropdown nav-item">
|
||||
<a class="dropdown-toggle nav-link" data-toggle="dropdown" data-value="Menu" href="#">
|
||||
Menu
|
||||
<b class="caret"></b>
|
||||
</a>
|
||||
<ul class="dropdown-menu" data-tabsetid="1502">
|
||||
<a class="dropdown-item" href="#tab-1502-1" data-toggle="tab" data-value="C">C</a>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</nav>
|
||||
<div class="container-fluid">
|
||||
<div class="tab-content" data-tabsetid="4785">
|
||||
<div class="tab-pane active" data-value="A" id="tab-4785-1">a</div>
|
||||
<div class="tab-pane" data-value="B" data-icon-class="fab fa-github" id="tab-4785-2">b</div>
|
||||
<div class="tab-pane" data-value="C" id="tab-1502-1">c</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
# String input is handled properly
|
||||
|
||||
Code
|
||||
nav_list
|
||||
Output
|
||||
<div class="row">
|
||||
<div class="col-sm-4 well">
|
||||
<ul class="nav nav-pills nav-stacked" data-tabsetid="4785">
|
||||
<li class="navbar-brand">A header</li>
|
||||
<li class="active">
|
||||
<a href="#tab-4785-2" data-toggle="tab" data-value="A">A</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#tab-4785-3" data-toggle="tab" data-value="B">
|
||||
<i class=" fab fa-github fa-fw" role="presentation" aria-label=" icon"></i>
|
||||
B
|
||||
</a>
|
||||
</li>
|
||||
<li class="dropdown">
|
||||
<a href="#" class="dropdown-toggle" data-toggle="dropdown" data-value="Menu">
|
||||
Menu
|
||||
<b class="caret"></b>
|
||||
</a>
|
||||
<ul class="dropdown-menu" data-tabsetid="1502">
|
||||
<li>
|
||||
<a href="#tab-1502-1" data-toggle="tab" data-value="C">C</a>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="col-sm-8">
|
||||
<div class="tab-content" data-tabsetid="4785">
|
||||
<div class="tab-pane active" data-value="A" id="tab-4785-2">a</div>
|
||||
<div class="tab-pane" data-value="B" data-icon-class="fab fa-github" id="tab-4785-3">b</div>
|
||||
<div class="tab-pane" data-value="C" id="tab-1502-1">c</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
---
|
||||
|
||||
Code
|
||||
bslib_tags(x)
|
||||
Output
|
||||
<div class="row">
|
||||
<div class="col-sm-4 well">
|
||||
<ul class="nav nav-pills nav-stacked" data-tabsetid="4785">
|
||||
<li class="navbar-brand">A header</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link active" data-toggle="tab" data-value="A" href="#tab-4785-2">A</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="#tab-4785-3" data-toggle="tab" data-value="B">
|
||||
<i class=" fab fa-github fa-fw" role="presentation" aria-label=" icon"></i>
|
||||
B
|
||||
</a>
|
||||
</li>
|
||||
<li class="dropdown nav-item">
|
||||
<a class="dropdown-toggle nav-link" data-toggle="dropdown" data-value="Menu" href="#">
|
||||
Menu
|
||||
<b class="caret"></b>
|
||||
</a>
|
||||
<ul class="dropdown-menu" data-tabsetid="1502">
|
||||
<a class="dropdown-item" href="#tab-1502-1" data-toggle="tab" data-value="C">C</a>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="col-sm-8">
|
||||
<div class="tab-content" data-tabsetid="4785">
|
||||
<div class="tab-pane active" data-value="A" id="tab-4785-2">a</div>
|
||||
<div class="tab-pane" data-value="B" data-icon-class="fab fa-github" id="tab-4785-3">b</div>
|
||||
<div class="tab-pane" data-value="C" id="tab-1502-1">c</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
# Shiny.tag input produces a warning
|
||||
|
||||
Code
|
||||
tab_tags
|
||||
Output
|
||||
<div class="tabbable">
|
||||
<ul class="nav nav-tabs" data-tabsetid="4785">
|
||||
<li class="active">
|
||||
<a href="#tab-4785-1" data-toggle="tab"></a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#tab-4785-2" data-toggle="tab" data-value="A">A</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#tab-4785-3" data-toggle="tab" data-value="B">
|
||||
<i class=" fab fa-github fa-fw" role="presentation" aria-label=" icon"></i>
|
||||
B
|
||||
</a>
|
||||
</li>
|
||||
<li class="dropdown">
|
||||
<a href="#" class="dropdown-toggle" data-toggle="dropdown" data-value="Menu">
|
||||
Menu
|
||||
<b class="caret"></b>
|
||||
</a>
|
||||
<ul class="dropdown-menu" data-tabsetid="1502">
|
||||
<li>
|
||||
<a href="#tab-1502-1" data-toggle="tab" data-value="C">C</a>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="tab-content" data-tabsetid="4785">
|
||||
<div class="active" id="tab-4785-1">A div</div>
|
||||
<div class="tab-pane" data-value="A" id="tab-4785-2">a</div>
|
||||
<div class="tab-pane" data-value="B" data-icon-class="fab fa-github" id="tab-4785-3">b</div>
|
||||
<div class="tab-pane" data-value="C" id="tab-1502-1">c</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,4 +1,87 @@
|
||||
# tabsetPanel() et al. use p_randomInt() to generate ids (which uses withPrivateSeed()),
|
||||
# so we need to fix Shiny's private seed in order to make their HTML output deterministic
|
||||
navlist_panel <- function(...) {
|
||||
|
||||
withPrivateSeed(set.seed(100))
|
||||
navlistPanel(...)
|
||||
}
|
||||
navbar_page <- function(...) {
|
||||
withPrivateSeed(set.seed(100))
|
||||
navbarPage(...)
|
||||
}
|
||||
tabset_panel <- function(...) {
|
||||
withPrivateSeed(set.seed(100))
|
||||
tabsetPanel(...)
|
||||
}
|
||||
|
||||
expect_snapshot2 <- function(...) {
|
||||
if (getRversion() < "3.6.0") {
|
||||
skip("Skipping snapshots on R < 3.6 because of different RNG method")
|
||||
}
|
||||
expect_snapshot(...)
|
||||
}
|
||||
|
||||
expect_snapshot_bslib <- function(x, ...) {
|
||||
expect_snapshot2(bslib_tags(x), ...)
|
||||
}
|
||||
|
||||
# Simulates the UI tags that would be produced by
|
||||
# shinyApp(bootstrapPage(theme), function(...) {})
|
||||
bslib_tags <- function(ui, theme = bslib::bs_theme()) {
|
||||
old_theme <- getCurrentTheme()
|
||||
on.exit(setCurrentTheme(old_theme), add = TRUE)
|
||||
setCurrentTheme(theme)
|
||||
htmltools::renderTags(ui)$html
|
||||
}
|
||||
|
||||
panels <- list(
|
||||
tabPanel("A", "a"),
|
||||
tabPanel("B", "b", icon = icon("github")),
|
||||
navbarMenu("Menu", tabPanel("C", "c"))
|
||||
)
|
||||
|
||||
test_that("tabsetPanel() markup is correct", {
|
||||
|
||||
default <- tabset_panel(!!!panels)
|
||||
pills <- tabset_panel(
|
||||
!!!panels, type = "pills", selected = "B",
|
||||
header = div(class = "content-header"),
|
||||
footer = div(class = "content-footer")
|
||||
)
|
||||
# BS3
|
||||
expect_snapshot2(default)
|
||||
expect_snapshot2(pills)
|
||||
# BS4
|
||||
expect_snapshot_bslib(default)
|
||||
expect_snapshot_bslib(pills)
|
||||
})
|
||||
|
||||
test_that("navbarPage() markup is correct", {
|
||||
nav_page <- navbar_page("Title", !!!panels)
|
||||
expect_snapshot2(nav_page)
|
||||
expect_snapshot_bslib(nav_page)
|
||||
})
|
||||
|
||||
# navlistPanel() can handle strings, but the others can't
|
||||
test_that("String input is handled properly", {
|
||||
nav_list <- navlist_panel(!!!c(list("A header"), panels))
|
||||
expect_snapshot2(nav_list)
|
||||
expect_snapshot_bslib(nav_list)
|
||||
expect_error(
|
||||
tabsetPanel(!!!c(list("A header"), panels)),
|
||||
"tabPanel"
|
||||
)
|
||||
})
|
||||
|
||||
test_that("Shiny.tag input produces a warning", {
|
||||
panels3 <- c(list(div("A div")), panels)
|
||||
tab_tags <- expect_warning(tabset_panel(!!!panels3))
|
||||
# Carson March 12th, 2021: Yes, he 'empty nav' output here isn't
|
||||
# sensible (which is why we now throw a warning), but it's probably
|
||||
# too late to change the behavior (it could break user code to do
|
||||
# anything different)
|
||||
expect_snapshot2(tab_tags)
|
||||
})
|
||||
|
||||
test_that("tabPanelBody validates it's input", {
|
||||
expect_silent(tabPanelBody("a", "content1", "content2", icon = icon("table")))
|
||||
|
||||
Reference in New Issue
Block a user