Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
f4d8551
Add initial tabindex handling for tabs
patrickhlauke Jun 17, 2019
7c90656
WIP add initial keyboard handling and initialisation
patrickhlauke Jun 17, 2019
33df950
Remove dropdown-in-tabs related code
patrickhlauke Jun 18, 2019
78737b2
Add explicit note about aria-orientation, add all missing role="prese…
patrickhlauke Jun 18, 2019
578444b
Merge branch 'master' into patrickhlauke-tabs-kbd-automatic-activation
patrickhlauke Jun 18, 2019
53e954c
Initial code tweaks based on comments
patrickhlauke Jun 19, 2019
d722edc
Remove dropdown-in-tabs related unit tests
patrickhlauke Jun 19, 2019
701977b
Make note about dropdowns in tabs an actual callout
patrickhlauke Jun 19, 2019
cbac96d
Replace for loop with forEach
patrickhlauke Jun 19, 2019
95ebced
Tweak
patrickhlauke Jun 19, 2019
d91293d
Merge branch 'master' into patrickhlauke-tabs-kbd-automatic-activation
patrickhlauke Jun 19, 2019
8de6558
Add missing aria-orientation to list group tablists
patrickhlauke Jun 19, 2019
ef0c5c0
Merge branch 'master' into patrickhlauke-tabs-kbd-automatic-activation
patrickhlauke Jun 24, 2019
3b64256
Merge branch 'master' into patrickhlauke-tabs-kbd-automatic-activation
patrickhlauke Jun 25, 2019
8be4c13
Merge branch 'master' into patrickhlauke-tabs-kbd-automatic-activation
patrickhlauke Jun 25, 2019
007c758
Merge branch 'master' into patrickhlauke-tabs-kbd-automatic-activation
patrickhlauke Jul 6, 2019
2e5a010
Add initial tabindex handling for tabs
patrickhlauke Jun 17, 2019
da9f93d
WIP add initial keyboard handling and initialisation
patrickhlauke Jun 17, 2019
a1e7738
Remove dropdown-in-tabs related code
patrickhlauke Jun 18, 2019
31dd87c
Add explicit note about aria-orientation, add all missing role="prese…
patrickhlauke Jun 18, 2019
a8cf124
Initial code tweaks based on comments
patrickhlauke Jun 19, 2019
35179a4
Remove dropdown-in-tabs related unit tests
patrickhlauke Jun 19, 2019
66d0d7b
Make note about dropdowns in tabs an actual callout
patrickhlauke Jun 19, 2019
7c79b8a
Replace for loop with forEach
patrickhlauke Jun 19, 2019
3c81700
Tweak
patrickhlauke Jun 19, 2019
d5616fe
Add missing aria-orientation to list group tablists
patrickhlauke Jun 19, 2019
ea0e99d
add transitioning state to avoid displaying two tabs in the same time
Johann-S Jul 8, 2019
10b293f
Merge branch 'master' into patrickhlauke-tabs-kbd-automatic-activation
patrickhlauke Jul 8, 2019
eace005
Merge branch 'master' into patrickhlauke-tabs-kbd-automatic-activation
patrickhlauke Jul 9, 2019
65b32a0
Merge branch 'patrickhlauke-tabs-kbd-automatic-activation' of https:/…
patrickhlauke Jul 13, 2019
eb299c9
Unit test for tablist initialization
patrickhlauke Jul 13, 2019
1687b77
Fix one of the existing unit tests
patrickhlauke Jul 13, 2019
fabe1f0
Remove redundant sanity checks
patrickhlauke Jul 13, 2019
430a120
Add extensive keyboard handling unit tests
patrickhlauke Jul 13, 2019
a38a3bd
Fix visual unit test
patrickhlauke Jul 13, 2019
76f87e2
Fixes #28994
mdo Jul 9, 2019
349eeeb
Fix typo. (#29008)
XhmikosR Jul 10, 2019
e81f20c
separate file for our polyfills to have lighter plugins
Johann-S Jul 9, 2019
bfe5438
Remove attribute selectors (#28988)
MartijnCuppens Jul 12, 2019
cc5353d
Drop support for .form-control-plaintext inside .input-group (#28972)
ysds Jul 12, 2019
79d5387
v5: Forms update (#28450)
mdo Jul 12, 2019
a13fba8
dist v5
mdo Jul 12, 2019
4fdae4b
Update collapse.md (#29025)
Brianmanden Jul 13, 2019
c11118b
Merge branch 'master' into patrickhlauke-tabs-kbd-automatic-activation
patrickhlauke Jul 13, 2019
897f4db
Update tab.js
XhmikosR Nov 25, 2020
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
104 changes: 82 additions & 22 deletions js/src/tab.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,33 +29,39 @@ const VERSION = '4.3.1'
const DATA_KEY = 'bs.tab'
const EVENT_KEY = `.${DATA_KEY}`
const DATA_API_KEY = '.data-api'
const ARROW_LEFT_KEYCODE = 37 // KeyboardEvent.which value for left arrow key
const ARROW_UP_KEYCODE = 38 // KeyboardEvent.which value for up arrow key
const ARROW_RIGHT_KEYCODE = 39 // KeyboardEvent.which value for right arrow key
const ARROW_DOWN_KEYCODE = 40 // KeyboardEvent.which value for down arrow key

const Event = {
HIDE: `hide${EVENT_KEY}`,
HIDDEN: `hidden${EVENT_KEY}`,
SHOW: `show${EVENT_KEY}`,
SHOWN: `shown${EVENT_KEY}`,
CLICK_DATA_API: `click${EVENT_KEY}${DATA_API_KEY}`
CLICK_DATA_API: `click${EVENT_KEY}${DATA_API_KEY}`,
KEYDOWN_DATA_API: `keydown${EVENT_KEY}${DATA_API_KEY}`,
LOAD_DATA_API: `load${EVENT_KEY}${DATA_API_KEY}`
}

const ClassName = {
DROPDOWN_MENU: 'dropdown-menu',
ACTIVE: 'active',
DISABLED: 'disabled',
FADE: 'fade',
SHOW: 'show'
}

const Selector = {
DROPDOWN: '.dropdown',
NAV_LIST_GROUP: '.nav, .list-group',
ACTIVE: '.active',
ACTIVE_UL: ':scope > li > .active',
DATA_TOGGLE: '[data-toggle="tab"], [data-toggle="pill"], [data-toggle="list"]',
DROPDOWN_TOGGLE: '.dropdown-toggle',
DROPDOWN_ACTIVE_CHILD: ':scope > .dropdown-menu .active'
TABLIST: '[role="tablist"]'
}

const ORIENTATION_VERTICAL = 'vertical'
const ORIENTATION_HORIZONTAL = 'horizontal'

/**
* ------------------------------------------------------------------------
* Class Definition
Expand Down Expand Up @@ -97,6 +103,7 @@ class Tab {
}

let hideEvent = null
this.isTransitioning = true

if (previous) {
hideEvent = EventHandler.trigger(previous, Event.HIDE, {
Expand Down Expand Up @@ -129,6 +136,7 @@ class Tab {
EventHandler.trigger(this._element, Event.SHOWN, {
relatedTarget: previous
})
this.isTransitioning = false
}

if (target) {
Expand Down Expand Up @@ -175,20 +183,16 @@ class Tab {
if (active) {
active.classList.remove(ClassName.ACTIVE)

const dropdownChild = SelectorEngine.findOne(Selector.DROPDOWN_ACTIVE_CHILD, active.parentNode)

if (dropdownChild) {
dropdownChild.classList.remove(ClassName.ACTIVE)
}

if (active.getAttribute('role') === 'tab') {
active.setAttribute('aria-selected', false)
active.setAttribute('tabindex', '-1')
}
}

element.classList.add(ClassName.ACTIVE)
if (element.getAttribute('role') === 'tab') {
element.setAttribute('aria-selected', true)
element.setAttribute('tabindex', '0')
}

reflow(element)
Expand All @@ -197,17 +201,6 @@ class Tab {
element.classList.add(ClassName.SHOW)
}

if (element.parentNode && element.parentNode.classList.contains(ClassName.DROPDOWN_MENU)) {
const dropdownElement = SelectorEngine.closest(element, Selector.DROPDOWN)

if (dropdownElement) {
makeArray(SelectorEngine.find(Selector.DROPDOWN_TOGGLE))
.forEach(dropdown => dropdown.classList.add(ClassName.ACTIVE))
}

element.setAttribute('aria-expanded', true)
}

if (callback) {
callback()
}
Expand All @@ -229,6 +222,48 @@ class Tab {
})
}

static _dataApiKeydownHandler(event) {
const tablist = SelectorEngine.closest(event.target, Selector.TABLIST)
let tabListOrientation = tablist.getAttribute('aria-orientation')
if (tabListOrientation !== ORIENTATION_VERTICAL) {
tabListOrientation = ORIENTATION_HORIZONTAL
}

if ((tabListOrientation === ORIENTATION_HORIZONTAL && event.which !== ARROW_LEFT_KEYCODE && event.which !== ARROW_RIGHT_KEYCODE) ||
(tabListOrientation === ORIENTATION_VERTICAL && event.which !== ARROW_UP_KEYCODE && event.which !== ARROW_DOWN_KEYCODE)) {
return
}

event.preventDefault()
event.stopPropagation()

if (this.disabled || this.classList.contains(ClassName.DISABLED)) {
return
}

const tabs = makeArray(SelectorEngine.find(Selector.DATA_TOGGLE, tablist))

let index = tabs.indexOf(event.target)
const tabInstance = Data.getData(tabs[index], DATA_KEY) || new Tab(tabs[index])

if (tabInstance.isTransitioning) {
return
}

// Left / Up
if ((event.which === ARROW_LEFT_KEYCODE || event.which === ARROW_UP_KEYCODE) && index > 0) {
index--
}

// Right / Down
if ((event.which === ARROW_RIGHT_KEYCODE || event.which === ARROW_DOWN_KEYCODE) && index < tabs.length - 1) {
index++
}

tabs[index].focus()
tabs[index].click()
}

static _getInstance(element) {
return Data.getData(element, DATA_KEY)
}
Expand All @@ -240,6 +275,31 @@ class Tab {
* ------------------------------------------------------------------------
*/

EventHandler.on(window, Event.LOAD_DATA_API, () => {
makeArray(SelectorEngine.find(Selector.TABLIST))
.forEach(tablist => {
const tabs = makeArray(SelectorEngine.find(Selector.DATA_TOGGLE, tablist))
let selectedTabFound = false

// iterate over each tab in the tablist, make sure they have correct tabindex/aria-selected
tabs.forEach(tab => {
if (tab.getAttribute('aria-selected') === 'true' && !selectedTabFound) {
tab.setAttribute('tabindex', '0')
selectedTabFound = true
} else {
tab.setAttribute('tabindex', '-1')
tab.setAttribute('aria-selected', 'false')
}
})

// if none of the tabs were explicitly marked as selected, pick first one
if (!selectedTabFound) {
tabs[0].setAttribute('tabindex', '0')
tabs[0].setAttribute('aria-selected', 'true')
}
})
})
EventHandler.on(document, Event.KEYDOWN_DATA_API, Selector.DATA_TOGGLE, Tab._dataApiKeydownHandler)
EventHandler.on(document, Event.CLICK_DATA_API, Selector.DATA_TOGGLE, function (event) {
event.preventDefault()

Expand Down
Loading