2 * --------------------------------------------------------------------------
3 * Bootstrap (v5.1.3): tab.js
4 * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
5 * --------------------------------------------------------------------------
10 getElementFromSelector
,
14 import EventHandler
from './dom/event-handler'
15 import SelectorEngine
from './dom/selector-engine'
16 import BaseComponent
from './base-component'
19 * ------------------------------------------------------------------------
21 * ------------------------------------------------------------------------
25 const DATA_KEY
= 'bs.tab'
26 const EVENT_KEY
= `.${DATA_KEY}`
27 const DATA_API_KEY
= '.data-api'
29 const EVENT_HIDE
= `hide${EVENT_KEY}`
30 const EVENT_HIDDEN
= `hidden${EVENT_KEY}`
31 const EVENT_SHOW
= `show${EVENT_KEY}`
32 const EVENT_SHOWN
= `shown${EVENT_KEY}`
33 const EVENT_CLICK_DATA_API
= `click${EVENT_KEY}${DATA_API_KEY}`
35 const CLASS_NAME_DROPDOWN_MENU
= 'dropdown-menu'
36 const CLASS_NAME_ACTIVE
= 'active'
37 const CLASS_NAME_FADE
= 'fade'
38 const CLASS_NAME_SHOW
= 'show'
40 const SELECTOR_DROPDOWN
= '.dropdown'
41 const SELECTOR_NAV_LIST_GROUP
= '.nav, .list-group'
42 const SELECTOR_ACTIVE
= '.active'
43 const SELECTOR_ACTIVE_UL
= ':scope > li > .active'
44 const SELECTOR_DATA_TOGGLE
= '[data-bs-toggle="tab"], [data-bs-toggle="pill"], [data-bs-toggle="list"]'
45 const SELECTOR_DROPDOWN_TOGGLE
= '.dropdown-toggle'
46 const SELECTOR_DROPDOWN_ACTIVE_CHILD
= ':scope > .dropdown-menu .active'
49 * ------------------------------------------------------------------------
51 * ------------------------------------------------------------------------
54 class Tab
extends BaseComponent
{
64 if ((this._element
.parentNode
&&
65 this._element
.parentNode
.nodeType
=== Node
.ELEMENT_NODE
&&
66 this._element
.classList
.contains(CLASS_NAME_ACTIVE
))) {
71 const target
= getElementFromSelector(this._element
)
72 const listElement
= this._element
.closest(SELECTOR_NAV_LIST_GROUP
)
75 const itemSelector
= listElement
.nodeName
=== 'UL' || listElement
.nodeName
=== 'OL' ? SELECTOR_ACTIVE_UL
: SELECTOR_ACTIVE
76 previous
= SelectorEngine
.find(itemSelector
, listElement
)
77 previous
= previous
[previous
.length
- 1]
80 const hideEvent
= previous
?
81 EventHandler
.trigger(previous
, EVENT_HIDE
, {
82 relatedTarget
: this._element
86 const showEvent
= EventHandler
.trigger(this._element
, EVENT_SHOW
, {
87 relatedTarget
: previous
90 if (showEvent
.defaultPrevented
|| (hideEvent
!== null && hideEvent
.defaultPrevented
)) {
94 this._activate(this._element
, listElement
)
96 const complete
= () => {
97 EventHandler
.trigger(previous
, EVENT_HIDDEN
, {
98 relatedTarget
: this._element
100 EventHandler
.trigger(this._element
, EVENT_SHOWN
, {
101 relatedTarget
: previous
106 this._activate(target
, target
.parentNode
, complete
)
114 _activate(element
, container
, callback
) {
115 const activeElements
= container
&& (container
.nodeName
=== 'UL' || container
.nodeName
=== 'OL') ?
116 SelectorEngine
.find(SELECTOR_ACTIVE_UL
, container
) :
117 SelectorEngine
.children(container
, SELECTOR_ACTIVE
)
119 const active
= activeElements
[0]
120 const isTransitioning
= callback
&& (active
&& active
.classList
.contains(CLASS_NAME_FADE
))
122 const complete
= () => this._transitionComplete(element
, active
, callback
)
124 if (active
&& isTransitioning
) {
125 active
.classList
.remove(CLASS_NAME_SHOW
)
126 this._queueCallback(complete
, element
, true)
132 _transitionComplete(element
, active
, callback
) {
134 active
.classList
.remove(CLASS_NAME_ACTIVE
)
136 const dropdownChild
= SelectorEngine
.findOne(SELECTOR_DROPDOWN_ACTIVE_CHILD
, active
.parentNode
)
139 dropdownChild
.classList
.remove(CLASS_NAME_ACTIVE
)
142 if (active
.getAttribute('role') === 'tab') {
143 active
.setAttribute('aria-selected', false)
147 element
.classList
.add(CLASS_NAME_ACTIVE
)
148 if (element
.getAttribute('role') === 'tab') {
149 element
.setAttribute('aria-selected', true)
154 if (element
.classList
.contains(CLASS_NAME_FADE
)) {
155 element
.classList
.add(CLASS_NAME_SHOW
)
158 let parent
= element
.parentNode
159 if (parent
&& parent
.nodeName
=== 'LI') {
160 parent
= parent
.parentNode
163 if (parent
&& parent
.classList
.contains(CLASS_NAME_DROPDOWN_MENU
)) {
164 const dropdownElement
= element
.closest(SELECTOR_DROPDOWN
)
166 if (dropdownElement
) {
167 for (const dropdown
of SelectorEngine
.find(SELECTOR_DROPDOWN_TOGGLE
, dropdownElement
)) {
168 dropdown
.classList
.add(CLASS_NAME_ACTIVE
)
172 element
.setAttribute('aria-expanded', true)
182 static jQueryInterface(config
) {
183 return this.each(function () {
184 const data
= Tab
.getOrCreateInstance(this)
186 if (typeof config
=== 'string') {
187 if (typeof data
[config
] === 'undefined') {
188 throw new TypeError(`No method named "${config}"`)
198 * ------------------------------------------------------------------------
199 * Data Api implementation
200 * ------------------------------------------------------------------------
203 EventHandler
.on(document
, EVENT_CLICK_DATA_API
, SELECTOR_DATA_TOGGLE
, function (event
) {
204 if (['A', 'AREA'].includes(this.tagName
)) {
205 event
.preventDefault()
208 if (isDisabled(this)) {
212 const data
= Tab
.getOrCreateInstance(this)
217 * ------------------------------------------------------------------------
219 * ------------------------------------------------------------------------
220 * add .Tab to jQuery only if jQuery is present
223 defineJQueryPlugin(Tab
)