25개 이상의 토픽을 선택하실 수 없습니다. Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

265 lines
8.2 KiB

  1. /**
  2. * --------------------------------------------------------------------------
  3. * Bootstrap (v5.1.3): tab.js and base-component.js
  4. * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
  5. * --------------------------------------------------------------------------
  6. */
  7. define([
  8. "./util/index",
  9. "./dom/event-handler",
  10. "./dom/selector-engine"
  11. ], function(Util, EventHandler, SelectorEngine) {
  12. 'use strict';
  13. const defineJQueryPlugin = Util.defineJQueryPlugin;
  14. const executeAfterTransition = Util.executeAfterTransition;
  15. const getElement = Util.getElement;
  16. const getElementFromSelector = Util.getElementFromSelector;
  17. const isDisabled = Util.isDisabled;
  18. const reflow = Util.reflow;
  19. /**
  20. * ------------------------------------------------------------------------
  21. * Constants
  22. * ------------------------------------------------------------------------
  23. */
  24. const VERSION = '5.1.3';
  25. const NAME = 'tab';
  26. const DATA_KEY = 'bs.tab';
  27. const EVENT_KEY = `.${DATA_KEY}`;
  28. 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}`;
  34. const CLASS_NAME_DROPDOWN_MENU = 'dropdown-menu';
  35. const CLASS_NAME_ACTIVE = 'active';
  36. const CLASS_NAME_FADE = 'fade';
  37. const CLASS_NAME_SHOW = 'show';
  38. const SELECTOR_DROPDOWN = '.dropdown';
  39. const SELECTOR_NAV_LIST_GROUP = '.nav, .list-group';
  40. const SELECTOR_ACTIVE = '.active';
  41. const SELECTOR_ACTIVE_UL = ':scope > li > .active';
  42. const SELECTOR_DATA_TOGGLE = '[data-bs-toggle="tab"], [data-bs-toggle="pill"], [data-bs-toggle="list"]';
  43. const SELECTOR_DROPDOWN_TOGGLE = '.dropdown-toggle';
  44. const SELECTOR_DROPDOWN_ACTIVE_CHILD = ':scope > .dropdown-menu .active';
  45. /**
  46. * ------------------------------------------------------------------------
  47. * Class Definition
  48. * ------------------------------------------------------------------------
  49. */
  50. function Tab(element) {
  51. element = getElement(element);
  52. if (!element) {
  53. return;
  54. }
  55. this._element = element;
  56. Data.set(this._element, DATA_KEY, this);
  57. }
  58. // Getters
  59. Tab.VERSION = VERSION;
  60. Tab.NAME = NAME;
  61. Tab.DATA_KEY = 'bs.' + Tab.NAME;
  62. Tab.EVENT_KEY = '.' + Tab.DATA_KEY;
  63. // Public
  64. Tab.prototype.dispose = function() {
  65. Data.remove(this._element, this.constructor.DATA_KEY);
  66. EventHandler.off(this._element, this.constructor.EVENT_KEY);
  67. Object.getOwnPropertyNames(this).forEach(propertyName => {
  68. this[propertyName] = null;
  69. })
  70. }
  71. Tab.prototype._queueCallback = function(callback, element, isAnimated = true) {
  72. executeAfterTransition(callback, element, isAnimated);
  73. }
  74. Tab.prototype.show = function() {
  75. if ((this._element.parentNode &&
  76. this._element.parentNode.nodeType === Node.ELEMENT_NODE &&
  77. this._element.classList.contains(CLASS_NAME_ACTIVE))) {
  78. return;
  79. }
  80. let previous;
  81. const target = getElementFromSelector(this._element);
  82. const listElement = this._element.closest(SELECTOR_NAV_LIST_GROUP);
  83. if (listElement) {
  84. const itemSelector = listElement.nodeName === 'UL' || listElement.nodeName === 'OL' ? SELECTOR_ACTIVE_UL : SELECTOR_ACTIVE;
  85. previous = SelectorEngine.find(itemSelector, listElement);
  86. previous = previous[previous.length - 1];
  87. }
  88. const hideEvent = previous ?
  89. EventHandler.trigger(previous, EVENT_HIDE, {
  90. relatedTarget: this._element
  91. }) :
  92. null;
  93. const showEvent = EventHandler.trigger(this._element, EVENT_SHOW, {
  94. relatedTarget: previous
  95. });
  96. if (showEvent.defaultPrevented || (hideEvent !== null && hideEvent.defaultPrevented)) {
  97. return;
  98. }
  99. this._activate(this._element, listElement);
  100. const complete = () => {
  101. EventHandler.trigger(previous, EVENT_HIDDEN, {
  102. relatedTarget: this._element
  103. });
  104. EventHandler.trigger(this._element, EVENT_SHOWN, {
  105. relatedTarget: previous
  106. });
  107. }
  108. if (target) {
  109. this._activate(target, target.parentNode, complete);
  110. } else {
  111. complete();
  112. }
  113. }
  114. // Private
  115. Tab.prototype._activate = function(element, container, callback) {
  116. const activeElements = container && (container.nodeName === 'UL' || container.nodeName === 'OL') ?
  117. SelectorEngine.find(SELECTOR_ACTIVE_UL, container) :
  118. SelectorEngine.children(container, SELECTOR_ACTIVE);
  119. const active = activeElements[0];
  120. const isTransitioning = callback && (active && active.classList.contains(CLASS_NAME_FADE));
  121. const complete = () => this._transitionComplete(element, active, callback);
  122. if (active && isTransitioning) {
  123. active.classList.remove(CLASS_NAME_SHOW);
  124. this._queueCallback(complete, element, true);
  125. } else {
  126. complete();
  127. }
  128. }
  129. Tab.prototype._transitionComplete = function(element, active, callback) {
  130. if (active) {
  131. active.classList.remove(CLASS_NAME_ACTIVE);
  132. const dropdownChild = SelectorEngine.findOne(SELECTOR_DROPDOWN_ACTIVE_CHILD, active.parentNode);
  133. if (dropdownChild) {
  134. dropdownChild.classList.remove(CLASS_NAME_ACTIVE);
  135. }
  136. if (active.getAttribute('role') === 'tab') {
  137. active.setAttribute('aria-selected', false);
  138. }
  139. }
  140. element.classList.add(CLASS_NAME_ACTIVE);
  141. if (element.getAttribute('role') === 'tab') {
  142. element.setAttribute('aria-selected', true);
  143. }
  144. reflow(element);
  145. if (element.classList.contains(CLASS_NAME_FADE)) {
  146. element.classList.add(CLASS_NAME_SHOW);
  147. }
  148. let parent = element.parentNode;
  149. if (parent && parent.nodeName === 'LI') {
  150. parent = parent.parentNode;
  151. }
  152. if (parent && parent.classList.contains(CLASS_NAME_DROPDOWN_MENU)) {
  153. const dropdownElement = element.closest(SELECTOR_DROPDOWN);
  154. if (dropdownElement) {
  155. SelectorEngine.find(SELECTOR_DROPDOWN_TOGGLE, dropdownElement)
  156. .forEach(dropdown => dropdown.classList.add(CLASS_NAME_ACTIVE));
  157. }
  158. element.setAttribute('aria-expanded', true);
  159. }
  160. if (callback) {
  161. callback();
  162. }
  163. }
  164. // Static
  165. Tab.getInstance = function(element) {
  166. return Data.get(getElement(element), this.DATA_KEY);
  167. }
  168. Tab.getOrCreateInstance = function(element, config = {}) {
  169. return this.getInstance(element) || new this(element, typeof config === 'object' ? config : null);
  170. }
  171. Tab.jQueryInterface = function(config) {
  172. return this.each(function () {
  173. const data = Tab.getOrCreateInstance(this);
  174. if (typeof config === 'string') {
  175. if (typeof data[config] === 'undefined') {
  176. throw new TypeError(`No method named "${config}"`);
  177. }
  178. data[config]();
  179. }
  180. })
  181. }
  182. /**
  183. * ------------------------------------------------------------------------
  184. * Data Api implementation
  185. * ------------------------------------------------------------------------
  186. */
  187. EventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function (event) {
  188. if (['A', 'AREA'].includes(this.tagName)) {
  189. event.preventDefault();
  190. }
  191. if (isDisabled(this)) {
  192. return;
  193. }
  194. const data = Tab.getOrCreateInstance(this);
  195. data.show();
  196. })
  197. /**
  198. * ------------------------------------------------------------------------
  199. * jQuery
  200. * ------------------------------------------------------------------------
  201. * add .Tab to jQuery only if jQuery is present
  202. */
  203. defineJQueryPlugin(Tab);
  204. return Tab;
  205. });