mirror of
https://github.com/rstudio/shiny.git
synced 2026-01-12 00:19:06 -05:00
Compare commits
3 Commits
navtreePan
...
slider-che
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
03b03f1173 | ||
|
|
28dc3ecd5b | ||
|
|
c3e6fdc550 |
@@ -170,7 +170,6 @@ Collate:
|
||||
'mock-session.R'
|
||||
'modal.R'
|
||||
'modules.R'
|
||||
'navtreePanel.R'
|
||||
'notifications.R'
|
||||
'priorityqueue.R'
|
||||
'progress.R'
|
||||
|
||||
@@ -175,7 +175,6 @@ export(moduleServer)
|
||||
export(navbarMenu)
|
||||
export(navbarPage)
|
||||
export(navlistPanel)
|
||||
export(navtreePanel)
|
||||
export(nearPoints)
|
||||
export(need)
|
||||
export(ns.sep)
|
||||
@@ -409,7 +408,6 @@ 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)
|
||||
|
||||
13
NEWS.md
13
NEWS.md
@@ -7,18 +7,7 @@ shiny 1.6.0.9000
|
||||
|
||||
* The `format` and `locale` arguments to `sliderInput()` have been removed. They have been deprecated since 0.10.2.2 (released on 2014-12-08).
|
||||
|
||||
### New features and improvements
|
||||
|
||||
* All uses of `list(...)` have been replaced with `rlang::list2(...)`. This means that you can use trailing `,` without error and use rlang's `!!!` operator to "splice" a list of argument values into `...`. We think this'll be particularly useful for passing a list of `tabPanel()` to their consumers (i.e., `tabsetPanel()`, `navbarPage()`, etc). For example, `tabs <- list(tabPanel("A", "a"), tabPanel("B", "b")); navbarPage(!!!tabs)`. (#3315 and #3328)
|
||||
|
||||
* Numerous improvements tabset panels (i.e., `tabPanel()`, `navbarMenu()`, `tabsetPanel()`, `navbarPage()`, etc) (#3315):
|
||||
* Closed #3322: `tabsetPanel()` and `navlistPanel()` gain `header`/`footer` arguments (inspired by `navbarPage()`'s already existing `header`/`footer`), making it easier to include content that should appear on every tab.
|
||||
* Closed #3313 and #1823: More informative error when non-`tabPanel()`/`shiny.tag` objects are supplied to `...`.
|
||||
* Closed #3321: New informative warning when `shiny.tag` object(s) are supplied to `...`. In this case we will continue to create an "empty" nav item and include the content on every tab, but the warning will mention the (new) `header`/`footer` args, which is likely what the user wants.
|
||||
* Closed #3320: The HTML markup that `tabPanel()` et. al generate (for Bootstrap nav) is now Bootstrap 4+ compliant when used with `theme = bslib::bs_theme()`.
|
||||
* Closed #1928: `NULL` values are now dropped instead of producing an empty nav item.
|
||||
|
||||
### Other improvements
|
||||
### Minor new features and improvements
|
||||
|
||||
* Shiny's core JavaScript code was converted to TypeScript. For the latest development information, please see the [README.md in `./srcts`](https://github.com/rstudio/shiny/tree/master/srcts). (#3296)
|
||||
|
||||
|
||||
@@ -396,7 +396,7 @@ mainPanel <- function(..., width = 8) {
|
||||
#' }
|
||||
#' @export
|
||||
verticalLayout <- function(..., fluid = TRUE) {
|
||||
lapply(list2(...), function(row) {
|
||||
lapply(list(...), function(row) {
|
||||
col <- column(12, row)
|
||||
if (fluid)
|
||||
fluidRow(col)
|
||||
@@ -433,7 +433,7 @@ verticalLayout <- function(..., fluid = TRUE) {
|
||||
#' @export
|
||||
flowLayout <- function(..., cellArgs = list()) {
|
||||
|
||||
children <- list2(...)
|
||||
children <- list(...)
|
||||
childIdx <- !nzchar(names(children) %||% character(length(children)))
|
||||
attribs <- children[!childIdx]
|
||||
children <- children[childIdx]
|
||||
@@ -516,7 +516,7 @@ inputPanel <- function(...) {
|
||||
#' @export
|
||||
splitLayout <- function(..., cellWidths = NULL, cellArgs = list()) {
|
||||
|
||||
children <- list2(...)
|
||||
children <- list(...)
|
||||
childIdx <- !nzchar(names(children) %||% character(length(children)))
|
||||
attribs <- children[!childIdx]
|
||||
children <- children[childIdx]
|
||||
@@ -614,7 +614,7 @@ fillCol <- function(..., flex = 1, width = "100%", height = "100%") {
|
||||
}
|
||||
|
||||
flexfill <- function(..., direction, flex, width = width, height = height) {
|
||||
children <- list2(...)
|
||||
children <- list(...)
|
||||
attrs <- list()
|
||||
|
||||
if (!is.null(names(children))) {
|
||||
|
||||
343
R/bootstrap.R
343
R/bootstrap.R
@@ -49,7 +49,7 @@ bootstrapPage <- function(..., title = NULL, responsive = deprecated(), theme =
|
||||
tags$head(tags$link(rel="stylesheet", type="text/css", href=theme))
|
||||
},
|
||||
# remainder of tags passed to the function
|
||||
list2(...)
|
||||
list(...)
|
||||
)
|
||||
|
||||
# If theme is a bslib::bs_theme() object, bootstrapLib() needs to come first
|
||||
@@ -91,10 +91,6 @@ getLang <- function(ui) {
|
||||
#' @export
|
||||
bootstrapLib <- function(theme = NULL) {
|
||||
tagFunction(function() {
|
||||
if (isRunning()) {
|
||||
setCurrentTheme(theme)
|
||||
}
|
||||
|
||||
# If we're not compiling Bootstrap Sass (from bslib), return the
|
||||
# static Bootstrap build.
|
||||
if (!is_bs_theme(theme)) {
|
||||
@@ -116,6 +112,7 @@ bootstrapLib <- function(theme = NULL) {
|
||||
# Note also that since this is shinyOptions() (and not options()), the
|
||||
# option is automatically reset when the app (or session) exits
|
||||
if (isRunning()) {
|
||||
setCurrentTheme(theme)
|
||||
registerThemeDependency(bs_theme_deps)
|
||||
|
||||
} else {
|
||||
@@ -166,15 +163,6 @@ 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)
|
||||
}
|
||||
@@ -223,7 +211,7 @@ registerThemeDependency <- function(func) {
|
||||
|
||||
bootstrapDependency <- function(theme) {
|
||||
htmlDependency(
|
||||
"bootstrap", bootstrapVersion,
|
||||
"bootstrap", "3.4.1",
|
||||
c(
|
||||
href = "shared/bootstrap",
|
||||
file = system.file("www/shared/bootstrap", package = "shiny")
|
||||
@@ -242,8 +230,6 @@ bootstrapDependency <- function(theme) {
|
||||
)
|
||||
}
|
||||
|
||||
bootstrapVersion <- "3.4.1"
|
||||
|
||||
|
||||
#' @rdname bootstrapPage
|
||||
#' @export
|
||||
@@ -450,11 +436,10 @@ 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 <- paste0(navbarClass, " navbar-", position)
|
||||
navbarClass <- paste(navbarClass, " navbar-", position, sep = "")
|
||||
if (inverse)
|
||||
navbarClass <- paste(navbarClass, "navbar-inverse")
|
||||
|
||||
@@ -462,14 +447,21 @@ navbarPage <- function(title,
|
||||
selected <- restoreInput(id = id, default = selected)
|
||||
|
||||
# build the tabset
|
||||
tabset <- buildTabset(..., ulClass = "nav navbar-nav", id = id, selected = selected)
|
||||
tabs <- list(...)
|
||||
tabset <- buildTabset(tabs, "nav navbar-nav", NULL, id, selected)
|
||||
|
||||
containerClass <- paste0("container", if (fluid) "-fluid")
|
||||
# function to return plain or fluid class name
|
||||
className <- function(name) {
|
||||
if (fluid)
|
||||
paste(name, "-fluid", sep="")
|
||||
else
|
||||
name
|
||||
}
|
||||
|
||||
# built the container div dynamically to support optional collapsibility
|
||||
if (collapsible) {
|
||||
navId <- paste0("navbar-collapse-", p_randomInt(1000, 10000))
|
||||
containerDiv <- div(class=containerClass,
|
||||
navId <- paste("navbar-collapse-", p_randomInt(1000, 10000), sep="")
|
||||
containerDiv <- div(class=className("container"),
|
||||
div(class="navbar-header",
|
||||
tags$button(type="button", class="navbar-toggle collapsed",
|
||||
`data-toggle`="collapse", `data-target`=paste0("#", navId),
|
||||
@@ -483,7 +475,7 @@ navbarPage <- function(title,
|
||||
div(class="navbar-collapse collapse", id=navId, tabset$navList)
|
||||
)
|
||||
} else {
|
||||
containerDiv <- div(class=containerClass,
|
||||
containerDiv <- div(class=className("container"),
|
||||
div(class="navbar-header",
|
||||
span(class="navbar-brand", pageTitle)
|
||||
),
|
||||
@@ -492,7 +484,7 @@ navbarPage <- function(title,
|
||||
}
|
||||
|
||||
# build the main tab content div
|
||||
contentDiv <- div(class=containerClass)
|
||||
contentDiv <- div(class=className("container"))
|
||||
if (!is.null(header))
|
||||
contentDiv <- tagAppendChild(contentDiv, div(class="row", header))
|
||||
contentDiv <- tagAppendChild(contentDiv, tabset$content)
|
||||
@@ -519,15 +511,11 @@ navbarPage <- function(title,
|
||||
navbarMenu <- function(title, ..., menuName = title, icon = NULL) {
|
||||
structure(list(title = title,
|
||||
menuName = menuName,
|
||||
tabs = list2(...),
|
||||
tabs = list(...),
|
||||
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
|
||||
@@ -668,13 +656,6 @@ 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.
|
||||
@@ -712,7 +693,6 @@ 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()],
|
||||
@@ -762,8 +742,6 @@ tabsetPanel <- function(...,
|
||||
id = NULL,
|
||||
selected = NULL,
|
||||
type = c("tabs", "pills", "hidden"),
|
||||
header = NULL,
|
||||
footer = NULL,
|
||||
position = deprecated()) {
|
||||
if (lifecycle::is_present(position)) {
|
||||
shinyDeprecated(
|
||||
@@ -775,18 +753,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)
|
||||
|
||||
tags$div(
|
||||
class = "tabbable",
|
||||
!!!dropNulls(list(
|
||||
tabset$navList,
|
||||
header,
|
||||
tabset$content,
|
||||
footer
|
||||
))
|
||||
)
|
||||
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)
|
||||
}
|
||||
|
||||
#' Create a navigation list panel
|
||||
@@ -806,10 +784,8 @@ tabsetPanel <- function(...,
|
||||
#' navigation list.
|
||||
#' @param fluid `TRUE` to use fluid layout; `FALSE` to use fixed
|
||||
#' layout.
|
||||
#' @param widths Column widths of the navigation list and tabset content areas
|
||||
#' @param widths Column withs 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
|
||||
@@ -836,30 +812,37 @@ 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)
|
||||
|
||||
tabset <- buildTabset(
|
||||
..., ulClass = "nav nav-pills nav-stacked",
|
||||
textFilter = function(text) tags$li(class = "navbar-brand", text),
|
||||
id = id, selected = 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)
|
||||
)
|
||||
|
||||
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))
|
||||
)
|
||||
)
|
||||
# return the row
|
||||
if (fluid)
|
||||
fluidRow(columns)
|
||||
else
|
||||
fixedRow(columns)
|
||||
}
|
||||
|
||||
# Helpers to build tabsetPanels (& Co.) and their elements
|
||||
@@ -877,14 +860,14 @@ containsSelectedTab <- function(tabs) {
|
||||
}
|
||||
|
||||
findAndMarkSelectedTab <- function(tabs, selected, foundSelected) {
|
||||
tabs <- lapply(tabs, function(x) {
|
||||
if (foundSelected || is.character(x)) {
|
||||
tabs <- lapply(tabs, function(div) {
|
||||
if (foundSelected || is.character(div)) {
|
||||
# Strings are not selectable items
|
||||
|
||||
} else if (isNavbarMenu(x)) {
|
||||
} else if (inherits(div, "shiny.navbarmenu")) {
|
||||
# Recur for navbarMenus
|
||||
res <- findAndMarkSelectedTab(x$tabs, selected, foundSelected)
|
||||
x$tabs <- res$tabs
|
||||
res <- findAndMarkSelectedTab(div$tabs, selected, foundSelected)
|
||||
div$tabs <- res$tabs
|
||||
foundSelected <<- res$foundSelected
|
||||
|
||||
} else {
|
||||
@@ -893,16 +876,16 @@ findAndMarkSelectedTab <- function(tabs, selected, foundSelected) {
|
||||
# mark first available item as selected
|
||||
if (is.null(selected)) {
|
||||
foundSelected <<- TRUE
|
||||
x <- markTabAsSelected(x)
|
||||
div <- markTabAsSelected(div)
|
||||
} else {
|
||||
tabValue <- x$attribs$`data-value` %||% x$attribs$title
|
||||
tabValue <- div$attribs$`data-value` %||% div$attribs$title
|
||||
if (identical(selected, tabValue)) {
|
||||
foundSelected <<- TRUE
|
||||
x <- markTabAsSelected(x)
|
||||
div <- markTabAsSelected(div)
|
||||
}
|
||||
}
|
||||
}
|
||||
return(x)
|
||||
return(div)
|
||||
})
|
||||
return(list(tabs = tabs, foundSelected = foundSelected))
|
||||
}
|
||||
@@ -928,10 +911,9 @@ navbarMenuTextFilter <- function(text) {
|
||||
|
||||
# This function is called internally by navbarPage, tabsetPanel
|
||||
# and navlistPanel
|
||||
buildTabset <- function(..., ulClass, textFilter = NULL, id = NULL,
|
||||
buildTabset <- function(tabs, ulClass, textFilter = NULL, id = NULL,
|
||||
selected = NULL, foundSelected = FALSE) {
|
||||
|
||||
tabs <- dropNulls(list2(...))
|
||||
res <- findAndMarkSelectedTab(tabs, selected, foundSelected)
|
||||
tabs <- res$tabs
|
||||
foundSelected <- res$foundSelected
|
||||
@@ -952,10 +934,10 @@ buildTabset <- function(..., ulClass, textFilter = NULL, id = NULL,
|
||||
tabs = tabs, textFilter = textFilter)
|
||||
|
||||
tabNavList <- tags$ul(class = ulClass, id = id,
|
||||
`data-tabsetid` = tabsetId, !!!lapply(tabs, "[[", "liTag"))
|
||||
`data-tabsetid` = tabsetId, lapply(tabs, "[[", 1))
|
||||
|
||||
tabContent <- tags$div(class = "tab-content",
|
||||
`data-tabsetid` = tabsetId, !!!lapply(tabs, "[[", "divTag"))
|
||||
`data-tabsetid` = tabsetId, lapply(tabs, "[[", 2))
|
||||
|
||||
list(navList = tabNavList, content = tabContent)
|
||||
}
|
||||
@@ -967,173 +949,56 @@ buildTabset <- function(..., ulClass, textFilter = NULL, id = NULL,
|
||||
buildTabItem <- function(index, tabsetId, foundSelected, tabs = NULL,
|
||||
divTag = NULL, textFilter = NULL) {
|
||||
|
||||
divTag <- divTag %||% tabs[[index]]
|
||||
divTag <- if (!is.null(divTag)) divTag else tabs[[index]]
|
||||
|
||||
# Handles navlistPanel() headers and dropdown dividers
|
||||
if (is.character(divTag) && !is.null(textFilter)) {
|
||||
return(list(liTag = textFilter(divTag), divTag = NULL))
|
||||
}
|
||||
# text item: pass it to the textFilter if it exists
|
||||
liTag <- textFilter(divTag)
|
||||
divTag <- NULL
|
||||
|
||||
if (isNavbarMenu(divTag)) {
|
||||
# tabPanelMenu item: build the child tabset
|
||||
tabset <- buildTabset(
|
||||
!!!divTag$tabs, ulClass = "dropdown-menu",
|
||||
textFilter = navbarMenuTextFilter,
|
||||
foundSelected = foundSelected
|
||||
} 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
|
||||
)
|
||||
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)
|
||||
}
|
||||
})
|
||||
)
|
||||
}
|
||||
divTag <- tabset$content$children
|
||||
|
||||
|
||||
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
|
||||
} 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 = liTag, divTag = divTag))
|
||||
}
|
||||
|
||||
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,12 +49,6 @@ 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
|
||||
|
||||
@@ -79,9 +79,12 @@ sliderInput <- function(inputId, label, min, max, value, step = NULL,
|
||||
round = FALSE, ticks = TRUE, animate = FALSE,
|
||||
width = NULL, sep = ",", pre = NULL, post = NULL,
|
||||
timeFormat = NULL, timezone = NULL, dragRange = TRUE) {
|
||||
validate_slider_value(min, max, value, "sliderInput")
|
||||
|
||||
dataType <- getSliderType(min, max, value)
|
||||
# Force required arguments for maximally informative errors
|
||||
inputId; label; min; max; value
|
||||
|
||||
validate_slider_value(min, max, value, "sliderInput")
|
||||
dataType <- slider_type(value)
|
||||
|
||||
if (is.null(timeFormat)) {
|
||||
timeFormat <- switch(dataType, date = "%F", datetime = "%F %T", number = NULL)
|
||||
@@ -288,37 +291,45 @@ findStepSize <- function(min, max, step) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
# Throw a warning if ever `value` is not in the [`min`, `max`] range
|
||||
validate_slider_value <- function(min, max, value, fun) {
|
||||
if (length(min) != 1 || is_na(min) ||
|
||||
length(max) != 1 || is_na(max) ||
|
||||
length(value) < 1 || length(value) > 2 || any(is.na(value)))
|
||||
{
|
||||
stop(call. = FALSE,
|
||||
sprintf("In %s(): `min`, `max`, and `value` cannot be NULL, NA, or empty.", fun)
|
||||
if (!is_slider_type(min) || length(min) != 1 || is_na(min)) {
|
||||
rlang::abort("sliderInput(min) must be a single number, Date, or POSIXct")
|
||||
}
|
||||
if (!is_slider_type(min) || length(max) != 1 || is_na(max)) {
|
||||
rlang::abort("sliderInput(value) must be a single number, Date, or POSIXct")
|
||||
}
|
||||
if (!is_slider_type(value) || !length(value) %in% c(1, 2) || any(is_na(value))) {
|
||||
rlang::abort(
|
||||
"sliderInput(value) must be a single or pair of numbers, Dates, or POSIXcts"
|
||||
)
|
||||
}
|
||||
|
||||
if (min(value) < min) {
|
||||
warning(call. = FALSE,
|
||||
sprintf(
|
||||
"In %s(): `value` should be greater than or equal to `min` (value = %s, min = %s).",
|
||||
fun, paste(value, collapse = ", "), min
|
||||
)
|
||||
)
|
||||
if (!identical(class(min), class(value)) || !identical(class(max), class(value))) {
|
||||
rlang::abort(c(
|
||||
"Type mismatch for `min`, `max`, and `value`.",
|
||||
i = "All values must have same type: either numeric, Date, or POSIXt."
|
||||
))
|
||||
}
|
||||
|
||||
if (max(value) > max) {
|
||||
warning(
|
||||
noBreaks. = TRUE, call. = FALSE,
|
||||
sprintf(
|
||||
"In %s(): `value` should be less than or equal to `max` (value = %s, max = %s).",
|
||||
fun, paste(value, collapse = ", "), max
|
||||
)
|
||||
)
|
||||
if (min(value) < min || max(value) > max) {
|
||||
rlang::abort("`value` does not lie within [min, max]")
|
||||
}
|
||||
}
|
||||
|
||||
is_slider_type <- function(x) {
|
||||
is.numeric(x) || inherits(x, "Date") || inherits(x, "POSIXct")
|
||||
}
|
||||
slider_type <- function(x) {
|
||||
if (is.numeric(x)) {
|
||||
"number"
|
||||
} else if (inherits(x, "Date")) {
|
||||
"date"
|
||||
} else if (inherits(x, "POSIXct")) {
|
||||
"datetime"
|
||||
}
|
||||
}
|
||||
|
||||
#' @rdname sliderInput
|
||||
#'
|
||||
|
||||
159
R/navtreePanel.R
159
R/navtreePanel.R
@@ -1,159 +0,0 @@
|
||||
#' @export
|
||||
navtreePanel <- function(..., id = NULL,
|
||||
selected = NULL,
|
||||
fluid = TRUE,
|
||||
# Also allow for string to determine padding in a flex layout?
|
||||
widths = c(3, 9)) {
|
||||
# TODO: how to incorporate this into a sidebar layout?
|
||||
|
||||
if (!is.null(id))
|
||||
selected <- restoreInput(id = id, default = selected)
|
||||
|
||||
tabset <- buildTreePanel(..., ulClass = "nav nav-navtree", id = id, selected = selected)
|
||||
|
||||
row <- if (fluid) fluidRow else fixedRow
|
||||
|
||||
navList <- attachDependencies(
|
||||
tabset$navList,
|
||||
bslib::bs_dependency_defer(navtreeCssDependency)
|
||||
)
|
||||
|
||||
row(
|
||||
column(widths[[1]], navList),
|
||||
column(widths[[2]], tabset$content)
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
# Algorithm inspired by buildTabset() but we need different HTML/CSS,
|
||||
# and menus are rendered as collapse toggles instead of Bootstrap dropdowns
|
||||
buildTreePanel <- function(..., ulClass, id = NULL, selected = NULL, foundSelected = FALSE, depth = 0) {
|
||||
|
||||
tabs <- list2(...)
|
||||
res <- findAndMarkSelectedTab(tabs, selected, foundSelected)
|
||||
tabs <- res$tabs
|
||||
foundSelected <- res$foundSelected
|
||||
|
||||
# add input class if we have an id
|
||||
if (!is.null(id)) ulClass <- paste(ulClass, "shiny-tab-input")
|
||||
|
||||
if (anyNamed(tabs)) {
|
||||
nms <- names(tabs)
|
||||
nms <- nms[nzchar(nms)]
|
||||
stop("Tabs should all be unnamed arguments, but some are named: ",
|
||||
paste(nms, collapse = ", "))
|
||||
}
|
||||
|
||||
tabsetId <- p_randomInt(1000, 10000)
|
||||
tabs <- lapply(
|
||||
seq_len(length(tabs)), buildTreeItem,
|
||||
tabsetId = tabsetId,
|
||||
foundSelected = foundSelected,
|
||||
tabs = tabs, depth = depth
|
||||
)
|
||||
|
||||
list(
|
||||
navList = tags$ul(
|
||||
class = ulClass, id = id,
|
||||
`data-tabsetid` = tabsetId,
|
||||
!!!lapply(tabs, "[[", "liTag")
|
||||
),
|
||||
content = div(
|
||||
class = "tab-content",
|
||||
`data-tabsetid` = tabsetId,
|
||||
!!!lapply(tabs, "[[", "divTag")
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
buildTreeItem <- function(index, tabsetId, foundSelected, tabs = NULL, divTag = NULL, depth = 0) {
|
||||
divTag <- divTag %||% tabs[[index]]
|
||||
|
||||
subMenuPadding <- if (depth > 0) css(padding_left = paste0(depth * 1.25, "rem"))
|
||||
|
||||
if (isNavbarMenu(divTag)) {
|
||||
icon <- getIcon(iconClass = divTag$iconClass)
|
||||
if (!is.null(icon)) {
|
||||
warning("Configurable icons are not yet supported in navtreePanel().")
|
||||
}
|
||||
tabset <- buildTreePanel(
|
||||
!!!divTag$tabs, ulClass = "nav nav-navtree",
|
||||
foundSelected = foundSelected, depth = depth + 1
|
||||
)
|
||||
# Sort of like .dropdown in the tabsetPanel() case,
|
||||
# but utilizes collapsing (which is recursive) instead of dropdown
|
||||
active <- containsSelectedTab(divTag$tabs)
|
||||
menuId <- paste0("collapse-", p_randomInt(1000, 10000))
|
||||
liTag <- tags$li(
|
||||
tags$a(
|
||||
class = if (!active) "collapsed",
|
||||
"data-toggle" = "collapse",
|
||||
"data-value" = divTag$menuName,
|
||||
"data-target" = paste0("#", menuId),
|
||||
role = "button",
|
||||
style = subMenuPadding,
|
||||
getIcon(iconClass = divTag$iconClass),
|
||||
divTag$title
|
||||
),
|
||||
div(
|
||||
class = "collapse",
|
||||
class = if (active) "show in",
|
||||
id = menuId,
|
||||
tabset$navList
|
||||
)
|
||||
)
|
||||
return(list(liTag = liTag, divTag = tabset$content$children))
|
||||
}
|
||||
|
||||
if (isTabPanel(divTag)) {
|
||||
# Borrow from the usual nav logic so we get the right
|
||||
# li.active vs li > a.active (BS4) markup
|
||||
navItem <- buildNavItem(divTag, tabsetId, index)
|
||||
liTag <- navItem$liTag
|
||||
return(
|
||||
list(
|
||||
divTag = navItem$divTag,
|
||||
liTag = tagFunction(function() {
|
||||
# Incoming liTag should be a tagFunction()
|
||||
liTag <- if (inherits(liTag, "shiny.tag.function")) liTag() else liTag
|
||||
|
||||
# Add padding for sub menu anchors
|
||||
liTag$children[[1]] <- tagAppendAttributes(
|
||||
liTag$children[[1]], style = subMenuPadding
|
||||
)
|
||||
|
||||
liTag
|
||||
})
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
abort("navtreePanel() items must be tabPanel()s and/or tabPanelMenu()s")
|
||||
}
|
||||
|
||||
|
||||
navtreeCssDependency <- function(theme) {
|
||||
name <- "navtreePanel"
|
||||
version <- packageVersion("shiny")
|
||||
if (!is_bs_theme(theme)) {
|
||||
# TODO: Should we allow navtreePanel() to be statically rendered?
|
||||
# Can/should we move away from href="shared/*"?
|
||||
htmlDependency(
|
||||
name = name, version = version,
|
||||
src = c(href = "shared/navtree", file = "www/shared/navtree"),
|
||||
package = "shiny",
|
||||
stylesheet = "navtree.css",
|
||||
script = "navtree.js"
|
||||
)
|
||||
} else {
|
||||
navtree <- system.file(package = "shiny", "www/shared/navtree")
|
||||
bslib::bs_dependency(
|
||||
sass::sass_file(file.path(navtree, "navtree.scss")),
|
||||
theme = theme,
|
||||
name = name,
|
||||
version = version,
|
||||
cache_key_extra = version,
|
||||
.dep_args = list(script = file.path(navtree, "navtree.js"))
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -568,7 +568,7 @@ ReactiveValues <- R6Class(
|
||||
#' @seealso [isolate()] and [is.reactivevalues()].
|
||||
#' @export
|
||||
reactiveValues <- function(...) {
|
||||
args <- list2(...)
|
||||
args <- list(...)
|
||||
if ((length(args) > 0) && (is.null(names(args)) || any(names(args) == "")))
|
||||
rlang::abort("All arguments passed to reactiveValues() must be named.")
|
||||
|
||||
@@ -1915,7 +1915,7 @@ reactivePoll <- function(intervalMillis, session, checkFunc, valueFunc) {
|
||||
#' @export
|
||||
reactiveFileReader <- function(intervalMillis, session, filePath, readFunc, ...) {
|
||||
filePath <- coerceToFunc(filePath)
|
||||
extraArgs <- list2(...)
|
||||
extraArgs <- list(...)
|
||||
|
||||
reactivePoll(
|
||||
intervalMillis, session,
|
||||
|
||||
@@ -178,7 +178,7 @@ getShinyOption <- function(name, default = NULL) {
|
||||
#' @aliases shiny-options
|
||||
#' @export
|
||||
shinyOptions <- function(...) {
|
||||
newOpts <- list2(...)
|
||||
newOpts <- list(...)
|
||||
|
||||
if (length(newOpts) > 0) {
|
||||
# If we're within a session, modify at the session level.
|
||||
|
||||
@@ -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 list2
|
||||
#' is_false
|
||||
#' missing_arg is_missing maybe_missing
|
||||
#' @importFrom ellipsis
|
||||
#' check_dots_empty check_dots_unnamed
|
||||
|
||||
19
R/utils.R
19
R/utils.R
@@ -1176,7 +1176,7 @@ reactiveStop <- function(message = "", class = NULL) {
|
||||
#'
|
||||
#' }
|
||||
validate <- function(..., errorClass = character(0)) {
|
||||
results <- sapply(list2(...), function(x) {
|
||||
results <- sapply(list(...), function(x) {
|
||||
# Detect NULL or NA
|
||||
if (is.null(x))
|
||||
return(NA_character_)
|
||||
@@ -1751,23 +1751,6 @@ createVarPromiseDomain <- function(env, name, value) {
|
||||
)
|
||||
}
|
||||
|
||||
getSliderType <- function(min, max, value) {
|
||||
vals <- dropNulls(list(value, min, max))
|
||||
if (length(vals) == 0) return("")
|
||||
type <- unique(lapply(vals, function(x) {
|
||||
if (inherits(x, "Date")) "date"
|
||||
else if (inherits(x, "POSIXt")) "datetime"
|
||||
else "number"
|
||||
}))
|
||||
if (length(type) > 1) {
|
||||
rlang::abort(c(
|
||||
"Type mismatch for `min`, `max`, and `value`.",
|
||||
"All values must either be numeric, Date, or POSIXt."
|
||||
))
|
||||
}
|
||||
type[[1]]
|
||||
}
|
||||
|
||||
# Reads the `shiny.sharedSecret` global option, and returns a function that can
|
||||
# be used to test header values for a match.
|
||||
loadSharedSecret <- function() {
|
||||
|
||||
@@ -1,50 +0,0 @@
|
||||
.nav-navtree {
|
||||
display: block !important;
|
||||
}
|
||||
|
||||
.nav-navtree li.active, .nav-navtree li > a.active, .nav-navtree li a:focus {
|
||||
font-weight: 600;
|
||||
background-color: rgba(0, 123, 255, 0.3) !important;
|
||||
color: rgba(33, 37, 41, 0.85);
|
||||
}
|
||||
|
||||
.nav-navtree li:not(.active) > a:not(.active):hover {
|
||||
background-color: rgba(33, 37, 41, 0.1);
|
||||
}
|
||||
|
||||
.nav-navtree li a {
|
||||
display: inline-flex !important;
|
||||
align-items: center !important;
|
||||
width: 100% !important;
|
||||
padding: .1875rem .5rem;
|
||||
border: 1px solid #dee2e6 !important;
|
||||
color: rgba(33, 37, 41, 0.65) !important;
|
||||
text-decoration: none !important;
|
||||
}
|
||||
|
||||
.nav-navtree li a i {
|
||||
padding-right: 1.25rem !important;
|
||||
}
|
||||
|
||||
.nav-navtree li a[data-toggle="collapse"]::before {
|
||||
width: 1.25em;
|
||||
line-height: 0;
|
||||
content: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='rgba%2833, 37, 41, 0.5%29' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M5 14l6-6-6-6'/%3e%3c/svg%3e");
|
||||
transition: transform 0.35s ease;
|
||||
transform-origin: .5em 50%;
|
||||
padding-right: 1rem;
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.nav-navtree li a[data-toggle="collapse"]::before {
|
||||
transition: none;
|
||||
}
|
||||
}
|
||||
|
||||
.nav-navtree li a[data-toggle="collapse"]:not(.collapsed) {
|
||||
color: rgba(33, 37, 41, 0.85);
|
||||
}
|
||||
|
||||
.nav-navtree li a[data-toggle="collapse"]:not(.collapsed)::before {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
// nav-navtree is built on a combination of Bootstrap's tab &
|
||||
// collapse components, but the tab component isn't smart enough to
|
||||
// know about the deactive when are activated. Note that this logic also
|
||||
// exists in input_binding_tabinput.js and is repeated here in case this
|
||||
// component wants to be statically rendered
|
||||
$(document).on("shown.bs.tab", ".nav-navtree", function(e) {
|
||||
var tgt = $(e.target);
|
||||
var nav = tgt.parents(".nav-navtree");
|
||||
nav.find("li").not(tgt).removeClass("active"); // BS3
|
||||
nav.find("li > a").not(tgt).removeClass("active"); // BS4
|
||||
});
|
||||
@@ -1,66 +0,0 @@
|
||||
// Inspired by BS5's sidebar nav
|
||||
// https://github.com/twbs/bootstrap/blob/548be2e/site/assets/scss/_sidebar.scss#L7
|
||||
// As well as
|
||||
// https://chniter.github.io/bstreeview/
|
||||
|
||||
$body-color: $text-color !default; // BS3compat
|
||||
$link-decoration: none !default;
|
||||
$link-hover-decoration: underline !default;
|
||||
|
||||
$sidebar-collapse-icon-color: rgba($body-color, 0.5) !default;
|
||||
$sidebar-collapse-icon: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 16 16'><path fill='none' stroke='#{$sidebar-collapse-icon-color}' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M5 14l6-6-6-6'/></svg>") !default;
|
||||
|
||||
// TODO: make this more configurable from the R side?
|
||||
$navtree-anchor-color: rgba($body-color, .65) !default;
|
||||
$navtree-active-bg: rgba($component-active-bg, .3) !default;
|
||||
$navtree-active-color: rgba($body-color, .85) !default;
|
||||
|
||||
.nav-navtree {
|
||||
// Override collapse behaviors
|
||||
display: block !important;
|
||||
|
||||
li {
|
||||
// Handle both li.active (BS3) and li > a.active (BS4)
|
||||
&.active, > a.active, a:focus {
|
||||
font-weight: 600;
|
||||
background-color: $navtree-active-bg !important;
|
||||
color: $navtree-active-color;
|
||||
}
|
||||
&:not(.active) > a:not(.active):hover {
|
||||
background-color: rgba($body-color, .1);
|
||||
}
|
||||
|
||||
a {
|
||||
display: inline-flex !important; // Needed for centering/transforming the collapse icon
|
||||
align-items: center !important;
|
||||
width: 100% !important;
|
||||
padding: .1875rem .5rem;
|
||||
border: $border-width solid $border-color !important;
|
||||
color: $navtree-anchor-color !important;
|
||||
text-decoration: none !important;
|
||||
i {
|
||||
padding-right: 1.25rem !important;
|
||||
}
|
||||
// Add chevron if there's a submenu
|
||||
&[data-toggle="collapse"] {
|
||||
&::before {
|
||||
width: 1.25em;
|
||||
line-height: 0; // Align in the middle
|
||||
content: escape-svg($sidebar-collapse-icon);
|
||||
@include transition(transform .35s ease);
|
||||
transform-origin: .5em 50%;
|
||||
padding-right: 1rem;
|
||||
}
|
||||
|
||||
&:not(.collapsed) {
|
||||
color: $navtree-active-color;
|
||||
&::before {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
}
|
||||
}
|
||||
} // a
|
||||
|
||||
} // li
|
||||
|
||||
} // nav
|
||||
@@ -1198,26 +1198,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/get-substitution.js
|
||||
var require_get_substitution = __commonJS(function(exports2, module2) {
|
||||
var toObject4 = require_to_object();
|
||||
@@ -1266,6 +1246,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/is-regexp.js
|
||||
var require_is_regexp = __commonJS(function(exports2, module2) {
|
||||
var isObject3 = require_is_object();
|
||||
@@ -2996,9 +2996,7 @@
|
||||
function getTargetTabs($tabset, $tabContent, target) {
|
||||
var dataValue = "[data-value='" + $escape(target) + "']";
|
||||
var $aTag = $tabset.find("a" + dataValue);
|
||||
var $liTag = $aTag.parent("li");
|
||||
if ($liTag.length === 0)
|
||||
$liTag = $aTag;
|
||||
var $liTag = $aTag.parent();
|
||||
if ($liTag.length === 0) {
|
||||
throw "There is no tabPanel (or navbarMenu) with value (or menuName) equal to '" + target + "'";
|
||||
}
|
||||
@@ -3007,10 +3005,7 @@
|
||||
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']");
|
||||
if ($dropdownLiTags.parent("li").length > 0) {
|
||||
$dropdownLiTags = $dropdownLiTags.parent("li");
|
||||
}
|
||||
var $dropdownLiTags = $dropdownTabset.find("a[data-toggle='tab']").parent("li");
|
||||
$dropdownLiTags.each(function(i, el) {
|
||||
$liTags.push(import_jquery6.default(el));
|
||||
});
|
||||
@@ -3041,9 +3036,6 @@
|
||||
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) {
|
||||
@@ -3055,10 +3047,7 @@
|
||||
if ($aTag.attr("data-toggle") === "tab") {
|
||||
var index = getTabIndex($tabset, tabsetId);
|
||||
var tabId = "tab-" + tabsetId + "-" + index;
|
||||
var anchor = $liTag.find("> a");
|
||||
if (anchor.length === 0)
|
||||
anchor = $liTag;
|
||||
anchor.attr("href", "#" + tabId);
|
||||
$liTag.find("> a").attr("href", "#" + tabId);
|
||||
$divTag.attr("id", tabId);
|
||||
}
|
||||
if (message.position === "before") {
|
||||
@@ -3086,29 +3075,13 @@
|
||||
$tabContent[0].appendChild(el);
|
||||
Shiny.renderContent(el, el.innerHTML || el.textContent);
|
||||
});
|
||||
if ($tabset.hasClass("nav-navtree") && $liTag.hasClass("dropdown")) {
|
||||
var collapseId = "collapse-" + tabsetId + "-" + getTabIndex($tabset, tabsetId);
|
||||
$tabset.find(".dropdown").each(function(i, el) {
|
||||
var $el = import_jquery6.default(el).removeClass("dropdown nav-item");
|
||||
$el.find(".dropdown-toggle").removeClass("dropdown-toggle nav-link").addClass(message.select ? "" : "collapsed").attr("data-toggle", "collapse").attr("data-target", "#" + collapseId);
|
||||
var collapse = import_jquery6.default("<div>").addClass("collapse" + (message.select ? " show" : "")).attr("id", collapseId);
|
||||
var menu = $el.find(".dropdown-menu").removeClass("dropdown-menu").addClass("nav nav-navtree").wrap(collapse);
|
||||
var depth = $el.parents(".nav-navtree").length - 1;
|
||||
if (depth > 0) {
|
||||
$el.find("a").css("padding-left", depth + "rem");
|
||||
}
|
||||
if (menu.find("li").length === 0) {
|
||||
menu.find("a").removeClass("dropdown-item").addClass("nav-link").wrap("<li class='nav-item'></li>");
|
||||
}
|
||||
});
|
||||
}
|
||||
if (message.select) {
|
||||
$liTag.find("a").tab("show");
|
||||
}
|
||||
function getTabIndex($tabset2, tabsetId2) {
|
||||
var existingTabIds = [0];
|
||||
$tabset2.find("a[data-toggle='tab']").each(function() {
|
||||
var $tab = import_jquery6.default(this);
|
||||
$tabset2.find("> li").each(function() {
|
||||
var $tab = import_jquery6.default(this).find("> a[data-toggle='tab']");
|
||||
if ($tab.length > 0) {
|
||||
var href = $tab.attr("href").replace(/.*(?=#[^\s]+$)/, "");
|
||||
var _index = href.replace("#tab-" + tabsetId2 + "-", "");
|
||||
@@ -3143,9 +3116,9 @@
|
||||
}
|
||||
});
|
||||
function ensureTabsetHasVisibleTab($tabset) {
|
||||
var inputBinding = $tabset.data("shiny-input-binding");
|
||||
if (!inputBinding.getValue($tabset)) {
|
||||
if ($tabset.find("li.active").not(".dropdown").length === 0) {
|
||||
var destTabValue = getFirstTab($tabset);
|
||||
var inputBinding = $tabset.data("shiny-input-binding");
|
||||
var evt = jQuery.Event("shiny:updateinput");
|
||||
evt.binding = inputBinding;
|
||||
$tabset.trigger(evt);
|
||||
@@ -6111,7 +6084,7 @@
|
||||
return import_jquery6.default(scope).find("ul.nav.shiny-tab-input");
|
||||
},
|
||||
getValue: function getValue(el) {
|
||||
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");
|
||||
var anchor = import_jquery6.default(el).find("li:not(.dropdown).active").children("a");
|
||||
if (anchor.length === 1)
|
||||
return this._getTabName(anchor);
|
||||
return null;
|
||||
@@ -6120,11 +6093,10 @@
|
||||
var self2 = this;
|
||||
var success = false;
|
||||
if (value) {
|
||||
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");
|
||||
var anchors = import_jquery6.default(el).find("li:not(.dropdown)").children("a");
|
||||
anchors.each(function() {
|
||||
if (self2._getTabName(import_jquery6.default(this)) === value) {
|
||||
import_jquery6.default(this).tab("show");
|
||||
import_jquery6.default(this).parents(".collapse").collapse("show");
|
||||
success = true;
|
||||
return false;
|
||||
}
|
||||
@@ -6146,9 +6118,7 @@
|
||||
import_jquery6.default(el).trigger("change");
|
||||
},
|
||||
subscribe: function subscribe(el, callback) {
|
||||
var deactivateOtherTabs = this._deactivateOtherTabs;
|
||||
import_jquery6.default(el).on("change shown.bootstrapTabInputBinding shown.bs.tab.bootstrapTabInputBinding", function(event) {
|
||||
deactivateOtherTabs(event);
|
||||
callback();
|
||||
});
|
||||
},
|
||||
@@ -6157,12 +6127,6 @@
|
||||
},
|
||||
_getTabName: function _getTabName(anchor) {
|
||||
return anchor.attr("data-value") || anchor.text();
|
||||
},
|
||||
_deactivateOtherTabs: function _deactivateOtherTabs(event) {
|
||||
var tgt = import_jquery6.default(event.target);
|
||||
var nav = tgt.parents(".nav-navtree");
|
||||
nav.find("li").not(tgt).removeClass("active");
|
||||
nav.find("li > a").not(tgt).removeClass("active");
|
||||
}
|
||||
});
|
||||
inputBindings.register(bootstrapTabInputBinding, "shiny.bootstrapTabInput");
|
||||
@@ -7295,68 +7259,28 @@
|
||||
}, {unsafe: true});
|
||||
}
|
||||
|
||||
// node_modules/core-js/modules/es.string.match.js
|
||||
// node_modules/core-js/modules/es.string.replace.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 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 regExpExec = require_regexp_exec_abstract();
|
||||
var max3 = Math.max;
|
||||
var min2 = Math.min;
|
||||
var maybeToString = function(it) {
|
||||
return it === void 0 ? it : String(it);
|
||||
};
|
||||
fixRegExpWellKnownSymbolLogic2("replace", 2, function(REPLACE, nativeReplace, maybeCallNative, reason) {
|
||||
fixRegExpWellKnownSymbolLogic("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 = requireObjectCoercible2(this);
|
||||
var O = requireObjectCoercible(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);
|
||||
},
|
||||
@@ -7366,7 +7290,7 @@
|
||||
if (res.done)
|
||||
return res.value;
|
||||
}
|
||||
var rx = anObject3(regexp);
|
||||
var rx = anObject2(regexp);
|
||||
var S = String(this);
|
||||
var functionalReplace = typeof replaceValue === "function";
|
||||
if (!functionalReplace)
|
||||
@@ -7378,7 +7302,7 @@
|
||||
}
|
||||
var results = [];
|
||||
while (true) {
|
||||
var result = regExpExec2(rx, S);
|
||||
var result = regExpExec(rx, S);
|
||||
if (result === null)
|
||||
break;
|
||||
results.push(result);
|
||||
@@ -7386,7 +7310,7 @@
|
||||
break;
|
||||
var matchStr = String(result[0]);
|
||||
if (matchStr === "")
|
||||
rx.lastIndex = advanceStringIndex2(S, toLength5(rx.lastIndex), fullUnicode);
|
||||
rx.lastIndex = advanceStringIndex(S, toLength4(rx.lastIndex), fullUnicode);
|
||||
}
|
||||
var accumulatedResult = "";
|
||||
var nextSourcePosition = 0;
|
||||
@@ -7418,13 +7342,13 @@
|
||||
|
||||
// node_modules/core-js/modules/es.string.split.js
|
||||
"use strict";
|
||||
var fixRegExpWellKnownSymbolLogic3 = require_fix_regexp_well_known_symbol_logic();
|
||||
var fixRegExpWellKnownSymbolLogic2 = require_fix_regexp_well_known_symbol_logic();
|
||||
var isRegExp = require_is_regexp();
|
||||
var anObject4 = require_an_object();
|
||||
var requireObjectCoercible3 = require_require_object_coercible();
|
||||
var anObject3 = require_an_object();
|
||||
var requireObjectCoercible2 = require_require_object_coercible();
|
||||
var speciesConstructor = require_species_constructor();
|
||||
var advanceStringIndex3 = require_advance_string_index();
|
||||
var toLength6 = require_to_length();
|
||||
var advanceStringIndex2 = require_advance_string_index();
|
||||
var toLength5 = require_to_length();
|
||||
var callRegExpExec = require_regexp_exec_abstract();
|
||||
var regexpExec = require_regexp_exec();
|
||||
var fails5 = require_fails();
|
||||
@@ -7434,11 +7358,11 @@
|
||||
var SUPPORTS_Y = !fails5(function() {
|
||||
return !RegExp(MAX_UINT32, "y");
|
||||
});
|
||||
fixRegExpWellKnownSymbolLogic3("split", 2, function(SPLIT, nativeSplit, maybeCallNative) {
|
||||
fixRegExpWellKnownSymbolLogic2("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(requireObjectCoercible3(this));
|
||||
var string = String(requireObjectCoercible2(this));
|
||||
var lim = limit === void 0 ? MAX_UINT32 : limit >>> 0;
|
||||
if (lim === 0)
|
||||
return [];
|
||||
@@ -7481,7 +7405,7 @@
|
||||
internalSplit = nativeSplit;
|
||||
return [
|
||||
function split(separator, limit) {
|
||||
var O = requireObjectCoercible3(this);
|
||||
var O = requireObjectCoercible2(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);
|
||||
},
|
||||
@@ -7489,7 +7413,7 @@
|
||||
var res = maybeCallNative(internalSplit, regexp, this, limit, internalSplit !== nativeSplit);
|
||||
if (res.done)
|
||||
return res.value;
|
||||
var rx = anObject4(regexp);
|
||||
var rx = anObject3(regexp);
|
||||
var S = String(this);
|
||||
var C = speciesConstructor(rx, RegExp);
|
||||
var unicodeMatching = rx.unicode;
|
||||
@@ -7507,8 +7431,8 @@
|
||||
splitter.lastIndex = SUPPORTS_Y ? q : 0;
|
||||
var z = callRegExpExec(splitter, SUPPORTS_Y ? S : S.slice(q));
|
||||
var e;
|
||||
if (z === null || (e = min3(toLength6(splitter.lastIndex + (SUPPORTS_Y ? 0 : q)), S.length)) === p) {
|
||||
q = advanceStringIndex3(S, q, unicodeMatching);
|
||||
if (z === null || (e = min3(toLength5(splitter.lastIndex + (SUPPORTS_Y ? 0 : q)), S.length)) === p) {
|
||||
q = advanceStringIndex2(S, q, unicodeMatching);
|
||||
} else {
|
||||
A.push(S.slice(p, q));
|
||||
if (A.length === lim)
|
||||
@@ -7801,12 +7725,6 @@
|
||||
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;
|
||||
@@ -7937,9 +7855,9 @@
|
||||
var $22 = require_export();
|
||||
var fails6 = require_fails();
|
||||
var ArrayBufferModule = require_array_buffer();
|
||||
var anObject5 = require_an_object();
|
||||
var anObject4 = require_an_object();
|
||||
var toAbsoluteIndex3 = require_to_absolute_index();
|
||||
var toLength7 = require_to_length();
|
||||
var toLength6 = require_to_length();
|
||||
var speciesConstructor2 = require_species_constructor();
|
||||
var ArrayBuffer3 = ArrayBufferModule.ArrayBuffer;
|
||||
var DataView2 = ArrayBufferModule.DataView;
|
||||
@@ -7950,12 +7868,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(anObject5(this), start);
|
||||
return nativeArrayBufferSlice.call(anObject4(this), start);
|
||||
}
|
||||
var length = anObject5(this).byteLength;
|
||||
var length = anObject4(this).byteLength;
|
||||
var first = toAbsoluteIndex3(start, length);
|
||||
var fin = toAbsoluteIndex3(end === void 0 ? length : end, length);
|
||||
var result = new (speciesConstructor2(this, ArrayBuffer3))(toLength7(fin - first));
|
||||
var result = new (speciesConstructor2(this, ArrayBuffer3))(toLength6(fin - first));
|
||||
var viewSource = new DataView2(this);
|
||||
var viewTarget = new DataView2(result);
|
||||
var index = 0;
|
||||
@@ -8073,6 +7991,46 @@
|
||||
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,8 +8,6 @@ navlistPanel(
|
||||
...,
|
||||
id = NULL,
|
||||
selected = NULL,
|
||||
header = NULL,
|
||||
footer = NULL,
|
||||
well = TRUE,
|
||||
fluid = TRUE,
|
||||
widths = c(4, 8)
|
||||
@@ -27,19 +25,13 @@ 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 widths of the navigation list and tabset content areas
|
||||
\item{widths}{Column withs of the navigation list and tabset content areas
|
||||
respectively.}
|
||||
}
|
||||
\description{
|
||||
|
||||
@@ -9,8 +9,6 @@ tabsetPanel(
|
||||
id = NULL,
|
||||
selected = NULL,
|
||||
type = c("tabs", "pills", "hidden"),
|
||||
header = NULL,
|
||||
footer = NULL,
|
||||
position = deprecated()
|
||||
)
|
||||
}
|
||||
@@ -34,12 +32,6 @@ 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,7 +34,6 @@ import {
|
||||
updateLabel,
|
||||
getComputedLinkColor,
|
||||
makeBlob,
|
||||
isBS3,
|
||||
} from "./utils";
|
||||
|
||||
import { isQt, isIE, IEVersion } from "./utils/browser";
|
||||
@@ -1242,10 +1241,8 @@ function main(): void {
|
||||
function getTargetTabs($tabset, $tabContent, target) {
|
||||
const dataValue = "[data-value='" + $escape(target) + "']";
|
||||
const $aTag = $tabset.find("a" + dataValue);
|
||||
let $liTag = $aTag.parent("li");
|
||||
// BS3 dropdown anchors are wrapped in <li>, but they can't be in BS4
|
||||
const $liTag = $aTag.parent();
|
||||
|
||||
if ($liTag.length === 0) $liTag = $aTag;
|
||||
if ($liTag.length === 0) {
|
||||
throw (
|
||||
"There is no tabPanel (or navbarMenu) with value" +
|
||||
@@ -1262,12 +1259,10 @@ function main(): void {
|
||||
const $dropdownTabset = $aTag.find("+ ul.dropdown-menu");
|
||||
const dropdownId = $dropdownTabset.attr("data-tabsetid");
|
||||
|
||||
let $dropdownLiTags = $dropdownTabset.find("a[data-toggle='tab']");
|
||||
// BS3 dropdown anchors are wrapped in <li>, but they can't be in BS4
|
||||
const $dropdownLiTags = $dropdownTabset
|
||||
.find("a[data-toggle='tab']")
|
||||
.parent("li");
|
||||
|
||||
if ($dropdownLiTags.parent("li").length > 0) {
|
||||
$dropdownLiTags = $dropdownLiTags.parent("li");
|
||||
}
|
||||
$dropdownLiTags.each(function (i, el) {
|
||||
$liTags.push($(el));
|
||||
});
|
||||
@@ -1291,7 +1286,7 @@ function main(): void {
|
||||
let tabsetId = $parentTabset.attr("data-tabsetid");
|
||||
|
||||
const $divTag = $(message.divTag.html);
|
||||
let $liTag = $(message.liTag.html);
|
||||
const $liTag = $(message.liTag.html);
|
||||
const $aTag = $liTag.find("> a");
|
||||
|
||||
// Unless the item is being prepended/appended, the target tab
|
||||
@@ -1302,12 +1297,6 @@ 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),
|
||||
@@ -1332,11 +1321,7 @@ function main(): void {
|
||||
const index = getTabIndex($tabset, tabsetId);
|
||||
const tabId = "tab-" + tabsetId + "-" + index;
|
||||
|
||||
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);
|
||||
$liTag.find("> a").attr("href", "#" + tabId);
|
||||
$divTag.attr("id", tabId);
|
||||
}
|
||||
|
||||
@@ -1407,50 +1392,6 @@ function main(): void {
|
||||
Shiny.renderContent(el, el.innerHTML || el.textContent);
|
||||
});
|
||||
|
||||
// If we're inserting a navbarMenu() into a navtreePanel() target, we need
|
||||
// to transform buildTabset() output (i.e., a .dropdown component) to
|
||||
// buildTreePanel() output (i.e., a .collapse component), because
|
||||
// insertTab() et al. doesn't know about the relevant tabset container
|
||||
if ($tabset.hasClass("nav-navtree") && $liTag.hasClass("dropdown")) {
|
||||
let collapseId =
|
||||
"collapse-" + tabsetId + "-" + getTabIndex($tabset, tabsetId);
|
||||
|
||||
$tabset.find(".dropdown").each(function (i, el) {
|
||||
let $el = $(el).removeClass("dropdown nav-item");
|
||||
|
||||
$el
|
||||
.find(".dropdown-toggle")
|
||||
.removeClass("dropdown-toggle nav-link")
|
||||
.addClass(message.select ? "" : "collapsed")
|
||||
.attr("data-toggle", "collapse")
|
||||
.attr("data-target", "#" + collapseId);
|
||||
|
||||
let collapse = $("<div>")
|
||||
.addClass("collapse" + (message.select ? " show" : ""))
|
||||
.attr("id", collapseId);
|
||||
|
||||
let menu = $el
|
||||
.find(".dropdown-menu")
|
||||
.removeClass("dropdown-menu")
|
||||
.addClass("nav nav-navtree")
|
||||
.wrap(collapse);
|
||||
|
||||
let depth = $el.parents(".nav-navtree").length - 1;
|
||||
|
||||
if (depth > 0) {
|
||||
$el.find("a").css("padding-left", depth + "rem");
|
||||
}
|
||||
|
||||
if (menu.find("li").length === 0) {
|
||||
menu
|
||||
.find("a")
|
||||
.removeClass("dropdown-item")
|
||||
.addClass("nav-link")
|
||||
.wrap("<li class='nav-item'></li>");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (message.select) {
|
||||
$liTag.find("a").tab("show");
|
||||
}
|
||||
@@ -1470,8 +1411,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("a[data-toggle='tab']").each(function () {
|
||||
const $tab = $(this);
|
||||
$tabset.find("> li").each(function () {
|
||||
const $tab = $(this).find("> a[data-toggle='tab']");
|
||||
|
||||
if ($tab.length > 0) {
|
||||
// remove leading url if it exists. (copy of bootstrap url stripper)
|
||||
@@ -1524,16 +1465,13 @@ function main(): void {
|
||||
|
||||
// If the given tabset has no active tabs, select the first one
|
||||
function ensureTabsetHasVisibleTab($tabset) {
|
||||
const inputBinding = $tabset.data("shiny-input-binding");
|
||||
|
||||
// Use the getValue() method to avoid duplicating the CSS selector
|
||||
// for querying the DOM for the currently active tab
|
||||
if (!inputBinding.getValue($tabset)) {
|
||||
if ($tabset.find("li.active").not(".dropdown").length === 0) {
|
||||
// Note: destTabValue may be null. We still want to proceed
|
||||
// through the below logic and setValue so that the input
|
||||
// value for the tabset gets updated (i.e. input$tabsetId
|
||||
// should be null if there are no tabs).
|
||||
const destTabValue = getFirstTab($tabset);
|
||||
const inputBinding = $tabset.data("shiny-input-binding");
|
||||
const evt = jQuery.Event("shiny:updateinput");
|
||||
|
||||
evt.binding = inputBinding;
|
||||
@@ -5943,12 +5881,7 @@ function main(): void {
|
||||
return $(scope).find("ul.nav.shiny-tab-input");
|
||||
},
|
||||
getValue: function (el) {
|
||||
// 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"
|
||||
);
|
||||
const anchor = $(el).find("li:not(.dropdown).active").children("a");
|
||||
|
||||
if (anchor.length === 1) return this._getTabName(anchor);
|
||||
|
||||
@@ -5959,18 +5892,11 @@ function main(): void {
|
||||
let success = false;
|
||||
|
||||
if (value) {
|
||||
// prettier-ignore
|
||||
let anchors = isBS3()
|
||||
? $(el).find("li:not(.dropdown) > a")
|
||||
: $(el).find(
|
||||
".nav-link:not(.dropdown-toggle), .dropdown-menu > .dropdown-item"
|
||||
);
|
||||
const anchors = $(el).find("li:not(.dropdown)").children("a");
|
||||
|
||||
anchors.each(function () {
|
||||
if (self._getTabName($(this)) === value) {
|
||||
$(this).tab("show");
|
||||
// navtreePanel()'s navbarMenu() uses collapsing -- expand the menu when activated!
|
||||
$(this).parents(".collapse").collapse("show");
|
||||
success = true;
|
||||
return false; // Break out of each()
|
||||
}
|
||||
@@ -5991,12 +5917,9 @@ function main(): void {
|
||||
$(el).trigger("change");
|
||||
},
|
||||
subscribe: function (el, callback) {
|
||||
let deactivateOtherTabs = this._deactivateOtherTabs;
|
||||
|
||||
$(el).on(
|
||||
"change shown.bootstrapTabInputBinding shown.bs.tab.bootstrapTabInputBinding",
|
||||
function (event) {
|
||||
deactivateOtherTabs(event);
|
||||
callback();
|
||||
}
|
||||
);
|
||||
@@ -6007,17 +5930,6 @@ function main(): void {
|
||||
_getTabName: function (anchor) {
|
||||
return anchor.attr("data-value") || anchor.text();
|
||||
},
|
||||
// nav-navtree is built on a combination of Bootstrap's tab &
|
||||
// collapse components, but the tab component isn't smart enough to
|
||||
// know about the deactive when are activated. Note that this logic is
|
||||
// very similar to shinydashboard's deactivateOtherTabs() (in tab.js)
|
||||
_deactivateOtherTabs: function (event) {
|
||||
let tgt = $(event.target);
|
||||
let nav = tgt.parents(".nav-navtree");
|
||||
|
||||
nav.find("li").not(tgt).removeClass("active"); // BS3
|
||||
nav.find("li > a").not(tgt).removeClass("active"); // BS4
|
||||
},
|
||||
});
|
||||
inputBindings.register(bootstrapTabInputBinding, "shiny.bootstrapTabInput");
|
||||
|
||||
|
||||
@@ -361,17 +361,6 @@ 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,
|
||||
@@ -395,5 +384,4 @@ export {
|
||||
updateLabel,
|
||||
getComputedLinkColor,
|
||||
makeBlob,
|
||||
isBS3,
|
||||
};
|
||||
|
||||
28
tests/testthat/_snaps/input-slider.md
Normal file
28
tests/testthat/_snaps/input-slider.md
Normal file
@@ -0,0 +1,28 @@
|
||||
# sliderInput gives informative errors for bad inputs
|
||||
|
||||
Code
|
||||
sliderInput("x", "x")
|
||||
Error <simpleError>
|
||||
argument "min" is missing, with no default
|
||||
Code
|
||||
sliderInput("x", "x", min = NULL, max = 3, value = 2)
|
||||
Error <rlang_error>
|
||||
sliderInput(min) must be a single number, Date, or POSIXct
|
||||
Code
|
||||
sliderInput("x", "x", min = 1, max = NULL, value = 2)
|
||||
Error <rlang_error>
|
||||
sliderInput(value) must be a single number, Date, or POSIXct
|
||||
Code
|
||||
sliderInput("x", "x", min = 1, max = 3, value = NULL)
|
||||
Error <rlang_error>
|
||||
sliderInput(value) must be a single or pair of numbers, Dates, or POSIXcts
|
||||
Code
|
||||
sliderInput("x", "x", min = Sys.Date(), max = Sys.Date(), value = 1)
|
||||
Error <rlang_error>
|
||||
Type mismatch for `min`, `max`, and `value`.
|
||||
i All values must have same type: either numeric, Date, or POSIXt.
|
||||
Code
|
||||
sliderInput("x", "x", min = 1, max = 3, value = 0)
|
||||
Error <rlang_error>
|
||||
`value` does not lie within [min, max]
|
||||
|
||||
@@ -1,339 +0,0 @@
|
||||
# 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>
|
||||
|
||||
@@ -4,99 +4,23 @@ test_that("sliderInput steps don't have rounding errors", {
|
||||
expect_identical(findStepSize(-5.5, 4, NULL), 0.1)
|
||||
})
|
||||
|
||||
test_that("sliderInput can use numbers, dates, or POSIXct", {
|
||||
n <- 1
|
||||
d <- Sys.Date()
|
||||
dt <- Sys.time()
|
||||
|
||||
|
||||
test_that("sliderInput validation", {
|
||||
# Number
|
||||
x <- 10
|
||||
expect_silent(sliderInput('s', 's', x-1, x+1, x))
|
||||
expect_silent(sliderInput('s', 's', x-1, x+1, x-1))
|
||||
expect_silent(sliderInput('s', 's', x-1, x+1, c(x-1, x+1)))
|
||||
|
||||
expect_warning(sliderInput('s', 's', x-1, x+1, x+2))
|
||||
expect_warning(sliderInput('s', 's', x-1, x+1, x-2))
|
||||
expect_warning(sliderInput('s', 's', x-1, x+1, c(x-2, x)))
|
||||
expect_warning(sliderInput('s', 's', x-1, x+1, c(x, x+2)))
|
||||
|
||||
expect_error(sliderInput('s', 's', x-1, x+1))
|
||||
expect_error(sliderInput('s', 's', x-1, x+1, NULL))
|
||||
expect_error(sliderInput('s', 's', x-1, NULL, x))
|
||||
expect_error(sliderInput('s', 's', NULL, x+1, x))
|
||||
expect_error(sliderInput('s', 's', NULL, NULL, x))
|
||||
expect_error(sliderInput('s', 's', x-1, x+1, NA_real_))
|
||||
expect_error(sliderInput('s', 's', x-1, x+1, c(x, NA_real_)))
|
||||
|
||||
# Date
|
||||
x <- Sys.Date()
|
||||
expect_silent(sliderInput('s', 's', x-1, x+1, x))
|
||||
expect_silent(sliderInput('s', 's', x-1, x+1, x-1))
|
||||
expect_silent(sliderInput('s', 's', x-1, x+1, c(x-1, x+1)))
|
||||
|
||||
expect_warning(sliderInput('s', 's', x-1, x+1, x+2))
|
||||
expect_warning(sliderInput('s', 's', x-1, x+1, x-2))
|
||||
expect_warning(sliderInput('s', 's', x-1, x+1, c(x-2, x)))
|
||||
expect_warning(sliderInput('s', 's', x-1, x+1, c(x, x+2)))
|
||||
|
||||
expect_error(sliderInput('s', 's', x-1, x+1))
|
||||
expect_error(sliderInput('s', 's', x-1, x+1, NULL))
|
||||
expect_error(sliderInput('s', 's', x-1, NULL, x))
|
||||
expect_error(sliderInput('s', 's', NULL, x+1, x))
|
||||
expect_error(sliderInput('s', 's', NULL, NULL, x))
|
||||
expect_error(sliderInput('s', 's', x-1, x+1, as.Date(NA)))
|
||||
|
||||
# POSIXct
|
||||
x <- Sys.time()
|
||||
expect_silent(sliderInput('s', 's', x-1, x+1, x))
|
||||
expect_silent(sliderInput('s', 's', x-1, x+1, x-1))
|
||||
expect_silent(sliderInput('s', 's', x-1, x+1, c(x-1, x+1)))
|
||||
|
||||
expect_warning(sliderInput('s', 's', x-1, x+1, x+2))
|
||||
expect_warning(sliderInput('s', 's', x-1, x+1, x-2))
|
||||
expect_warning(sliderInput('s', 's', x-1, x+1, c(x-2, x)))
|
||||
expect_warning(sliderInput('s', 's', x-1, x+1, c(x, x+2)))
|
||||
|
||||
expect_error(sliderInput('s', 's', x-1, x+1))
|
||||
expect_error(sliderInput('s', 's', x-1, x+1, NULL))
|
||||
expect_error(sliderInput('s', 's', x-1, NULL, x))
|
||||
expect_error(sliderInput('s', 's', NULL, x+1, x))
|
||||
expect_error(sliderInput('s', 's', NULL, NULL, x))
|
||||
|
||||
# POSIXLt
|
||||
x <- as.POSIXlt(Sys.time())
|
||||
expect_silent(sliderInput('s', 's', x-1, x+1, x))
|
||||
expect_silent(sliderInput('s', 's', x-1, x+1, x-1))
|
||||
|
||||
expect_warning(sliderInput('s', 's', x-1, x+1, x+2))
|
||||
expect_warning(sliderInput('s', 's', x-1, x+1, x-2))
|
||||
|
||||
if (getRversion() >= "4.0") {
|
||||
expect_silent(sliderInput('s', 's', x-1, x+1, c(x-1, x+1)))
|
||||
expect_warning(sliderInput('s', 's', x-1, x+1, c(x-2, x)))
|
||||
expect_warning(sliderInput('s', 's', x-1, x+1, c(x, x+2)))
|
||||
} else {
|
||||
skip("c() doesn't work sensibly on POSIXlt objects with this version of R")
|
||||
}
|
||||
|
||||
|
||||
expect_error(sliderInput('s', 's', x-1, x+1))
|
||||
expect_error(sliderInput('s', 's', x-1, x+1, NULL))
|
||||
expect_error(sliderInput('s', 's', x-1, NULL, x))
|
||||
expect_error(sliderInput('s', 's', NULL, x+1, x))
|
||||
expect_error(sliderInput('s', 's', NULL, NULL, x))
|
||||
|
||||
|
||||
# Size
|
||||
x <- 10
|
||||
## length 0
|
||||
expect_error(sliderInput('s', 's', x-1, x+1, numeric(0)))
|
||||
expect_error(sliderInput('s', 's', x-1, numeric(0), x))
|
||||
expect_error(sliderInput('s', 's', numeric(0), x+1, x))
|
||||
## length 1
|
||||
expect_silent(sliderInput('s', 's', x-1, x+1, x))
|
||||
## length 2
|
||||
expect_silent(sliderInput('s', 's', x-1, x+1, c(x, x)))
|
||||
## length 3+
|
||||
expect_error(sliderInput('s', 's', x-1, x+1, c(x, x, x)))
|
||||
expect_error(sliderInput('s', 's', x-1, c(x, x, x), x))
|
||||
expect_error(sliderInput('s', 's', c(x, x, x), x+1, x))
|
||||
expect_error(sliderInput("x", "x", n - 1, n + 1, n), NA)
|
||||
expect_error(sliderInput("x", "x", d - 1, d + 1, d), NA)
|
||||
expect_error(sliderInput("x", "x", dt - 1, dt + 1, dt), NA)
|
||||
})
|
||||
|
||||
test_that("sliderInput gives informative errors for bad inputs", {
|
||||
expect_snapshot(error = TRUE, {
|
||||
sliderInput("x", "x")
|
||||
sliderInput("x", "x", min = NULL, max = 3, value = 2)
|
||||
sliderInput("x", "x", min = 1, max = NULL, value = 2)
|
||||
sliderInput("x", "x", min = 1, max = 3, value = NULL)
|
||||
sliderInput("x", "x", min = Sys.Date(), max = Sys.Date(), value = 1)
|
||||
sliderInput("x", "x", min = 1, max = 3, value = 0)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,87 +1,4 @@
|
||||
# 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")))
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
library(sass)
|
||||
home <- rprojroot::find_package_root_file("inst/www/shared/navtree")
|
||||
# TODO: write a unit test
|
||||
withr::with_dir(
|
||||
home, {
|
||||
sass_partial(
|
||||
sass_file("navtree.scss"),
|
||||
bslib::bs_theme(),
|
||||
output = "navtree.css",
|
||||
cache = FALSE,
|
||||
write_attachments = FALSE
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user