Compare commits

..

3 Commits

Author SHA1 Message Date
Hadley Wickham
03b03f1173 Update tests and language 2021-03-08 17:49:57 -06:00
Hadley Wickham
28dc3ecd5b Merge commit 'd582e53f73ff61953ee209c870eabb3bc7124288'
Conflicts:
	R/input-slider.R
2021-03-08 17:16:00 -06:00
Hadley Wickham
c3e6fdc550 Perform type check before value check
If you don't validate the input types before validating the values, you get uninformative errors from min/max. (Which comes up in Mastering Shiny)

I also move `getSliderType()` into input-slider.R since it's the only place that it's used, and mildly improved the error message.
2021-01-28 08:16:10 -06:00
18 changed files with 291 additions and 1039 deletions

View File

@@ -408,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)

View File

@@ -163,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)
}
@@ -220,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")
@@ -239,8 +230,6 @@ bootstrapDependency <- function(theme) {
)
}
bootstrapVersion <- "3.4.1"
#' @rdname bootstrapPage
#' @export
@@ -447,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")
@@ -459,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),
@@ -480,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)
),
@@ -489,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)
@@ -516,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
@@ -665,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.
@@ -707,10 +691,8 @@ tabPanelBody <- function(value, ..., icon = NULL) {
#' conjunction with [tabPanelBody()] and [updateTabsetPanel()] to control the
#' active tab via other input controls. (See example below)}
#' }
#' @param card whether to wrap the navigation controls and content into an 'output card'. This functionality currently requires a [bslib::bs_theme()] in the page layout with `version = 4` or higher.
#' @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()],
@@ -760,9 +742,6 @@ tabsetPanel <- function(...,
id = NULL,
selected = NULL,
type = c("tabs", "pills", "hidden"),
header = NULL,
footer = NULL,
card = FALSE,
position = deprecated()) {
if (lifecycle::is_present(position)) {
shinyDeprecated(
@@ -774,37 +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)
nav <- tabset$navList
if (card) {
nav <- tags$div(
class = "card-header",
tagFunction(function() {
if (getCurrentVersion() >= 4) {
return(NULL)
}
stop(
"`tabsetPanel(card = TRUE)` requires Bootstrap 4 or higher. ",
"Please supply `bslib::bs_theme()` to the UI's page layout function ",
"(e.g., `fluidPage(theme = bslib::bs_theme())`).",
call. = FALSE
)
}),
tagAppendAttributes(
nav, class = paste0("card-header-", type)
)
)
}
tabset <- buildTabset(tabs, paste0("nav nav-", type), NULL, id, selected)
tabs <- tags$div(class = "tabbable", class = if (card) "card", nav)
content <- dropNulls(list(header, tabset$content, footer))
if (card) {
tagAppendChild(tabs, tags$div(class = "card-body", !!!content))
} else {
tagAppendChildren(tabs, content)
}
# create the content
first <- tabset$navList
second <- tabset$content
# create the tab div
tags$div(class = "tabbable", first, second)
}
#' Create a navigation list panel
@@ -824,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
@@ -854,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
@@ -895,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 {
@@ -911,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))
}
@@ -946,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
@@ -970,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)
}
@@ -985,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
#'

View File

@@ -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

View File

@@ -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
#'

View File

@@ -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

View File

@@ -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() {

View File

@@ -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") {
@@ -3091,8 +3080,8 @@
}
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 + "-", "");
@@ -6095,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;
@@ -6104,7 +6093,7 @@
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");
@@ -7270,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);
},
@@ -7341,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)
@@ -7353,7 +7302,7 @@
}
var results = [];
while (true) {
var result = regExpExec2(rx, S);
var result = regExpExec(rx, S);
if (result === null)
break;
results.push(result);
@@ -7361,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;
@@ -7393,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();
@@ -7409,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 [];
@@ -7456,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);
},
@@ -7464,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;
@@ -7482,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)
@@ -7776,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;
@@ -7912,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;
@@ -7925,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;
@@ -8048,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

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -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{

View File

@@ -9,9 +9,6 @@ tabsetPanel(
id = NULL,
selected = NULL,
type = c("tabs", "pills", "hidden"),
header = NULL,
footer = NULL,
card = FALSE,
position = deprecated()
)
}
@@ -35,14 +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{card}{whether to wrap the navigation controls and content into an 'output card'. This functionality currently requires a \code{\link[bslib:bs_theme]{bslib::bs_theme()}} in the page layout with \code{version = 4} or higher.}
\item{position}{This argument is deprecated; it has been discontinued in
Bootstrap 3.}
}

View File

@@ -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);
}
@@ -1426,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)
@@ -5896,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);
@@ -5912,12 +5892,7 @@ 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) {

View File

@@ -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,
};

View 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]

View File

@@ -1,376 +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>
---
Code
bslib_tags(x)
Output
<div class="tabbable card">
<div class="card-header">
<ul class="nav nav-tabs card-header-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>
<div class="card-body">
<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>
</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>

View File

@@ -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)
})
})

View File

@@ -1,89 +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)
card <- tabset_panel(!!!panels, card = TRUE)
expect_snapshot_bslib(card)
})
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")))