Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add "aria-labelledby" attribute #469

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion NEWS.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,19 @@
htmlwidgets 1.6.2.9000
------------------------------------------------------

* `widget_html.*` methods can now support a `use_aria`
argument to support accessibility. By default, this will be
`TRUE` in `knitr` version 1.42.12 if a `fig.alt` chunk option is
supplied. The default can be overridden by setting the
`"htmlwidgets.USE_ARIA"` option.
* Added optional `aria-labelledby` attribute to the
`widget_html.default()` output to work with accessibility improvements in `knitr`. `knitr` will insert the specified
`fig.alt` or `fig.cap` text if `use_aria` is `TRUE`.

htmlwidgets 1.6.2
------------------------------------------------------

* Closed #452: `as.tag.htmlwidget()` now includes `...` in it's function signature (for compatibility with the `htmltools::as.tags` generic).
* Closed #452: `as.tag.htmlwidget()` now includes `...` in its function signature (for compatibility with the `htmltools::as.tags` generic).

htmlwidgets 1.6.1
------------------------------------------------------
Expand Down
24 changes: 19 additions & 5 deletions R/htmlwidgets.R
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,12 @@ addHook <- function(x, hookName, jsCode, data = NULL) {
x
}

do_use_aria <- function(knitrOptions) {
getOption("htmlwidgets.USE_ARIA") %||%
("fig.alt" %in% names(knitrOptions) &&
isNamespaceLoaded("knitr") &&
packageVersion("knitr") >= "1.42.12")
}

toHTML <- function(x, standalone = FALSE, knitrOptions = NULL) {

Expand All @@ -192,7 +198,8 @@ toHTML <- function(x, standalone = FALSE, knitrOptions = NULL) {
if (sizeInfo$fill) "html-fill-item-overflow-hidden"
),
width = sizeInfo$width,
height = sizeInfo$height
height = sizeInfo$height,
use_aria = do_use_aria(knitrOptions)
)

html <- bindFillRole(html, item = sizeInfo$fill)
Expand Down Expand Up @@ -257,18 +264,22 @@ lookup_widget_html_method <- function(name, package) {
list(fn = widget_html.default, name = "widget_html.default", legacy = FALSE)
}

widget_html <- function(name, package, id, style, class, inline = FALSE, ...) {
widget_html <- function(name, package, id, style, class, inline = FALSE, use_aria = FALSE, ...) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it make sense for use_aria to follow the htmlwidgets.use_aria option?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think so. It might make sense to use some test like the do_use_aria test, but at this point we can't see the knitr options.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, the knitr options aren't strictly necessary because the global R option is checked before the knitr options. So it'd be reasonable to do either

Suggested change
widget_html <- function(name, package, id, style, class, inline = FALSE, use_aria = FALSE, ...) {
widget_html <- function(name, package, id, style, class, inline = FALSE, ..., use_aria = getOption("htmlwidgets.USE_ARIA", FALSE)) {

or to default to use_aria = NULL and then internally call do_use_aria()

  args$use_aria <- use_aria %||% do_use_aria()

In either case, I'd prefer to add use_aria after the ..., since we're extending the original function signature.

Suggested change
widget_html <- function(name, package, id, style, class, inline = FALSE, use_aria = FALSE, ...) {
widget_html <- function(name, package, id, style, class, inline = FALSE, ..., use_aria = FALSE) {


fn_info <- lookup_widget_html_method(name, package)

fn <- fn_info[["fn"]]
# id, style, and class have been required args for years, but inline is fairly new
# and undocumented, so unsuprisingly there are widgets out there are don't have an
# and undocumented, so unsurprisingly there are widgets out there that don't have an
# inline arg https://github.com/renkun-ken/formattable/blob/484777/R/render.R#L79-L88
args <- list(id = id, style = style, class = class, ...)
if ("inline" %in% names(formals(fn))) {
args$inline <- inline
}
# use_aria was added to support accessibility in knitr
if ("use_aria" %in% names(formals(fn))) {
args$use_aria <- use_aria
}
fn_res <- do.call(fn, args)
if (isTRUE(fn_info[["legacy"]])) {
# For the PACKAGE:::NAME_html form (only), we worry about false positives;
Expand All @@ -284,11 +295,14 @@ widget_html <- function(name, package, id, style, class, inline = FALSE, ...) {
fn_res
}

widget_html.default <- function (name, package, id, style, class, inline = FALSE, ...) {
widget_html.default <- function (name, package, id, style, class, inline = FALSE, use_aria = FALSE, ...) {
if (inline) {
tags$span(id = id, style = style, class = class)
} else {
} else if (!use_aria) {
tags$div(id = id, style = style, class = class)
} else {
tags$div(id = id, style = style, class = class,
"aria-labelledby" = paste0(id, "-aria"))
}
}

Expand Down
7 changes: 7 additions & 0 deletions tests/testthat/test-htmlwidgets.R
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,10 @@ test_that("Legacy methods work with tagList() and HTML()", {
widget_html("widgetF", "htmlwidgets", id = "id", style = NULL, class = NULL)
}, NA)
})

test_that("The widget_html.default respects use_aria option", {
res <- widget_html("does_not_exist", "htmlwidgets", id = "id", style = NULL, class = NULL, use_aria = TRUE)
expect_identical(res, tags$div(id = "id", "aria-labelledby" = "id-aria"))
res <- widget_html("does_not_exist", "htmlwidgets", id = "id", style = NULL, class = NULL, use_aria = FALSE)
expect_identical(res, tags$div(id = "id"))
})
28 changes: 28 additions & 0 deletions vignettes/develop_advanced.Rmd
Original file line number Diff line number Diff line change
Expand Up @@ -187,3 +187,31 @@ Note that this function is looked up within the package implementing the widget
(**htmlwidgets** 1.5.2 and earlier used a convention of <code><em>widgetname</em>_html</code>. This is still supported for now, but the new <code>widget_html.<em>widgetname</em></code> convention is recommended going forward, as it seems less likely to lead to false positives.)

Most widgets won't need a custom HTML function but if you need to generate custom HTML for your widget (e.g. you need an `<input>` or a `<span>` rather than a `<div>`) then you should use the **htmltools** package (as demonstrated by the code above).

### Accessibility

By default, widgets will usually be invisible to screen readers
and other accessibility aids, but there is now some support
in `htmlwidgets` when used with `knitr` version 1.42.12 or higher.
That version added support for including alternate text in a
widget: if the
HTML housing for the widget contains an `aria-labelledby`
attribute, `knitr` will construct a label based on the
`fig.alt` or `fig.cap` text provided in the chunk options.

To support this, the `widget_html.default` function has a
new argument `use_aria`. If `TRUE`, it will insert an
`aria-labelledby` attribute containing `<id>-aria`, where
`<id>` is the id of the widget. If custom widgets have that
argument, they'll also receive the value.

The value will be automatically set to `TRUE` if the installed version
of `knitr` is sufficient and the `htmlwidgets::toHTML` function detects
a `fig.alt` setting in `knitrOptions`. This setting can
be overridden by setting `options("htmlwidgets.USE_ARIA")` to
`TRUE` or `FALSE`.

Widget developers are advised to test this feature with one or
more screen readers, as they are not entirely consistent in
supporting it. For example, the `rgl` package added a `role="img"`
attribute so that one screen reader would detect its figures.