Skip to content

Commit

Permalink
an option for shift-tab keyboard trap in modals
Browse files Browse the repository at this point in the history
  • Loading branch information
RyanBerliner committed Feb 27, 2021
1 parent 5560c86 commit 1747b86
Show file tree
Hide file tree
Showing 2 changed files with 37 additions and 1 deletion.
6 changes: 6 additions & 0 deletions js/src/dom/selector-engine.js
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,12 @@ const SelectorEngine = {
}

return []
},

focusableChildren(element) {
// POC, this is a small subset of focusable elements, sometimes being wrong.
// There are edge cases it misses (visibility, disabled, etc)
return this.find('a, button, input, textarea, [tabindex]:not([tabindex="-1"])', element)
}
}

Expand Down
32 changes: 31 additions & 1 deletion js/src/modal.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ const DATA_KEY = 'bs.modal'
const EVENT_KEY = `.${DATA_KEY}`
const DATA_API_KEY = '.data-api'
const ESCAPE_KEY = 'Escape'
const TAB_KEY = 'Tab'

const Default = {
backdrop: true,
Expand All @@ -53,6 +54,7 @@ const EVENT_SHOWN = `shown${EVENT_KEY}`
const EVENT_FOCUSIN = `focusin${EVENT_KEY}`
const EVENT_RESIZE = `resize${EVENT_KEY}`
const EVENT_CLICK_DISMISS = `click.dismiss${EVENT_KEY}`
const EVENT_KEYDOWN_TAB = `keydown.tab${EVENT_KEY}`
const EVENT_KEYDOWN_DISMISS = `keydown.dismiss${EVENT_KEY}`
const EVENT_MOUSEUP_DISMISS = `mouseup.dismiss${EVENT_KEY}`
const EVENT_MOUSEDOWN_DISMISS = `mousedown.dismiss${EVENT_KEY}`
Expand All @@ -72,6 +74,9 @@ const SELECTOR_DATA_DISMISS = '[data-bs-dismiss="modal"]'
const SELECTOR_FIXED_CONTENT = '.fixed-top, .fixed-bottom, .is-fixed, .sticky-top'
const SELECTOR_STICKY_CONTENT = '.sticky-top'

const TAB_NAV_FORWARD = 'forward'
const TAB_NAV_BACKWARD = 'backward'

/**
* ------------------------------------------------------------------------
* Class Definition
Expand All @@ -90,6 +95,7 @@ class Modal extends BaseComponent {
this._ignoreBackdropClick = false
this._isTransitioning = false
this._scrollbarWidth = 0
this._lastTabNavDirection = null
}

// Getters
Expand Down Expand Up @@ -132,6 +138,7 @@ class Modal extends BaseComponent {

this._adjustDialog()

this._setTabNavEvent()
this._setEscapeEvent()
this._setResizeEvent()

Expand Down Expand Up @@ -170,6 +177,7 @@ class Modal extends BaseComponent {
this._isTransitioning = true
}

this._setTabNavEvent()
this._setEscapeEvent()
this._setResizeEvent()

Expand Down Expand Up @@ -284,11 +292,33 @@ class Modal extends BaseComponent {
if (document !== event.target &&
this._element !== event.target &&
!this._element.contains(event.target)) {
this._element.focus()
const elements = SelectorEngine.focusableChildren(this._element)
if (elements.length === 0) {
this._element.focus()
} else if (this._lastTabNavDirection === TAB_NAV_BACKWARD) {
elements[elements.length - 1].focus()
} else {
// Acts as a catch for forward navigation, or if no keyboard navigation has occured
elements[0].focus()
}
}
})
}

_setTabNavEvent() {
if (this._isShown) {
EventHandler.on(document, EVENT_KEYDOWN_TAB, event => {
if (event.key !== TAB_KEY) {
return
}

this._lastTabNavDirection = event.shiftKey ? TAB_NAV_BACKWARD : TAB_NAV_FORWARD
})
} else {
EventHandler.off(document, EVENT_KEYDOWN_TAB)
}
}

_setEscapeEvent() {
if (this._isShown) {
EventHandler.on(this._element, EVENT_KEYDOWN_DISMISS, event => {
Expand Down

0 comments on commit 1747b86

Please sign in to comment.