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 a11y attr option for hidden/visible slides #651

Open
wants to merge 1 commit 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
28 changes: 27 additions & 1 deletion src/components/build.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { siblings } from '../utils/dom'
import { siblings, getFocusableElements } from '../utils/dom'

export default function (Glide, Components, Events) {
const Build = {
Expand Down Expand Up @@ -44,6 +44,28 @@ export default function (Glide, Components, Events) {
}
},

/**
* Sets visible classes and hidden accessibility attributes on slides
*
* @return {Void}
*/
setVisibleSlideAttributes () {
Components.Html.slides.forEach((slide, index) => {
// if slide is in visible range
if (index >= Glide.index && index < (Glide.index + Glide.settings.perView)) {
// remove aria hidden and negative tabindex on focusable children
getFocusableElements(slide, true).forEach((focusableElement) => focusableElement.removeAttribute('tabindex'))
slide.removeAttribute('aria-hidden')
slide.classList.add(Glide.settings.classes.slide.visible)
} else {
// apply tabindex = -1 to all focusable elements within the slide
getFocusableElements(slide).forEach((focusableElement) => focusableElement.setAttribute('tabindex', '-1'))
slide.setAttribute('aria-hidden', 'true')
slide.classList.remove(Glide.settings.classes.slide.visible)
}
})
},

/**
* Removes HTML classes applied at building.
*
Expand Down Expand Up @@ -84,6 +106,10 @@ export default function (Glide, Components, Events) {
*/
Events.on('move.after', () => {
Build.activeClass()

if (Glide.settings.setVisibleAttributes) {
Build.setVisibleSlideAttributes()
}
})

return Build
Expand Down
10 changes: 9 additions & 1 deletion src/defaults.js
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,13 @@ export default {
*/
direction: 'ltr',

/**
* Sets class and accessibility attributes on hidden/visible slides
*
* @type {Boolean}
*/
setVisibleAttributes: true,

/**
* The distance value of the next and previous viewports which
* have to peek in the current view. Accepts number and
Expand Down Expand Up @@ -216,7 +223,8 @@ export default {
},
slide: {
clone: 'glide__slide--clone',
active: 'glide__slide--active'
active: 'glide__slide--active',
visible: 'glide__slide--visible'
},
arrow: {
disabled: 'glide__arrow--disabled'
Expand Down
33 changes: 33 additions & 0 deletions src/utils/dom.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,3 +45,36 @@ export function exist (node) {
export function toArray (nodeList) {
return Array.prototype.slice.call(nodeList)
}

/**
* Gathers a list of focusable elements within the passed in element (including the element itself).
* Thank you to https://zellwk.com/blog/keyboard-focusable-elements/ for the inspiration
* @param {Element} element
* @param {Boolean} includeNegativeTabIndex - if true, includes elements with negative tabindex
*
* @returns {Array}
*/
export function getFocusableElements (element, includeNegativeTabIndex = false) {
if (!element) {
console.error('getFocusableElements: element does not exist')
}
const focusableElementsSelector = 'a[href], button, input, textarea, select, details, [tabindex], [contenteditable]'
const focusableElements = []

// if the container element itself is focusable, add it to the list first
if (element.matches(focusableElementsSelector)) {
focusableElements.push(element)
}
focusableElements.push(...element.querySelectorAll(focusableElementsSelector))

// filter by relevant tabindex, styling, and disabled attributes
return focusableElements.filter((element) => {
if (!includeNegativeTabIndex && element.hasAttribute('tabindex') && (element.getAttribute('tabindex') < 0)) {
return false
}
if (element.hasAttribute('disabled') || element.hasAttribute('hidden')) {
return false
}
return true
})
}