tab.js 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264
  1. import $ from 'jquery'
  2. import Util from './util'
  3. /**
  4. * --------------------------------------------------------------------------
  5. * Bootstrap (v4.1.3): tab.js
  6. * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
  7. * --------------------------------------------------------------------------
  8. */
  9. const Tab = (($) => {
  10. /**
  11. * ------------------------------------------------------------------------
  12. * Constants
  13. * ------------------------------------------------------------------------
  14. */
  15. const NAME = 'tab'
  16. const VERSION = '4.1.3'
  17. const DATA_KEY = 'bs.tab'
  18. const EVENT_KEY = `.${DATA_KEY}`
  19. const DATA_API_KEY = '.data-api'
  20. const JQUERY_NO_CONFLICT = $.fn[NAME]
  21. const Event = {
  22. HIDE : `hide${EVENT_KEY}`,
  23. HIDDEN : `hidden${EVENT_KEY}`,
  24. SHOW : `show${EVENT_KEY}`,
  25. SHOWN : `shown${EVENT_KEY}`,
  26. CLICK_DATA_API : `click${EVENT_KEY}${DATA_API_KEY}`
  27. }
  28. const ClassName = {
  29. DROPDOWN_MENU : 'dropdown-menu',
  30. ACTIVE : 'active',
  31. DISABLED : 'disabled',
  32. FADE : 'fade',
  33. SHOW : 'show'
  34. }
  35. const Selector = {
  36. DROPDOWN : '.dropdown',
  37. NAV_LIST_GROUP : '.nav, .list-group',
  38. ACTIVE : '.active',
  39. ACTIVE_UL : '> li > .active',
  40. DATA_TOGGLE : '[data-toggle="tab"], [data-toggle="pill"], [data-toggle="list"]',
  41. DROPDOWN_TOGGLE : '.dropdown-toggle',
  42. DROPDOWN_ACTIVE_CHILD : '> .dropdown-menu .active'
  43. }
  44. /**
  45. * ------------------------------------------------------------------------
  46. * Class Definition
  47. * ------------------------------------------------------------------------
  48. */
  49. class Tab {
  50. constructor(element) {
  51. this._element = element
  52. }
  53. // Getters
  54. static get VERSION() {
  55. return VERSION
  56. }
  57. // Public
  58. show() {
  59. if (this._element.parentNode &&
  60. this._element.parentNode.nodeType === Node.ELEMENT_NODE &&
  61. $(this._element).hasClass(ClassName.ACTIVE) ||
  62. $(this._element).hasClass(ClassName.DISABLED)) {
  63. return
  64. }
  65. let target
  66. let previous
  67. const listElement = $(this._element).closest(Selector.NAV_LIST_GROUP)[0]
  68. const selector = Util.getSelectorFromElement(this._element)
  69. if (listElement) {
  70. const itemSelector = listElement.nodeName === 'UL' ? Selector.ACTIVE_UL : Selector.ACTIVE
  71. previous = $.makeArray($(listElement).find(itemSelector))
  72. previous = previous[previous.length - 1]
  73. }
  74. const hideEvent = $.Event(Event.HIDE, {
  75. relatedTarget: this._element
  76. })
  77. const showEvent = $.Event(Event.SHOW, {
  78. relatedTarget: previous
  79. })
  80. if (previous) {
  81. $(previous).trigger(hideEvent)
  82. }
  83. $(this._element).trigger(showEvent)
  84. if (showEvent.isDefaultPrevented() ||
  85. hideEvent.isDefaultPrevented()) {
  86. return
  87. }
  88. if (selector) {
  89. target = document.querySelector(selector)
  90. }
  91. this._activate(
  92. this._element,
  93. listElement
  94. )
  95. const complete = () => {
  96. const hiddenEvent = $.Event(Event.HIDDEN, {
  97. relatedTarget: this._element
  98. })
  99. const shownEvent = $.Event(Event.SHOWN, {
  100. relatedTarget: previous
  101. })
  102. $(previous).trigger(hiddenEvent)
  103. $(this._element).trigger(shownEvent)
  104. }
  105. if (target) {
  106. this._activate(target, target.parentNode, complete)
  107. } else {
  108. complete()
  109. }
  110. }
  111. dispose() {
  112. $.removeData(this._element, DATA_KEY)
  113. this._element = null
  114. }
  115. // Private
  116. _activate(element, container, callback) {
  117. let activeElements
  118. if (container.nodeName === 'UL') {
  119. activeElements = $(container).find(Selector.ACTIVE_UL)
  120. } else {
  121. activeElements = $(container).children(Selector.ACTIVE)
  122. }
  123. const active = activeElements[0]
  124. const isTransitioning = callback &&
  125. (active && $(active).hasClass(ClassName.FADE))
  126. const complete = () => this._transitionComplete(
  127. element,
  128. active,
  129. callback
  130. )
  131. if (active && isTransitioning) {
  132. const transitionDuration = Util.getTransitionDurationFromElement(active)
  133. $(active)
  134. .one(Util.TRANSITION_END, complete)
  135. .emulateTransitionEnd(transitionDuration)
  136. } else {
  137. complete()
  138. }
  139. }
  140. _transitionComplete(element, active, callback) {
  141. if (active) {
  142. $(active).removeClass(`${ClassName.SHOW} ${ClassName.ACTIVE}`)
  143. const dropdownChild = $(active.parentNode).find(
  144. Selector.DROPDOWN_ACTIVE_CHILD
  145. )[0]
  146. if (dropdownChild) {
  147. $(dropdownChild).removeClass(ClassName.ACTIVE)
  148. }
  149. if (active.getAttribute('role') === 'tab') {
  150. active.setAttribute('aria-selected', false)
  151. }
  152. }
  153. $(element).addClass(ClassName.ACTIVE)
  154. if (element.getAttribute('role') === 'tab') {
  155. element.setAttribute('aria-selected', true)
  156. }
  157. Util.reflow(element)
  158. $(element).addClass(ClassName.SHOW)
  159. if (element.parentNode &&
  160. $(element.parentNode).hasClass(ClassName.DROPDOWN_MENU)) {
  161. const dropdownElement = $(element).closest(Selector.DROPDOWN)[0]
  162. if (dropdownElement) {
  163. const dropdownToggleList = [].slice.call(dropdownElement.querySelectorAll(Selector.DROPDOWN_TOGGLE))
  164. $(dropdownToggleList).addClass(ClassName.ACTIVE)
  165. }
  166. element.setAttribute('aria-expanded', true)
  167. }
  168. if (callback) {
  169. callback()
  170. }
  171. }
  172. // Static
  173. static _jQueryInterface(config) {
  174. return this.each(function () {
  175. const $this = $(this)
  176. let data = $this.data(DATA_KEY)
  177. if (!data) {
  178. data = new Tab(this)
  179. $this.data(DATA_KEY, data)
  180. }
  181. if (typeof config === 'string') {
  182. if (typeof data[config] === 'undefined') {
  183. throw new TypeError(`No method named "${config}"`)
  184. }
  185. data[config]()
  186. }
  187. })
  188. }
  189. }
  190. /**
  191. * ------------------------------------------------------------------------
  192. * Data Api implementation
  193. * ------------------------------------------------------------------------
  194. */
  195. $(document)
  196. .on(Event.CLICK_DATA_API, Selector.DATA_TOGGLE, function (event) {
  197. event.preventDefault()
  198. Tab._jQueryInterface.call($(this), 'show')
  199. })
  200. /**
  201. * ------------------------------------------------------------------------
  202. * jQuery
  203. * ------------------------------------------------------------------------
  204. */
  205. $.fn[NAME] = Tab._jQueryInterface
  206. $.fn[NAME].Constructor = Tab
  207. $.fn[NAME].noConflict = function () {
  208. $.fn[NAME] = JQUERY_NO_CONFLICT
  209. return Tab._jQueryInterface
  210. }
  211. return Tab
  212. })($)
  213. export default Tab