Skip to content

Instantly share code, notes, and snippets.

@giordanocardillo
Last active August 12, 2020 09:47
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save giordanocardillo/b30b98dfbc085bb8cbcad69554c785a9 to your computer and use it in GitHub Desktop.
Save giordanocardillo/b30b98dfbc085bb8cbcad69554c785a9 to your computer and use it in GitHub Desktop.
Focus Trapper - How to trap focus inside an element

FocusTrapper

A simple method to "trap" tabbing inside an element

Usage

const trapper = new FocusTrapper(element)
trapper.trap() // To trap focus
trapper.untrap() // To release
class FocusTrapper {
_element
_focusables
_lastFocused
_ignoreFocusing = false
_boundaries = []
constructor(element) {
try {
if (!document.contains(element)) {
throw new Error()
}
} catch (e) {
throw new Error('Not a valid element!')
}
this._element = element
}
_getBoundaryDiv() {
const div = document.createElement('div')
div.setAttribute('tabindex', '0')
div.setAttribute('aria-hidden', 'true')
return div
}
_addBoundaries() {
this._boundaries = []
this._boundaries.push(this._getBoundaryDiv())
this._boundaries.push(this._getBoundaryDiv())
this._element.parentNode.insertBefore(this._boundaries[0], this._element)
this._element.parentNode.insertBefore(this._boundaries[1], this._element.nextSibling)
}
_removeBoundaries() {
this._boundaries.forEach(e => e.remove())
}
_findFocusables() {
if (!this._focusables) {
const selectors = [
'a[href]:not([href="#"])',
'button',
'textarea',
'input',
'*[tabindex]:not([tabindex="-1"])',
].map(e => e += ':not([aria-hidden="true"]):not([disabled])')
this._focusables = this._element.querySelectorAll(selectors.join(', '))
}
return this._focusables
}
_setFocus(position) {
const focusables = this._findFocusables()
const index = position === 'first' ? 0 : focusables.length - 1
this._ignoreFocusing = true
focusables[index].focus()
this._ignoreFocusing = false
}
_focusTrap(event) {
event.stopImmediatePropagation()
if (this._ignoreFocusing) return
if (this._element.contains(event.target)) {
this._lastFocused = event.target
return
}
this._setFocus('first')
if (this._lastFocused === document.activeElement) {
this._setFocus('last')
}
this._lastFocused = document.activeElement
}
trap() {
this._addBoundaries()
document.addEventListener('focus', this._focusTrap.bind(this), true)
}
untrap() {
this._removeBoundaries()
this._focusables = null
document.removeEventListener('focus', this._focusTrap.bind(this), true)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment