tab.js 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278
  1. import Util from './util'
  2. /**
  3. * --------------------------------------------------------------------------
  4. * Bootstrap (v4.0.0-alpha.4): tab.js
  5. * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
  6. * --------------------------------------------------------------------------
  7. */
  8. const Tab = (($) => {
  9. /**
  10. * ------------------------------------------------------------------------
  11. * Constants
  12. * ------------------------------------------------------------------------
  13. */
  14. const NAME = 'tab'
  15. const VERSION = '4.0.0-alpha.4'
  16. const DATA_KEY = 'bs.tab'
  17. const EVENT_KEY = `.${DATA_KEY}`
  18. const DATA_API_KEY = '.data-api'
  19. const JQUERY_NO_CONFLICT = $.fn[NAME]
  20. const TRANSITION_DURATION = 150
  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. FADE : 'fade',
  32. IN : 'in'
  33. }
  34. const Selector = {
  35. A : 'a',
  36. LI : 'li',
  37. DROPDOWN : '.dropdown',
  38. UL : 'ul:not(.dropdown-menu)',
  39. FADE_CHILD : '> .nav-item .fade, > .fade',
  40. ACTIVE : '.active',
  41. ACTIVE_CHILD : '> .nav-item > .active, > .active',
  42. DATA_TOGGLE : '[data-toggle="tab"], [data-toggle="pill"]',
  43. DROPDOWN_TOGGLE : '.dropdown-toggle',
  44. DROPDOWN_ACTIVE_CHILD : '> .dropdown-menu .active'
  45. }
  46. /**
  47. * ------------------------------------------------------------------------
  48. * Class Definition
  49. * ------------------------------------------------------------------------
  50. */
  51. class Tab {
  52. constructor(element) {
  53. this._element = element
  54. }
  55. // getters
  56. static get VERSION() {
  57. return VERSION
  58. }
  59. // public
  60. show() {
  61. if (this._element.parentNode &&
  62. (this._element.parentNode.nodeType === Node.ELEMENT_NODE) &&
  63. ($(this._element).hasClass(ClassName.ACTIVE))) {
  64. return
  65. }
  66. let target
  67. let previous
  68. let ulElement = $(this._element).closest(Selector.UL)[0]
  69. let selector = Util.getSelectorFromElement(this._element)
  70. if (ulElement) {
  71. previous = $.makeArray($(ulElement).find(Selector.ACTIVE))
  72. previous = previous[previous.length - 1]
  73. }
  74. let hideEvent = $.Event(Event.HIDE, {
  75. relatedTarget: this._element
  76. })
  77. let 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 = $(selector)[0]
  90. }
  91. this._activate(
  92. this._element,
  93. ulElement
  94. )
  95. let complete = () => {
  96. let hiddenEvent = $.Event(Event.HIDDEN, {
  97. relatedTarget: this._element
  98. })
  99. let 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. $.removeClass(this._element, DATA_KEY)
  113. this._element = null
  114. }
  115. // private
  116. _activate(element, container, callback) {
  117. let active = $(container).find(Selector.ACTIVE_CHILD)[0]
  118. let isTransitioning = callback
  119. && Util.supportsTransitionEnd()
  120. && ((active && $(active).hasClass(ClassName.FADE))
  121. || Boolean($(container).find(Selector.FADE_CHILD)[0]))
  122. let complete = $.proxy(
  123. this._transitionComplete,
  124. this,
  125. element,
  126. active,
  127. isTransitioning,
  128. callback
  129. )
  130. if (active && isTransitioning) {
  131. $(active)
  132. .one(Util.TRANSITION_END, complete)
  133. .emulateTransitionEnd(TRANSITION_DURATION)
  134. } else {
  135. complete()
  136. }
  137. if (active) {
  138. $(active).removeClass(ClassName.IN)
  139. }
  140. }
  141. _transitionComplete(element, active, isTransitioning, callback) {
  142. if (active) {
  143. $(active).removeClass(ClassName.ACTIVE)
  144. let dropdownChild = $(active).find(
  145. Selector.DROPDOWN_ACTIVE_CHILD
  146. )[0]
  147. if (dropdownChild) {
  148. $(dropdownChild).removeClass(ClassName.ACTIVE)
  149. }
  150. active.setAttribute('aria-expanded', false)
  151. }
  152. $(element).addClass(ClassName.ACTIVE)
  153. element.setAttribute('aria-expanded', true)
  154. if (isTransitioning) {
  155. Util.reflow(element)
  156. $(element).addClass(ClassName.IN)
  157. } else {
  158. $(element).removeClass(ClassName.FADE)
  159. }
  160. if (element.parentNode &&
  161. ($(element.parentNode).hasClass(ClassName.DROPDOWN_MENU))) {
  162. let dropdownElement = $(element).closest(Selector.DROPDOWN)[0]
  163. if (dropdownElement) {
  164. $(dropdownElement).find(Selector.DROPDOWN_TOGGLE).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. let $this = $(this)
  176. let data = $this.data(DATA_KEY)
  177. if (!data) {
  178. data = data = new Tab(this)
  179. $this.data(DATA_KEY, data)
  180. }
  181. if (typeof config === 'string') {
  182. if (data[config] === undefined) {
  183. throw new Error(`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. })(jQuery)
  213. export default Tab