bootstrap 5.1.3 → 5.3.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (127) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +61 -0
  3. data/CHANGELOG.md +9 -0
  4. data/Gemfile +1 -0
  5. data/README.md +35 -14
  6. data/Rakefile +16 -5
  7. data/assets/javascripts/bootstrap/alert.js +22 -167
  8. data/assets/javascripts/bootstrap/base-component.js +34 -133
  9. data/assets/javascripts/bootstrap/button.js +19 -86
  10. data/assets/javascripts/bootstrap/carousel.js +209 -564
  11. data/assets/javascripts/bootstrap/collapse.js +78 -324
  12. data/assets/javascripts/bootstrap/dom/data.js +8 -14
  13. data/assets/javascripts/bootstrap/dom/event-handler.js +89 -174
  14. data/assets/javascripts/bootstrap/dom/manipulator.js +22 -39
  15. data/assets/javascripts/bootstrap/dom/selector-engine.js +47 -71
  16. data/assets/javascripts/bootstrap/dropdown.js +135 -420
  17. data/assets/javascripts/bootstrap/modal.js +115 -837
  18. data/assets/javascripts/bootstrap/offcanvas.js +93 -714
  19. data/assets/javascripts/bootstrap/popover.js +42 -130
  20. data/assets/javascripts/bootstrap/scrollspy.js +180 -296
  21. data/assets/javascripts/bootstrap/tab.js +197 -245
  22. data/assets/javascripts/bootstrap/toast.js +52 -276
  23. data/assets/javascripts/bootstrap/tooltip.js +283 -744
  24. data/assets/javascripts/bootstrap/util/backdrop.js +138 -0
  25. data/assets/javascripts/bootstrap/util/component-functions.js +41 -0
  26. data/assets/javascripts/bootstrap/util/config.js +67 -0
  27. data/assets/javascripts/bootstrap/util/focustrap.js +112 -0
  28. data/assets/javascripts/bootstrap/util/index.js +280 -0
  29. data/assets/javascripts/bootstrap/util/sanitizer.js +113 -0
  30. data/assets/javascripts/bootstrap/util/scrollbar.js +112 -0
  31. data/assets/javascripts/bootstrap/util/swipe.js +134 -0
  32. data/assets/javascripts/bootstrap/util/template-factory.js +150 -0
  33. data/assets/javascripts/bootstrap-global-this-define.js +1 -1
  34. data/assets/javascripts/bootstrap-sprockets.js +15 -6
  35. data/assets/javascripts/bootstrap.js +2278 -2831
  36. data/assets/javascripts/bootstrap.min.js +3 -3
  37. data/assets/stylesheets/_bootstrap-grid.scss +4 -9
  38. data/assets/stylesheets/_bootstrap-reboot.scss +4 -7
  39. data/assets/stylesheets/_bootstrap-utilities.scss +19 -0
  40. data/assets/stylesheets/_bootstrap.scss +5 -6
  41. data/assets/stylesheets/bootstrap/_accordion.scss +68 -33
  42. data/assets/stylesheets/bootstrap/_alert.scss +25 -14
  43. data/assets/stylesheets/bootstrap/_badge.scss +14 -5
  44. data/assets/stylesheets/bootstrap/_breadcrumb.scss +22 -10
  45. data/assets/stylesheets/bootstrap/_button-group.scss +12 -4
  46. data/assets/stylesheets/bootstrap/_buttons.scss +133 -28
  47. data/assets/stylesheets/bootstrap/_card.scss +61 -39
  48. data/assets/stylesheets/bootstrap/_carousel.scss +22 -25
  49. data/assets/stylesheets/bootstrap/_close.scss +36 -10
  50. data/assets/stylesheets/bootstrap/_containers.scss +1 -1
  51. data/assets/stylesheets/bootstrap/_dropdown.scss +86 -76
  52. data/assets/stylesheets/bootstrap/_functions.scss +10 -10
  53. data/assets/stylesheets/bootstrap/_grid.scss +9 -3
  54. data/assets/stylesheets/bootstrap/_helpers.scss +3 -0
  55. data/assets/stylesheets/bootstrap/_list-group.scss +81 -56
  56. data/assets/stylesheets/bootstrap/_maps.scss +174 -0
  57. data/assets/stylesheets/bootstrap/_mixins.scss +1 -2
  58. data/assets/stylesheets/bootstrap/_modal.scss +76 -45
  59. data/assets/stylesheets/bootstrap/_nav.scss +87 -29
  60. data/assets/stylesheets/bootstrap/_navbar.scss +102 -148
  61. data/assets/stylesheets/bootstrap/_offcanvas.scss +125 -61
  62. data/assets/stylesheets/bootstrap/_pagination.scss +66 -21
  63. data/assets/stylesheets/bootstrap/_placeholders.scss +1 -1
  64. data/assets/stylesheets/bootstrap/_popover.scss +90 -52
  65. data/assets/stylesheets/bootstrap/_progress.scss +31 -11
  66. data/assets/stylesheets/bootstrap/_reboot.scss +32 -46
  67. data/assets/stylesheets/bootstrap/_root.scss +155 -22
  68. data/assets/stylesheets/bootstrap/_spinners.scss +38 -22
  69. data/assets/stylesheets/bootstrap/_tables.scss +40 -24
  70. data/assets/stylesheets/bootstrap/_toasts.scss +38 -16
  71. data/assets/stylesheets/bootstrap/_tooltip.scss +60 -56
  72. data/assets/stylesheets/bootstrap/_type.scss +3 -1
  73. data/assets/stylesheets/bootstrap/_utilities.scss +209 -33
  74. data/assets/stylesheets/bootstrap/_variables-dark.scss +102 -0
  75. data/assets/stylesheets/bootstrap/_variables.scss +415 -303
  76. data/assets/stylesheets/bootstrap/forms/_floating-labels.scss +39 -5
  77. data/assets/stylesheets/bootstrap/forms/_form-check.scss +51 -14
  78. data/assets/stylesheets/bootstrap/forms/_form-control.scss +36 -41
  79. data/assets/stylesheets/bootstrap/forms/_form-range.scss +3 -3
  80. data/assets/stylesheets/bootstrap/forms/_form-select.scss +12 -4
  81. data/assets/stylesheets/bootstrap/forms/_input-group.scss +20 -9
  82. data/assets/stylesheets/bootstrap/helpers/_color-bg.scss +7 -0
  83. data/assets/stylesheets/bootstrap/helpers/_colored-links.scss +20 -2
  84. data/assets/stylesheets/bootstrap/helpers/_focus-ring.scss +5 -0
  85. data/assets/stylesheets/bootstrap/helpers/_icon-link.scss +25 -0
  86. data/assets/stylesheets/bootstrap/helpers/_position.scss +7 -1
  87. data/assets/stylesheets/bootstrap/helpers/_ratio.scss +2 -2
  88. data/assets/stylesheets/bootstrap/helpers/_vr.scss +2 -2
  89. data/assets/stylesheets/bootstrap/mixins/_alert.scss +11 -4
  90. data/assets/stylesheets/bootstrap/mixins/_banner.scss +7 -0
  91. data/assets/stylesheets/bootstrap/mixins/_breakpoints.scss +8 -8
  92. data/assets/stylesheets/bootstrap/mixins/_buttons.scss +32 -95
  93. data/assets/stylesheets/bootstrap/mixins/_caret.scss +30 -25
  94. data/assets/stylesheets/bootstrap/mixins/_color-mode.scss +21 -0
  95. data/assets/stylesheets/bootstrap/mixins/_container.scss +4 -2
  96. data/assets/stylesheets/bootstrap/mixins/_forms.scss +38 -19
  97. data/assets/stylesheets/bootstrap/mixins/_gradients.scss +1 -1
  98. data/assets/stylesheets/bootstrap/mixins/_grid.scss +15 -15
  99. data/assets/stylesheets/bootstrap/mixins/_list-group.scss +2 -0
  100. data/assets/stylesheets/bootstrap/mixins/_pagination.scss +4 -25
  101. data/assets/stylesheets/bootstrap/mixins/_reset-text.scss +1 -1
  102. data/assets/stylesheets/bootstrap/mixins/_table-variants.scss +12 -9
  103. data/assets/stylesheets/bootstrap/mixins/_utilities.scss +14 -6
  104. data/assets/stylesheets/bootstrap/mixins/_visually-hidden.scss +6 -2
  105. data/assets/stylesheets/bootstrap/vendor/_rfs.scss +23 -29
  106. data/bootstrap.gemspec +3 -3
  107. data/lib/bootstrap/engine.rb +17 -2
  108. data/lib/bootstrap/version.rb +2 -2
  109. data/tasks/updater/js.rb +10 -5
  110. data/tasks/updater/network.rb +2 -2
  111. data/tasks/updater/scss.rb +2 -2
  112. data/tasks/updater.rb +2 -2
  113. data/test/dummy_rails/config/application.rb +0 -2
  114. data/test/dummy_rails/public/favicon.ico +0 -0
  115. data/test/gemfiles/rails_4_2.gemfile +2 -1
  116. data/test/gemfiles/rails_5_0.gemfile +1 -2
  117. data/test/gemfiles/rails_5_1.gemfile +1 -2
  118. data/test/gemfiles/rails_5_2.gemfile +7 -0
  119. data/test/gemfiles/rails_6_0.gemfile +1 -1
  120. data/test/gemfiles/rails_6_1.gemfile +1 -1
  121. data/test/gemfiles/rails_7_0_dartsass.gemfile +7 -0
  122. data/test/gemfiles/rails_7_0_sassc.gemfile +7 -0
  123. data/test/rails_test.rb +0 -5
  124. data/test/test_helper.rb +3 -2
  125. metadata +49 -29
  126. data/.travis.yml +0 -32
  127. data/assets/stylesheets/bootstrap/bootstrap-utilities.scss +0 -18
@@ -1,331 +1,283 @@
1
1
  /*!
2
- * Bootstrap tab.js v5.1.3 (https://getbootstrap.com/)
3
- * Copyright 2011-2021 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
2
+ * Bootstrap tab.js v5.3.5 (https://getbootstrap.com/)
3
+ * Copyright 2011-2025 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
4
4
  * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
5
5
  */
6
6
  (function (global, factory) {
7
- typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('./dom/event-handler.js'), require('./dom/selector-engine.js'), require('./base-component.js')) :
8
- typeof define === 'function' && define.amd ? define(['./dom/event-handler', './dom/selector-engine', './base-component'], factory) :
9
- (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.Tab = factory(global.EventHandler, global.SelectorEngine, global.Base));
10
- })(this, (function (EventHandler, SelectorEngine, BaseComponent) { 'use strict';
11
-
12
- const _interopDefaultLegacy = e => e && typeof e === 'object' && 'default' in e ? e : { default: e };
13
-
14
- const EventHandler__default = /*#__PURE__*/_interopDefaultLegacy(EventHandler);
15
- const SelectorEngine__default = /*#__PURE__*/_interopDefaultLegacy(SelectorEngine);
16
- const BaseComponent__default = /*#__PURE__*/_interopDefaultLegacy(BaseComponent);
7
+ typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('./base-component.js'), require('./dom/event-handler.js'), require('./dom/selector-engine.js'), require('./util/index.js')) :
8
+ typeof define === 'function' && define.amd ? define(['./base-component', './dom/event-handler', './dom/selector-engine', './util/index'], factory) :
9
+ (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.Tab = factory(global.BaseComponent, global.EventHandler, global.SelectorEngine, global.Index));
10
+ })(this, (function (BaseComponent, EventHandler, SelectorEngine, index_js) { 'use strict';
17
11
 
18
12
  /**
19
13
  * --------------------------------------------------------------------------
20
- * Bootstrap (v5.1.3): util/index.js
14
+ * Bootstrap tab.js
21
15
  * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
22
16
  * --------------------------------------------------------------------------
23
17
  */
24
18
 
25
- const getSelector = element => {
26
- let selector = element.getAttribute('data-bs-target');
27
-
28
- if (!selector || selector === '#') {
29
- let hrefAttr = element.getAttribute('href'); // The only valid content that could double as a selector are IDs or classes,
30
- // so everything starting with `#` or `.`. If a "real" URL is used as the selector,
31
- // `document.querySelector` will rightfully complain it is invalid.
32
- // See https://github.com/twbs/bootstrap/issues/32273
33
-
34
- if (!hrefAttr || !hrefAttr.includes('#') && !hrefAttr.startsWith('.')) {
35
- return null;
36
- } // Just in case some CMS puts out a full URL with the anchor appended
37
-
38
-
39
- if (hrefAttr.includes('#') && !hrefAttr.startsWith('#')) {
40
- hrefAttr = `#${hrefAttr.split('#')[1]}`;
41
- }
42
-
43
- selector = hrefAttr && hrefAttr !== '#' ? hrefAttr.trim() : null;
44
- }
45
-
46
- return selector;
47
- };
48
-
49
- const getElementFromSelector = element => {
50
- const selector = getSelector(element);
51
- return selector ? document.querySelector(selector) : null;
52
- };
53
-
54
- const isDisabled = element => {
55
- if (!element || element.nodeType !== Node.ELEMENT_NODE) {
56
- return true;
57
- }
58
-
59
- if (element.classList.contains('disabled')) {
60
- return true;
61
- }
62
19
 
63
- if (typeof element.disabled !== 'undefined') {
64
- return element.disabled;
65
- }
66
-
67
- return element.hasAttribute('disabled') && element.getAttribute('disabled') !== 'false';
68
- };
69
20
  /**
70
- * Trick to restart an element's animation
71
- *
72
- * @param {HTMLElement} element
73
- * @return void
74
- *
75
- * @see https://www.charistheo.io/blog/2021/02/restart-a-css-animation-with-javascript/#restarting-a-css-animation
76
- */
77
-
78
-
79
- const reflow = element => {
80
- // eslint-disable-next-line no-unused-expressions
81
- element.offsetHeight;
82
- };
83
-
84
- const getjQuery = () => {
85
- const {
86
- jQuery
87
- } = window;
88
-
89
- if (jQuery && !document.body.hasAttribute('data-bs-no-jquery')) {
90
- return jQuery;
91
- }
92
-
93
- return null;
94
- };
95
-
96
- const DOMContentLoadedCallbacks = [];
97
-
98
- const onDOMContentLoaded = callback => {
99
- if (document.readyState === 'loading') {
100
- // add listener on the first call when the document is in loading state
101
- if (!DOMContentLoadedCallbacks.length) {
102
- document.addEventListener('DOMContentLoaded', () => {
103
- DOMContentLoadedCallbacks.forEach(callback => callback());
104
- });
105
- }
106
-
107
- DOMContentLoadedCallbacks.push(callback);
108
- } else {
109
- callback();
110
- }
111
- };
112
-
113
- const defineJQueryPlugin = plugin => {
114
- onDOMContentLoaded(() => {
115
- const $ = getjQuery();
116
- /* istanbul ignore if */
117
-
118
- if ($) {
119
- const name = plugin.NAME;
120
- const JQUERY_NO_CONFLICT = $.fn[name];
121
- $.fn[name] = plugin.jQueryInterface;
122
- $.fn[name].Constructor = plugin;
123
-
124
- $.fn[name].noConflict = () => {
125
- $.fn[name] = JQUERY_NO_CONFLICT;
126
- return plugin.jQueryInterface;
127
- };
128
- }
129
- });
130
- };
131
-
132
- /**
133
- * --------------------------------------------------------------------------
134
- * Bootstrap (v5.1.3): tab.js
135
- * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
136
- * --------------------------------------------------------------------------
137
- */
138
- /**
139
- * ------------------------------------------------------------------------
140
21
  * Constants
141
- * ------------------------------------------------------------------------
142
22
  */
143
23
 
144
24
  const NAME = 'tab';
145
25
  const DATA_KEY = 'bs.tab';
146
26
  const EVENT_KEY = `.${DATA_KEY}`;
147
- const DATA_API_KEY = '.data-api';
148
27
  const EVENT_HIDE = `hide${EVENT_KEY}`;
149
28
  const EVENT_HIDDEN = `hidden${EVENT_KEY}`;
150
29
  const EVENT_SHOW = `show${EVENT_KEY}`;
151
30
  const EVENT_SHOWN = `shown${EVENT_KEY}`;
152
- const EVENT_CLICK_DATA_API = `click${EVENT_KEY}${DATA_API_KEY}`;
153
- const CLASS_NAME_DROPDOWN_MENU = 'dropdown-menu';
31
+ const EVENT_CLICK_DATA_API = `click${EVENT_KEY}`;
32
+ const EVENT_KEYDOWN = `keydown${EVENT_KEY}`;
33
+ const EVENT_LOAD_DATA_API = `load${EVENT_KEY}`;
34
+ const ARROW_LEFT_KEY = 'ArrowLeft';
35
+ const ARROW_RIGHT_KEY = 'ArrowRight';
36
+ const ARROW_UP_KEY = 'ArrowUp';
37
+ const ARROW_DOWN_KEY = 'ArrowDown';
38
+ const HOME_KEY = 'Home';
39
+ const END_KEY = 'End';
154
40
  const CLASS_NAME_ACTIVE = 'active';
155
41
  const CLASS_NAME_FADE = 'fade';
156
42
  const CLASS_NAME_SHOW = 'show';
157
- const SELECTOR_DROPDOWN = '.dropdown';
158
- const SELECTOR_NAV_LIST_GROUP = '.nav, .list-group';
159
- const SELECTOR_ACTIVE = '.active';
160
- const SELECTOR_ACTIVE_UL = ':scope > li > .active';
161
- const SELECTOR_DATA_TOGGLE = '[data-bs-toggle="tab"], [data-bs-toggle="pill"], [data-bs-toggle="list"]';
43
+ const CLASS_DROPDOWN = 'dropdown';
162
44
  const SELECTOR_DROPDOWN_TOGGLE = '.dropdown-toggle';
163
- const SELECTOR_DROPDOWN_ACTIVE_CHILD = ':scope > .dropdown-menu .active';
45
+ const SELECTOR_DROPDOWN_MENU = '.dropdown-menu';
46
+ const NOT_SELECTOR_DROPDOWN_TOGGLE = `:not(${SELECTOR_DROPDOWN_TOGGLE})`;
47
+ const SELECTOR_TAB_PANEL = '.list-group, .nav, [role="tablist"]';
48
+ const SELECTOR_OUTER = '.nav-item, .list-group-item';
49
+ const SELECTOR_INNER = `.nav-link${NOT_SELECTOR_DROPDOWN_TOGGLE}, .list-group-item${NOT_SELECTOR_DROPDOWN_TOGGLE}, [role="tab"]${NOT_SELECTOR_DROPDOWN_TOGGLE}`;
50
+ const SELECTOR_DATA_TOGGLE = '[data-bs-toggle="tab"], [data-bs-toggle="pill"], [data-bs-toggle="list"]'; // TODO: could only be `tab` in v6
51
+ const SELECTOR_INNER_ELEM = `${SELECTOR_INNER}, ${SELECTOR_DATA_TOGGLE}`;
52
+ const SELECTOR_DATA_TOGGLE_ACTIVE = `.${CLASS_NAME_ACTIVE}[data-bs-toggle="tab"], .${CLASS_NAME_ACTIVE}[data-bs-toggle="pill"], .${CLASS_NAME_ACTIVE}[data-bs-toggle="list"]`;
53
+
164
54
  /**
165
- * ------------------------------------------------------------------------
166
- * Class Definition
167
- * ------------------------------------------------------------------------
55
+ * Class definition
168
56
  */
169
57
 
170
- class Tab extends BaseComponent__default.default {
58
+ class Tab extends BaseComponent {
59
+ constructor(element) {
60
+ super(element);
61
+ this._parent = this._element.closest(SELECTOR_TAB_PANEL);
62
+ if (!this._parent) {
63
+ return;
64
+ // TODO: should throw exception in v6
65
+ // throw new TypeError(`${element.outerHTML} has not a valid parent ${SELECTOR_INNER_ELEM}`)
66
+ }
67
+
68
+ // Set up initial aria attributes
69
+ this._setInitialAttributes(this._parent, this._getChildren());
70
+ EventHandler.on(this._element, EVENT_KEYDOWN, event => this._keydown(event));
71
+ }
72
+
171
73
  // Getters
172
74
  static get NAME() {
173
75
  return NAME;
174
- } // Public
175
-
76
+ }
176
77
 
78
+ // Public
177
79
  show() {
178
- if (this._element.parentNode && this._element.parentNode.nodeType === Node.ELEMENT_NODE && this._element.classList.contains(CLASS_NAME_ACTIVE)) {
80
+ // Shows this elem and deactivate the active sibling if exists
81
+ const innerElem = this._element;
82
+ if (this._elemIsActive(innerElem)) {
179
83
  return;
180
84
  }
181
85
 
182
- let previous;
183
- const target = getElementFromSelector(this._element);
184
-
185
- const listElement = this._element.closest(SELECTOR_NAV_LIST_GROUP);
186
-
187
- if (listElement) {
188
- const itemSelector = listElement.nodeName === 'UL' || listElement.nodeName === 'OL' ? SELECTOR_ACTIVE_UL : SELECTOR_ACTIVE;
189
- previous = SelectorEngine__default.default.find(itemSelector, listElement);
190
- previous = previous[previous.length - 1];
191
- }
192
-
193
- const hideEvent = previous ? EventHandler__default.default.trigger(previous, EVENT_HIDE, {
194
- relatedTarget: this._element
86
+ // Search for active tab on same parent to deactivate it
87
+ const active = this._getActiveElem();
88
+ const hideEvent = active ? EventHandler.trigger(active, EVENT_HIDE, {
89
+ relatedTarget: innerElem
195
90
  }) : null;
196
- const showEvent = EventHandler__default.default.trigger(this._element, EVENT_SHOW, {
197
- relatedTarget: previous
91
+ const showEvent = EventHandler.trigger(innerElem, EVENT_SHOW, {
92
+ relatedTarget: active
198
93
  });
199
-
200
- if (showEvent.defaultPrevented || hideEvent !== null && hideEvent.defaultPrevented) {
94
+ if (showEvent.defaultPrevented || hideEvent && hideEvent.defaultPrevented) {
201
95
  return;
202
96
  }
97
+ this._deactivate(active, innerElem);
98
+ this._activate(innerElem, active);
99
+ }
203
100
 
204
- this._activate(this._element, listElement);
101
+ // Private
102
+ _activate(element, relatedElem) {
103
+ if (!element) {
104
+ return;
105
+ }
106
+ element.classList.add(CLASS_NAME_ACTIVE);
107
+ this._activate(SelectorEngine.getElementFromSelector(element)); // Search and activate/show the proper section
205
108
 
206
109
  const complete = () => {
207
- EventHandler__default.default.trigger(previous, EVENT_HIDDEN, {
208
- relatedTarget: this._element
209
- });
210
- EventHandler__default.default.trigger(this._element, EVENT_SHOWN, {
211
- relatedTarget: previous
110
+ if (element.getAttribute('role') !== 'tab') {
111
+ element.classList.add(CLASS_NAME_SHOW);
112
+ return;
113
+ }
114
+ element.removeAttribute('tabindex');
115
+ element.setAttribute('aria-selected', true);
116
+ this._toggleDropDown(element, true);
117
+ EventHandler.trigger(element, EVENT_SHOWN, {
118
+ relatedTarget: relatedElem
212
119
  });
213
120
  };
214
-
215
- if (target) {
216
- this._activate(target, target.parentNode, complete);
217
- } else {
218
- complete();
121
+ this._queueCallback(complete, element, element.classList.contains(CLASS_NAME_FADE));
122
+ }
123
+ _deactivate(element, relatedElem) {
124
+ if (!element) {
125
+ return;
219
126
  }
220
- } // Private
221
-
222
-
223
- _activate(element, container, callback) {
224
- const activeElements = container && (container.nodeName === 'UL' || container.nodeName === 'OL') ? SelectorEngine__default.default.find(SELECTOR_ACTIVE_UL, container) : SelectorEngine__default.default.children(container, SELECTOR_ACTIVE);
225
- const active = activeElements[0];
226
- const isTransitioning = callback && active && active.classList.contains(CLASS_NAME_FADE);
227
-
228
- const complete = () => this._transitionComplete(element, active, callback);
229
-
230
- if (active && isTransitioning) {
231
- active.classList.remove(CLASS_NAME_SHOW);
127
+ element.classList.remove(CLASS_NAME_ACTIVE);
128
+ element.blur();
129
+ this._deactivate(SelectorEngine.getElementFromSelector(element)); // Search and deactivate the shown section too
232
130
 
233
- this._queueCallback(complete, element, true);
131
+ const complete = () => {
132
+ if (element.getAttribute('role') !== 'tab') {
133
+ element.classList.remove(CLASS_NAME_SHOW);
134
+ return;
135
+ }
136
+ element.setAttribute('aria-selected', false);
137
+ element.setAttribute('tabindex', '-1');
138
+ this._toggleDropDown(element, false);
139
+ EventHandler.trigger(element, EVENT_HIDDEN, {
140
+ relatedTarget: relatedElem
141
+ });
142
+ };
143
+ this._queueCallback(complete, element, element.classList.contains(CLASS_NAME_FADE));
144
+ }
145
+ _keydown(event) {
146
+ if (![ARROW_LEFT_KEY, ARROW_RIGHT_KEY, ARROW_UP_KEY, ARROW_DOWN_KEY, HOME_KEY, END_KEY].includes(event.key)) {
147
+ return;
148
+ }
149
+ event.stopPropagation(); // stopPropagation/preventDefault both added to support up/down keys without scrolling the page
150
+ event.preventDefault();
151
+ const children = this._getChildren().filter(element => !index_js.isDisabled(element));
152
+ let nextActiveElement;
153
+ if ([HOME_KEY, END_KEY].includes(event.key)) {
154
+ nextActiveElement = children[event.key === HOME_KEY ? 0 : children.length - 1];
234
155
  } else {
235
- complete();
156
+ const isNext = [ARROW_RIGHT_KEY, ARROW_DOWN_KEY].includes(event.key);
157
+ nextActiveElement = index_js.getNextActiveElement(children, event.target, isNext, true);
158
+ }
159
+ if (nextActiveElement) {
160
+ nextActiveElement.focus({
161
+ preventScroll: true
162
+ });
163
+ Tab.getOrCreateInstance(nextActiveElement).show();
236
164
  }
237
165
  }
238
-
239
- _transitionComplete(element, active, callback) {
240
- if (active) {
241
- active.classList.remove(CLASS_NAME_ACTIVE);
242
- const dropdownChild = SelectorEngine__default.default.findOne(SELECTOR_DROPDOWN_ACTIVE_CHILD, active.parentNode);
243
-
244
- if (dropdownChild) {
245
- dropdownChild.classList.remove(CLASS_NAME_ACTIVE);
246
- }
247
-
248
- if (active.getAttribute('role') === 'tab') {
249
- active.setAttribute('aria-selected', false);
250
- }
166
+ _getChildren() {
167
+ // collection of inner elements
168
+ return SelectorEngine.find(SELECTOR_INNER_ELEM, this._parent);
169
+ }
170
+ _getActiveElem() {
171
+ return this._getChildren().find(child => this._elemIsActive(child)) || null;
172
+ }
173
+ _setInitialAttributes(parent, children) {
174
+ this._setAttributeIfNotExists(parent, 'role', 'tablist');
175
+ for (const child of children) {
176
+ this._setInitialAttributesOnChild(child);
251
177
  }
252
-
253
- element.classList.add(CLASS_NAME_ACTIVE);
254
-
255
- if (element.getAttribute('role') === 'tab') {
256
- element.setAttribute('aria-selected', true);
178
+ }
179
+ _setInitialAttributesOnChild(child) {
180
+ child = this._getInnerElement(child);
181
+ const isActive = this._elemIsActive(child);
182
+ const outerElem = this._getOuterElement(child);
183
+ child.setAttribute('aria-selected', isActive);
184
+ if (outerElem !== child) {
185
+ this._setAttributeIfNotExists(outerElem, 'role', 'presentation');
257
186
  }
258
-
259
- reflow(element);
260
-
261
- if (element.classList.contains(CLASS_NAME_FADE)) {
262
- element.classList.add(CLASS_NAME_SHOW);
187
+ if (!isActive) {
188
+ child.setAttribute('tabindex', '-1');
263
189
  }
190
+ this._setAttributeIfNotExists(child, 'role', 'tab');
264
191
 
265
- let parent = element.parentNode;
266
-
267
- if (parent && parent.nodeName === 'LI') {
268
- parent = parent.parentNode;
192
+ // set attributes to the related panel too
193
+ this._setInitialAttributesOnTargetPanel(child);
194
+ }
195
+ _setInitialAttributesOnTargetPanel(child) {
196
+ const target = SelectorEngine.getElementFromSelector(child);
197
+ if (!target) {
198
+ return;
269
199
  }
270
-
271
- if (parent && parent.classList.contains(CLASS_NAME_DROPDOWN_MENU)) {
272
- const dropdownElement = element.closest(SELECTOR_DROPDOWN);
273
-
274
- if (dropdownElement) {
275
- SelectorEngine__default.default.find(SELECTOR_DROPDOWN_TOGGLE, dropdownElement).forEach(dropdown => dropdown.classList.add(CLASS_NAME_ACTIVE));
200
+ this._setAttributeIfNotExists(target, 'role', 'tabpanel');
201
+ if (child.id) {
202
+ this._setAttributeIfNotExists(target, 'aria-labelledby', `${child.id}`);
203
+ }
204
+ }
205
+ _toggleDropDown(element, open) {
206
+ const outerElem = this._getOuterElement(element);
207
+ if (!outerElem.classList.contains(CLASS_DROPDOWN)) {
208
+ return;
209
+ }
210
+ const toggle = (selector, className) => {
211
+ const element = SelectorEngine.findOne(selector, outerElem);
212
+ if (element) {
213
+ element.classList.toggle(className, open);
276
214
  }
277
-
278
- element.setAttribute('aria-expanded', true);
215
+ };
216
+ toggle(SELECTOR_DROPDOWN_TOGGLE, CLASS_NAME_ACTIVE);
217
+ toggle(SELECTOR_DROPDOWN_MENU, CLASS_NAME_SHOW);
218
+ outerElem.setAttribute('aria-expanded', open);
219
+ }
220
+ _setAttributeIfNotExists(element, attribute, value) {
221
+ if (!element.hasAttribute(attribute)) {
222
+ element.setAttribute(attribute, value);
279
223
  }
224
+ }
225
+ _elemIsActive(elem) {
226
+ return elem.classList.contains(CLASS_NAME_ACTIVE);
227
+ }
280
228
 
281
- if (callback) {
282
- callback();
283
- }
284
- } // Static
229
+ // Try to get the inner element (usually the .nav-link)
230
+ _getInnerElement(elem) {
231
+ return elem.matches(SELECTOR_INNER_ELEM) ? elem : SelectorEngine.findOne(SELECTOR_INNER_ELEM, elem);
232
+ }
285
233
 
234
+ // Try to get the outer element (usually the .nav-item)
235
+ _getOuterElement(elem) {
236
+ return elem.closest(SELECTOR_OUTER) || elem;
237
+ }
286
238
 
239
+ // Static
287
240
  static jQueryInterface(config) {
288
241
  return this.each(function () {
289
242
  const data = Tab.getOrCreateInstance(this);
290
-
291
- if (typeof config === 'string') {
292
- if (typeof data[config] === 'undefined') {
293
- throw new TypeError(`No method named "${config}"`);
294
- }
295
-
296
- data[config]();
243
+ if (typeof config !== 'string') {
244
+ return;
297
245
  }
246
+ if (data[config] === undefined || config.startsWith('_') || config === 'constructor') {
247
+ throw new TypeError(`No method named "${config}"`);
248
+ }
249
+ data[config]();
298
250
  });
299
251
  }
300
-
301
252
  }
253
+
302
254
  /**
303
- * ------------------------------------------------------------------------
304
- * Data Api implementation
305
- * ------------------------------------------------------------------------
255
+ * Data API implementation
306
256
  */
307
257
 
308
-
309
- EventHandler__default.default.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function (event) {
258
+ EventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function (event) {
310
259
  if (['A', 'AREA'].includes(this.tagName)) {
311
260
  event.preventDefault();
312
261
  }
313
-
314
- if (isDisabled(this)) {
262
+ if (index_js.isDisabled(this)) {
315
263
  return;
316
264
  }
265
+ Tab.getOrCreateInstance(this).show();
266
+ });
317
267
 
318
- const data = Tab.getOrCreateInstance(this);
319
- data.show();
268
+ /**
269
+ * Initialize on focus
270
+ */
271
+ EventHandler.on(window, EVENT_LOAD_DATA_API, () => {
272
+ for (const element of SelectorEngine.find(SELECTOR_DATA_TOGGLE_ACTIVE)) {
273
+ Tab.getOrCreateInstance(element);
274
+ }
320
275
  });
321
276
  /**
322
- * ------------------------------------------------------------------------
323
277
  * jQuery
324
- * ------------------------------------------------------------------------
325
- * add .Tab to jQuery only if jQuery is present
326
278
  */
327
279
 
328
- defineJQueryPlugin(Tab);
280
+ index_js.defineJQueryPlugin(Tab);
329
281
 
330
282
  return Tab;
331
283