@openeuropa/bcl-bootstrap 0.25.1 → 0.26.0

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 (142) hide show
  1. package/bootstrap-icons.svg +1 -1
  2. package/icons/apple.svg +2 -2
  3. package/icons/boombox-fill.svg +2 -2
  4. package/icons/cup-fill.svg +1 -1
  5. package/icons/cup.svg +1 -1
  6. package/js/dist/alert.js +3 -12
  7. package/js/dist/alert.js.map +1 -1
  8. package/js/dist/base-component.js +32 -18
  9. package/js/dist/base-component.js.map +1 -1
  10. package/js/dist/button.js +3 -12
  11. package/js/dist/button.js.map +1 -1
  12. package/js/dist/carousel.js +207 -307
  13. package/js/dist/carousel.js.map +1 -1
  14. package/js/dist/collapse.js +57 -88
  15. package/js/dist/collapse.js.map +1 -1
  16. package/js/dist/dom/data.js +1 -3
  17. package/js/dist/dom/data.js.map +1 -1
  18. package/js/dist/dom/event-handler.js +87 -106
  19. package/js/dist/dom/event-handler.js.map +1 -1
  20. package/js/dist/dom/manipulator.js +21 -25
  21. package/js/dist/dom/manipulator.js.map +1 -1
  22. package/js/dist/dom/selector-engine.js +11 -10
  23. package/js/dist/dom/selector-engine.js.map +1 -1
  24. package/js/dist/dropdown.js +83 -115
  25. package/js/dist/dropdown.js.map +1 -1
  26. package/js/dist/modal.js +95 -152
  27. package/js/dist/modal.js.map +1 -1
  28. package/js/dist/offcanvas.js +75 -58
  29. package/js/dist/offcanvas.js.map +1 -1
  30. package/js/dist/popover.js +29 -56
  31. package/js/dist/popover.js.map +1 -1
  32. package/js/dist/scrollspy.js +176 -125
  33. package/js/dist/scrollspy.js.map +1 -1
  34. package/js/dist/tab.js +207 -92
  35. package/js/dist/tab.js.map +1 -1
  36. package/js/dist/toast.js +23 -37
  37. package/js/dist/toast.js.map +1 -1
  38. package/js/dist/tooltip.js +259 -348
  39. package/js/dist/tooltip.js.map +1 -1
  40. package/js/dist/util/backdrop.js +62 -39
  41. package/js/dist/util/backdrop.js.map +1 -1
  42. package/js/dist/util/component-functions.js +1 -1
  43. package/js/dist/util/component-functions.js.map +1 -1
  44. package/js/dist/util/config.js +75 -0
  45. package/js/dist/util/config.js.map +1 -0
  46. package/js/dist/util/focustrap.js +41 -34
  47. package/js/dist/util/focustrap.js.map +1 -1
  48. package/js/dist/util/index.js +56 -52
  49. package/js/dist/util/index.js.map +1 -1
  50. package/js/dist/util/sanitizer.js +12 -19
  51. package/js/dist/util/sanitizer.js.map +1 -1
  52. package/js/dist/util/scrollbar.js +49 -34
  53. package/js/dist/util/scrollbar.js.map +1 -1
  54. package/js/dist/util/swipe.js +151 -0
  55. package/js/dist/util/swipe.js.map +1 -0
  56. package/js/dist/util/template-factory.js +173 -0
  57. package/js/dist/util/template-factory.js.map +1 -0
  58. package/js/src/alert.js +3 -15
  59. package/js/src/base-component.js +28 -18
  60. package/js/src/button.js +3 -17
  61. package/js/src/carousel.js +203 -319
  62. package/js/src/collapse.js +61 -94
  63. package/js/src/dom/data.js +1 -3
  64. package/js/src/dom/event-handler.js +80 -108
  65. package/js/src/dom/manipulator.js +22 -31
  66. package/js/src/dom/selector-engine.js +10 -19
  67. package/js/src/dropdown.js +84 -138
  68. package/js/src/modal.js +94 -158
  69. package/js/src/offcanvas.js +72 -61
  70. package/js/src/popover.js +31 -62
  71. package/js/src/scrollspy.js +166 -171
  72. package/js/src/tab.js +193 -110
  73. package/js/src/toast.js +19 -41
  74. package/js/src/tooltip.js +259 -371
  75. package/js/src/util/backdrop.js +55 -36
  76. package/js/src/util/component-functions.js +1 -1
  77. package/js/src/util/config.js +66 -0
  78. package/js/src/util/focustrap.js +38 -28
  79. package/js/src/util/index.js +67 -64
  80. package/js/src/util/sanitizer.js +11 -19
  81. package/js/src/util/scrollbar.js +47 -30
  82. package/js/src/util/swipe.js +146 -0
  83. package/js/src/util/template-factory.js +160 -0
  84. package/package.json +4 -4
  85. package/scss/_accordion.scss +52 -24
  86. package/scss/_alert.scss +18 -4
  87. package/scss/_badge.scss +14 -5
  88. package/scss/_breadcrumb.scss +22 -10
  89. package/scss/_button-group.scss +3 -0
  90. package/scss/_buttons.scss +97 -22
  91. package/scss/_card.scss +55 -37
  92. package/scss/_close.scss +1 -1
  93. package/scss/_containers.scss +1 -1
  94. package/scss/_dropdown.scss +83 -75
  95. package/scss/_functions.scss +7 -7
  96. package/scss/_grid.scss +3 -3
  97. package/scss/_helpers.scss +1 -0
  98. package/scss/_list-group.scss +44 -27
  99. package/scss/_maps.scss +54 -0
  100. package/scss/_modal.scss +71 -43
  101. package/scss/_nav.scss +53 -20
  102. package/scss/_navbar.scss +91 -150
  103. package/scss/_offcanvas.scss +119 -59
  104. package/scss/_pagination.scss +66 -21
  105. package/scss/_placeholders.scss +1 -1
  106. package/scss/_popover.scss +90 -52
  107. package/scss/_progress.scss +20 -9
  108. package/scss/_reboot.scss +25 -40
  109. package/scss/_root.scss +40 -21
  110. package/scss/_spinners.scss +38 -22
  111. package/scss/_tables.scss +32 -23
  112. package/scss/_toasts.scss +35 -16
  113. package/scss/_tooltip.scss +61 -56
  114. package/scss/_type.scss +2 -0
  115. package/scss/_utilities.scss +43 -26
  116. package/scss/_variables.scss +113 -121
  117. package/scss/bootstrap-grid.scss +3 -6
  118. package/scss/bootstrap-reboot.scss +3 -7
  119. package/scss/bootstrap-utilities.scss +3 -6
  120. package/scss/bootstrap.scss +4 -6
  121. package/scss/forms/_floating-labels.scss +14 -3
  122. package/scss/forms/_form-check.scss +28 -5
  123. package/scss/forms/_form-control.scss +12 -37
  124. package/scss/forms/_form-select.scss +0 -1
  125. package/scss/forms/_input-group.scss +15 -7
  126. package/scss/helpers/_color-bg.scss +10 -0
  127. package/scss/helpers/_colored-links.scss +2 -2
  128. package/scss/helpers/_position.scss +7 -1
  129. package/scss/helpers/_ratio.scss +2 -2
  130. package/scss/helpers/_vr.scss +1 -1
  131. package/scss/mixins/_alert.scss +7 -3
  132. package/scss/mixins/_banner.scss +9 -0
  133. package/scss/mixins/_breakpoints.scss +8 -8
  134. package/scss/mixins/_buttons.scss +32 -95
  135. package/scss/mixins/_container.scss +4 -2
  136. package/scss/mixins/_forms.scss +8 -0
  137. package/scss/mixins/_gradients.scss +1 -1
  138. package/scss/mixins/_grid.scss +12 -12
  139. package/scss/mixins/_pagination.scss +4 -25
  140. package/scss/mixins/_reset-text.scss +1 -1
  141. package/scss/mixins/_table-variants.scss +12 -9
  142. package/scss/mixins/_utilities.scss +12 -4
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * --------------------------------------------------------------------------
3
- * Bootstrap (v5.1.3): carousel.js
3
+ * Bootstrap (v5.2.0): carousel.js
4
4
  * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
5
5
  * --------------------------------------------------------------------------
6
6
  */
@@ -8,22 +8,20 @@
8
8
  import {
9
9
  defineJQueryPlugin,
10
10
  getElementFromSelector,
11
+ getNextActiveElement,
11
12
  isRTL,
12
13
  isVisible,
13
- getNextActiveElement,
14
14
  reflow,
15
- triggerTransitionEnd,
16
- typeCheckConfig
15
+ triggerTransitionEnd
17
16
  } from './util/index'
18
17
  import EventHandler from './dom/event-handler'
19
18
  import Manipulator from './dom/manipulator'
20
19
  import SelectorEngine from './dom/selector-engine'
20
+ import Swipe from './util/swipe'
21
21
  import BaseComponent from './base-component'
22
22
 
23
23
  /**
24
- * ------------------------------------------------------------------------
25
24
  * Constants
26
- * ------------------------------------------------------------------------
27
25
  */
28
26
 
29
27
  const NAME = 'carousel'
@@ -34,46 +32,17 @@ const DATA_API_KEY = '.data-api'
34
32
  const ARROW_LEFT_KEY = 'ArrowLeft'
35
33
  const ARROW_RIGHT_KEY = 'ArrowRight'
36
34
  const TOUCHEVENT_COMPAT_WAIT = 500 // Time for mouse compat events to fire after touch
37
- const SWIPE_THRESHOLD = 40
38
-
39
- const Default = {
40
- interval: 5000,
41
- keyboard: true,
42
- slide: false,
43
- pause: 'hover',
44
- wrap: true,
45
- touch: true
46
- }
47
-
48
- const DefaultType = {
49
- interval: '(number|boolean)',
50
- keyboard: 'boolean',
51
- slide: '(boolean|string)',
52
- pause: '(string|boolean)',
53
- wrap: 'boolean',
54
- touch: 'boolean'
55
- }
56
35
 
57
36
  const ORDER_NEXT = 'next'
58
37
  const ORDER_PREV = 'prev'
59
38
  const DIRECTION_LEFT = 'left'
60
39
  const DIRECTION_RIGHT = 'right'
61
40
 
62
- const KEY_TO_DIRECTION = {
63
- [ARROW_LEFT_KEY]: DIRECTION_RIGHT,
64
- [ARROW_RIGHT_KEY]: DIRECTION_LEFT
65
- }
66
-
67
41
  const EVENT_SLIDE = `slide${EVENT_KEY}`
68
42
  const EVENT_SLID = `slid${EVENT_KEY}`
69
43
  const EVENT_KEYDOWN = `keydown${EVENT_KEY}`
70
44
  const EVENT_MOUSEENTER = `mouseenter${EVENT_KEY}`
71
45
  const EVENT_MOUSELEAVE = `mouseleave${EVENT_KEY}`
72
- const EVENT_TOUCHSTART = `touchstart${EVENT_KEY}`
73
- const EVENT_TOUCHMOVE = `touchmove${EVENT_KEY}`
74
- const EVENT_TOUCHEND = `touchend${EVENT_KEY}`
75
- const EVENT_POINTERDOWN = `pointerdown${EVENT_KEY}`
76
- const EVENT_POINTERUP = `pointerup${EVENT_KEY}`
77
46
  const EVENT_DRAG_START = `dragstart${EVENT_KEY}`
78
47
  const EVENT_LOAD_DATA_API = `load${EVENT_KEY}${DATA_API_KEY}`
79
48
  const EVENT_CLICK_DATA_API = `click${EVENT_KEY}${DATA_API_KEY}`
@@ -85,64 +54,80 @@ const CLASS_NAME_END = 'carousel-item-end'
85
54
  const CLASS_NAME_START = 'carousel-item-start'
86
55
  const CLASS_NAME_NEXT = 'carousel-item-next'
87
56
  const CLASS_NAME_PREV = 'carousel-item-prev'
88
- const CLASS_NAME_POINTER_EVENT = 'pointer-event'
89
57
 
90
58
  const SELECTOR_ACTIVE = '.active'
91
- const SELECTOR_ACTIVE_ITEM = '.active.carousel-item'
92
59
  const SELECTOR_ITEM = '.carousel-item'
60
+ const SELECTOR_ACTIVE_ITEM = SELECTOR_ACTIVE + SELECTOR_ITEM
93
61
  const SELECTOR_ITEM_IMG = '.carousel-item img'
94
- const SELECTOR_NEXT_PREV = '.carousel-item-next, .carousel-item-prev'
95
62
  const SELECTOR_INDICATORS = '.carousel-indicators'
96
- const SELECTOR_INDICATOR = '[data-bs-target]'
97
63
  const SELECTOR_DATA_SLIDE = '[data-bs-slide], [data-bs-slide-to]'
98
64
  const SELECTOR_DATA_RIDE = '[data-bs-ride="carousel"]'
99
65
 
100
- const POINTER_TYPE_TOUCH = 'touch'
101
- const POINTER_TYPE_PEN = 'pen'
66
+ const KEY_TO_DIRECTION = {
67
+ [ARROW_LEFT_KEY]: DIRECTION_RIGHT,
68
+ [ARROW_RIGHT_KEY]: DIRECTION_LEFT
69
+ }
70
+
71
+ const Default = {
72
+ interval: 5000,
73
+ keyboard: true,
74
+ pause: 'hover',
75
+ ride: false,
76
+ touch: true,
77
+ wrap: true
78
+ }
79
+
80
+ const DefaultType = {
81
+ interval: '(number|boolean)', // TODO:v6 remove boolean support
82
+ keyboard: 'boolean',
83
+ pause: '(string|boolean)',
84
+ ride: '(boolean|string)',
85
+ touch: 'boolean',
86
+ wrap: 'boolean'
87
+ }
102
88
 
103
89
  /**
104
- * ------------------------------------------------------------------------
105
- * Class Definition
106
- * ------------------------------------------------------------------------
90
+ * Class definition
107
91
  */
92
+
108
93
  class Carousel extends BaseComponent {
109
94
  constructor(element, config) {
110
- super(element)
95
+ super(element, config)
111
96
 
112
- this._items = null
113
97
  this._interval = null
114
98
  this._activeElement = null
115
- this._isPaused = false
116
99
  this._isSliding = false
117
100
  this.touchTimeout = null
118
- this.touchStartX = 0
119
- this.touchDeltaX = 0
101
+ this._swipeHelper = null
120
102
 
121
- this._config = this._getConfig(config)
122
103
  this._indicatorsElement = SelectorEngine.findOne(SELECTOR_INDICATORS, this._element)
123
- this._touchSupported = 'ontouchstart' in document.documentElement || navigator.maxTouchPoints > 0
124
- this._pointerEvent = Boolean(window.PointerEvent)
125
-
126
104
  this._addEventListeners()
105
+
106
+ if (this._config.ride === CLASS_NAME_CAROUSEL) {
107
+ this.cycle()
108
+ }
127
109
  }
128
110
 
129
111
  // Getters
130
-
131
112
  static get Default() {
132
113
  return Default
133
114
  }
134
115
 
116
+ static get DefaultType() {
117
+ return DefaultType
118
+ }
119
+
135
120
  static get NAME() {
136
121
  return NAME
137
122
  }
138
123
 
139
124
  // Public
140
-
141
125
  next() {
142
126
  this._slide(ORDER_NEXT)
143
127
  }
144
128
 
145
129
  nextWhenVisible() {
130
+ // FIXME TODO use `document.visibilityState`
146
131
  // Don't call next when the page isn't visible
147
132
  // or the carousel or its parent isn't visible
148
133
  if (!document.hidden && isVisible(this._element)) {
@@ -154,45 +139,37 @@ class Carousel extends BaseComponent {
154
139
  this._slide(ORDER_PREV)
155
140
  }
156
141
 
157
- pause(event) {
158
- if (!event) {
159
- this._isPaused = true
160
- }
161
-
162
- if (SelectorEngine.findOne(SELECTOR_NEXT_PREV, this._element)) {
142
+ pause() {
143
+ if (this._isSliding) {
163
144
  triggerTransitionEnd(this._element)
164
- this.cycle(true)
165
145
  }
166
146
 
167
- clearInterval(this._interval)
168
- this._interval = null
147
+ this._clearInterval()
169
148
  }
170
149
 
171
- cycle(event) {
172
- if (!event) {
173
- this._isPaused = false
174
- }
150
+ cycle() {
151
+ this._clearInterval()
152
+ this._updateInterval()
175
153
 
176
- if (this._interval) {
177
- clearInterval(this._interval)
178
- this._interval = null
179
- }
154
+ this._interval = setInterval(() => this.nextWhenVisible(), this._config.interval)
155
+ }
180
156
 
181
- if (this._config && this._config.interval && !this._isPaused) {
182
- this._updateInterval()
157
+ _maybeEnableCycle() {
158
+ if (!this._config.ride) {
159
+ return
160
+ }
183
161
 
184
- this._interval = setInterval(
185
- (document.visibilityState ? this.nextWhenVisible : this.next).bind(this),
186
- this._config.interval
187
- )
162
+ if (this._isSliding) {
163
+ EventHandler.one(this._element, EVENT_SLID, () => this.cycle())
164
+ return
188
165
  }
166
+
167
+ this.cycle()
189
168
  }
190
169
 
191
170
  to(index) {
192
- this._activeElement = SelectorEngine.findOne(SELECTOR_ACTIVE_ITEM, this._element)
193
- const activeIndex = this._getItemIndex(this._activeElement)
194
-
195
- if (index > this._items.length - 1 || index < 0) {
171
+ const items = this._getItems()
172
+ if (index > items.length - 1 || index < 0) {
196
173
  return
197
174
  }
198
175
 
@@ -201,47 +178,28 @@ class Carousel extends BaseComponent {
201
178
  return
202
179
  }
203
180
 
181
+ const activeIndex = this._getItemIndex(this._getActive())
204
182
  if (activeIndex === index) {
205
- this.pause()
206
- this.cycle()
207
183
  return
208
184
  }
209
185
 
210
- const order = index > activeIndex ?
211
- ORDER_NEXT :
212
- ORDER_PREV
186
+ const order = index > activeIndex ? ORDER_NEXT : ORDER_PREV
213
187
 
214
- this._slide(order, this._items[index])
188
+ this._slide(order, items[index])
215
189
  }
216
190
 
217
- // Private
218
-
219
- _getConfig(config) {
220
- config = {
221
- ...Default,
222
- ...Manipulator.getDataAttributes(this._element),
223
- ...(typeof config === 'object' ? config : {})
191
+ dispose() {
192
+ if (this._swipeHelper) {
193
+ this._swipeHelper.dispose()
224
194
  }
225
- typeCheckConfig(NAME, config, DefaultType)
226
- return config
227
- }
228
195
 
229
- _handleSwipe() {
230
- const absDeltax = Math.abs(this.touchDeltaX)
231
-
232
- if (absDeltax <= SWIPE_THRESHOLD) {
233
- return
234
- }
235
-
236
- const direction = absDeltax / this.touchDeltaX
237
-
238
- this.touchDeltaX = 0
239
-
240
- if (!direction) {
241
- return
242
- }
196
+ super.dispose()
197
+ }
243
198
 
244
- this._slide(direction > 0 ? DIRECTION_RIGHT : DIRECTION_LEFT)
199
+ // Private
200
+ _configAfterMerge(config) {
201
+ config.defaultInterval = config.interval
202
+ return config
245
203
  }
246
204
 
247
205
  _addEventListeners() {
@@ -250,74 +208,48 @@ class Carousel extends BaseComponent {
250
208
  }
251
209
 
252
210
  if (this._config.pause === 'hover') {
253
- EventHandler.on(this._element, EVENT_MOUSEENTER, event => this.pause(event))
254
- EventHandler.on(this._element, EVENT_MOUSELEAVE, event => this.cycle(event))
211
+ EventHandler.on(this._element, EVENT_MOUSEENTER, () => this.pause())
212
+ EventHandler.on(this._element, EVENT_MOUSELEAVE, () => this._maybeEnableCycle())
255
213
  }
256
214
 
257
- if (this._config.touch && this._touchSupported) {
215
+ if (this._config.touch && Swipe.isSupported()) {
258
216
  this._addTouchEventListeners()
259
217
  }
260
218
  }
261
219
 
262
220
  _addTouchEventListeners() {
263
- const hasPointerPenTouch = event => {
264
- return this._pointerEvent &&
265
- (event.pointerType === POINTER_TYPE_PEN || event.pointerType === POINTER_TYPE_TOUCH)
221
+ for (const img of SelectorEngine.find(SELECTOR_ITEM_IMG, this._element)) {
222
+ EventHandler.on(img, EVENT_DRAG_START, event => event.preventDefault())
266
223
  }
267
224
 
268
- const start = event => {
269
- if (hasPointerPenTouch(event)) {
270
- this.touchStartX = event.clientX
271
- } else if (!this._pointerEvent) {
272
- this.touchStartX = event.touches[0].clientX
225
+ const endCallBack = () => {
226
+ if (this._config.pause !== 'hover') {
227
+ return
273
228
  }
274
- }
275
229
 
276
- const move = event => {
277
- // ensure swiping with one touch and not pinching
278
- this.touchDeltaX = event.touches && event.touches.length > 1 ?
279
- 0 :
280
- event.touches[0].clientX - this.touchStartX
281
- }
230
+ // If it's a touch-enabled device, mouseenter/leave are fired as
231
+ // part of the mouse compatibility events on first tap - the carousel
232
+ // would stop cycling until user tapped out of it;
233
+ // here, we listen for touchend, explicitly pause the carousel
234
+ // (as if it's the second time we tap on it, mouseenter compat event
235
+ // is NOT fired) and after a timeout (to allow for mouse compatibility
236
+ // events to fire) we explicitly restart cycling
282
237
 
283
- const end = event => {
284
- if (hasPointerPenTouch(event)) {
285
- this.touchDeltaX = event.clientX - this.touchStartX
238
+ this.pause()
239
+ if (this.touchTimeout) {
240
+ clearTimeout(this.touchTimeout)
286
241
  }
287
242
 
288
- this._handleSwipe()
289
- if (this._config.pause === 'hover') {
290
- // If it's a touch-enabled device, mouseenter/leave are fired as
291
- // part of the mouse compatibility events on first tap - the carousel
292
- // would stop cycling until user tapped out of it;
293
- // here, we listen for touchend, explicitly pause the carousel
294
- // (as if it's the second time we tap on it, mouseenter compat event
295
- // is NOT fired) and after a timeout (to allow for mouse compatibility
296
- // events to fire) we explicitly restart cycling
297
-
298
- this.pause()
299
- if (this.touchTimeout) {
300
- clearTimeout(this.touchTimeout)
301
- }
302
-
303
- this.touchTimeout = setTimeout(event => this.cycle(event), TOUCHEVENT_COMPAT_WAIT + this._config.interval)
304
- }
243
+ this.touchTimeout = setTimeout(() => this._maybeEnableCycle(), TOUCHEVENT_COMPAT_WAIT + this._config.interval)
305
244
  }
306
245
 
307
- SelectorEngine.find(SELECTOR_ITEM_IMG, this._element).forEach(itemImg => {
308
- EventHandler.on(itemImg, EVENT_DRAG_START, event => event.preventDefault())
309
- })
310
-
311
- if (this._pointerEvent) {
312
- EventHandler.on(this._element, EVENT_POINTERDOWN, event => start(event))
313
- EventHandler.on(this._element, EVENT_POINTERUP, event => end(event))
314
-
315
- this._element.classList.add(CLASS_NAME_POINTER_EVENT)
316
- } else {
317
- EventHandler.on(this._element, EVENT_TOUCHSTART, event => start(event))
318
- EventHandler.on(this._element, EVENT_TOUCHMOVE, event => move(event))
319
- EventHandler.on(this._element, EVENT_TOUCHEND, event => end(event))
246
+ const swipeConfig = {
247
+ leftCallback: () => this._slide(this._directionToOrder(DIRECTION_LEFT)),
248
+ rightCallback: () => this._slide(this._directionToOrder(DIRECTION_RIGHT)),
249
+ endCallback: endCallBack
320
250
  }
251
+
252
+ this._swipeHelper = new Swipe(this._element, swipeConfig)
321
253
  }
322
254
 
323
255
  _keydown(event) {
@@ -328,56 +260,34 @@ class Carousel extends BaseComponent {
328
260
  const direction = KEY_TO_DIRECTION[event.key]
329
261
  if (direction) {
330
262
  event.preventDefault()
331
- this._slide(direction)
263
+ this._slide(this._directionToOrder(direction))
332
264
  }
333
265
  }
334
266
 
335
267
  _getItemIndex(element) {
336
- this._items = element && element.parentNode ?
337
- SelectorEngine.find(SELECTOR_ITEM, element.parentNode) :
338
- []
339
-
340
- return this._items.indexOf(element)
341
- }
342
-
343
- _getItemByOrder(order, activeElement) {
344
- const isNext = order === ORDER_NEXT
345
- return getNextActiveElement(this._items, activeElement, isNext, this._config.wrap)
268
+ return this._getItems().indexOf(element)
346
269
  }
347
270
 
348
- _triggerSlideEvent(relatedTarget, eventDirectionName) {
349
- const targetIndex = this._getItemIndex(relatedTarget)
350
- const fromIndex = this._getItemIndex(SelectorEngine.findOne(SELECTOR_ACTIVE_ITEM, this._element))
351
-
352
- return EventHandler.trigger(this._element, EVENT_SLIDE, {
353
- relatedTarget,
354
- direction: eventDirectionName,
355
- from: fromIndex,
356
- to: targetIndex
357
- })
358
- }
271
+ _setActiveIndicatorElement(index) {
272
+ if (!this._indicatorsElement) {
273
+ return
274
+ }
359
275
 
360
- _setActiveIndicatorElement(element) {
361
- if (this._indicatorsElement) {
362
- const activeIndicator = SelectorEngine.findOne(SELECTOR_ACTIVE, this._indicatorsElement)
276
+ const activeIndicator = SelectorEngine.findOne(SELECTOR_ACTIVE, this._indicatorsElement)
363
277
 
364
- activeIndicator.classList.remove(CLASS_NAME_ACTIVE)
365
- activeIndicator.removeAttribute('aria-current')
278
+ activeIndicator.classList.remove(CLASS_NAME_ACTIVE)
279
+ activeIndicator.removeAttribute('aria-current')
366
280
 
367
- const indicators = SelectorEngine.find(SELECTOR_INDICATOR, this._indicatorsElement)
281
+ const newActiveIndicator = SelectorEngine.findOne(`[data-bs-slide-to="${index}"]`, this._indicatorsElement)
368
282
 
369
- for (let i = 0; i < indicators.length; i++) {
370
- if (Number.parseInt(indicators[i].getAttribute('data-bs-slide-to'), 10) === this._getItemIndex(element)) {
371
- indicators[i].classList.add(CLASS_NAME_ACTIVE)
372
- indicators[i].setAttribute('aria-current', 'true')
373
- break
374
- }
375
- }
283
+ if (newActiveIndicator) {
284
+ newActiveIndicator.classList.add(CLASS_NAME_ACTIVE)
285
+ newActiveIndicator.setAttribute('aria-current', 'true')
376
286
  }
377
287
  }
378
288
 
379
289
  _updateInterval() {
380
- const element = this._activeElement || SelectorEngine.findOne(SELECTOR_ACTIVE_ITEM, this._element)
290
+ const element = this._activeElement || this._getActive()
381
291
 
382
292
  if (!element) {
383
293
  return
@@ -385,103 +295,101 @@ class Carousel extends BaseComponent {
385
295
 
386
296
  const elementInterval = Number.parseInt(element.getAttribute('data-bs-interval'), 10)
387
297
 
388
- if (elementInterval) {
389
- this._config.defaultInterval = this._config.defaultInterval || this._config.interval
390
- this._config.interval = elementInterval
391
- } else {
392
- this._config.interval = this._config.defaultInterval || this._config.interval
393
- }
298
+ this._config.interval = elementInterval || this._config.defaultInterval
394
299
  }
395
300
 
396
- _slide(directionOrOrder, element) {
397
- const order = this._directionToOrder(directionOrOrder)
398
- const activeElement = SelectorEngine.findOne(SELECTOR_ACTIVE_ITEM, this._element)
399
- const activeElementIndex = this._getItemIndex(activeElement)
400
- const nextElement = element || this._getItemByOrder(order, activeElement)
401
-
402
- const nextElementIndex = this._getItemIndex(nextElement)
403
- const isCycling = Boolean(this._interval)
301
+ _slide(order, element = null) {
302
+ if (this._isSliding) {
303
+ return
304
+ }
404
305
 
306
+ const activeElement = this._getActive()
405
307
  const isNext = order === ORDER_NEXT
406
- const directionalClassName = isNext ? CLASS_NAME_START : CLASS_NAME_END
407
- const orderClassName = isNext ? CLASS_NAME_NEXT : CLASS_NAME_PREV
408
- const eventDirectionName = this._orderToDirection(order)
308
+ const nextElement = element || getNextActiveElement(this._getItems(), activeElement, isNext, this._config.wrap)
409
309
 
410
- if (nextElement && nextElement.classList.contains(CLASS_NAME_ACTIVE)) {
411
- this._isSliding = false
310
+ if (nextElement === activeElement) {
412
311
  return
413
312
  }
414
313
 
415
- if (this._isSliding) {
416
- return
314
+ const nextElementIndex = this._getItemIndex(nextElement)
315
+
316
+ const triggerEvent = eventName => {
317
+ return EventHandler.trigger(this._element, eventName, {
318
+ relatedTarget: nextElement,
319
+ direction: this._orderToDirection(order),
320
+ from: this._getItemIndex(activeElement),
321
+ to: nextElementIndex
322
+ })
417
323
  }
418
324
 
419
- const slideEvent = this._triggerSlideEvent(nextElement, eventDirectionName)
325
+ const slideEvent = triggerEvent(EVENT_SLIDE)
326
+
420
327
  if (slideEvent.defaultPrevented) {
421
328
  return
422
329
  }
423
330
 
424
331
  if (!activeElement || !nextElement) {
425
332
  // Some weirdness is happening, so we bail
333
+ // todo: change tests that use empty divs to avoid this check
426
334
  return
427
335
  }
428
336
 
429
- this._isSliding = true
337
+ const isCycling = Boolean(this._interval)
338
+ this.pause()
430
339
 
431
- if (isCycling) {
432
- this.pause()
433
- }
340
+ this._isSliding = true
434
341
 
435
- this._setActiveIndicatorElement(nextElement)
342
+ this._setActiveIndicatorElement(nextElementIndex)
436
343
  this._activeElement = nextElement
437
344
 
438
- const triggerSlidEvent = () => {
439
- EventHandler.trigger(this._element, EVENT_SLID, {
440
- relatedTarget: nextElement,
441
- direction: eventDirectionName,
442
- from: activeElementIndex,
443
- to: nextElementIndex
444
- })
445
- }
446
-
447
- if (this._element.classList.contains(CLASS_NAME_SLIDE)) {
448
- nextElement.classList.add(orderClassName)
449
-
450
- reflow(nextElement)
451
-
452
- activeElement.classList.add(directionalClassName)
453
- nextElement.classList.add(directionalClassName)
454
-
455
- const completeCallBack = () => {
456
- nextElement.classList.remove(directionalClassName, orderClassName)
457
- nextElement.classList.add(CLASS_NAME_ACTIVE)
345
+ const directionalClassName = isNext ? CLASS_NAME_START : CLASS_NAME_END
346
+ const orderClassName = isNext ? CLASS_NAME_NEXT : CLASS_NAME_PREV
458
347
 
459
- activeElement.classList.remove(CLASS_NAME_ACTIVE, orderClassName, directionalClassName)
348
+ nextElement.classList.add(orderClassName)
460
349
 
461
- this._isSliding = false
350
+ reflow(nextElement)
462
351
 
463
- setTimeout(triggerSlidEvent, 0)
464
- }
352
+ activeElement.classList.add(directionalClassName)
353
+ nextElement.classList.add(directionalClassName)
465
354
 
466
- this._queueCallback(completeCallBack, activeElement, true)
467
- } else {
468
- activeElement.classList.remove(CLASS_NAME_ACTIVE)
355
+ const completeCallBack = () => {
356
+ nextElement.classList.remove(directionalClassName, orderClassName)
469
357
  nextElement.classList.add(CLASS_NAME_ACTIVE)
470
358
 
359
+ activeElement.classList.remove(CLASS_NAME_ACTIVE, orderClassName, directionalClassName)
360
+
471
361
  this._isSliding = false
472
- triggerSlidEvent()
362
+
363
+ triggerEvent(EVENT_SLID)
473
364
  }
474
365
 
366
+ this._queueCallback(completeCallBack, activeElement, this._isAnimated())
367
+
475
368
  if (isCycling) {
476
369
  this.cycle()
477
370
  }
478
371
  }
479
372
 
480
- _directionToOrder(direction) {
481
- if (![DIRECTION_RIGHT, DIRECTION_LEFT].includes(direction)) {
482
- return direction
373
+ _isAnimated() {
374
+ return this._element.classList.contains(CLASS_NAME_SLIDE)
375
+ }
376
+
377
+ _getActive() {
378
+ return SelectorEngine.findOne(SELECTOR_ACTIVE_ITEM, this._element)
379
+ }
380
+
381
+ _getItems() {
382
+ return SelectorEngine.find(SELECTOR_ITEM, this._element)
383
+ }
384
+
385
+ _clearInterval() {
386
+ if (this._interval) {
387
+ clearInterval(this._interval)
388
+ this._interval = null
483
389
  }
390
+ }
484
391
 
392
+ _directionToOrder(direction) {
485
393
  if (isRTL()) {
486
394
  return direction === DIRECTION_LEFT ? ORDER_PREV : ORDER_NEXT
487
395
  }
@@ -490,10 +398,6 @@ class Carousel extends BaseComponent {
490
398
  }
491
399
 
492
400
  _orderToDirection(order) {
493
- if (![ORDER_NEXT, ORDER_PREV].includes(order)) {
494
- return order
495
- }
496
-
497
401
  if (isRTL()) {
498
402
  return order === ORDER_PREV ? DIRECTION_LEFT : DIRECTION_RIGHT
499
403
  }
@@ -502,88 +406,68 @@ class Carousel extends BaseComponent {
502
406
  }
503
407
 
504
408
  // Static
409
+ static jQueryInterface(config) {
410
+ return this.each(function () {
411
+ const data = Carousel.getOrCreateInstance(this, config)
505
412
 
506
- static carouselInterface(element, config) {
507
- const data = Carousel.getOrCreateInstance(element, config)
508
-
509
- let { _config } = data
510
- if (typeof config === 'object') {
511
- _config = {
512
- ..._config,
513
- ...config
413
+ if (typeof config === 'number') {
414
+ data.to(config)
415
+ return
514
416
  }
515
- }
516
417
 
517
- const action = typeof config === 'string' ? config : _config.slide
418
+ if (typeof config === 'string') {
419
+ if (data[config] === undefined || config.startsWith('_') || config === 'constructor') {
420
+ throw new TypeError(`No method named "${config}"`)
421
+ }
518
422
 
519
- if (typeof config === 'number') {
520
- data.to(config)
521
- } else if (typeof action === 'string') {
522
- if (typeof data[action] === 'undefined') {
523
- throw new TypeError(`No method named "${action}"`)
423
+ data[config]()
524
424
  }
525
-
526
- data[action]()
527
- } else if (_config.interval && _config.ride) {
528
- data.pause()
529
- data.cycle()
530
- }
531
- }
532
-
533
- static jQueryInterface(config) {
534
- return this.each(function () {
535
- Carousel.carouselInterface(this, config)
536
425
  })
537
426
  }
427
+ }
538
428
 
539
- static dataApiClickHandler(event) {
540
- const target = getElementFromSelector(this)
541
-
542
- if (!target || !target.classList.contains(CLASS_NAME_CAROUSEL)) {
543
- return
544
- }
429
+ /**
430
+ * Data API implementation
431
+ */
545
432
 
546
- const config = {
547
- ...Manipulator.getDataAttributes(target),
548
- ...Manipulator.getDataAttributes(this)
549
- }
550
- const slideIndex = this.getAttribute('data-bs-slide-to')
433
+ EventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_SLIDE, function (event) {
434
+ const target = getElementFromSelector(this)
551
435
 
552
- if (slideIndex) {
553
- config.interval = false
554
- }
436
+ if (!target || !target.classList.contains(CLASS_NAME_CAROUSEL)) {
437
+ return
438
+ }
555
439
 
556
- Carousel.carouselInterface(target, config)
440
+ event.preventDefault()
557
441
 
558
- if (slideIndex) {
559
- Carousel.getInstance(target).to(slideIndex)
560
- }
442
+ const carousel = Carousel.getOrCreateInstance(target)
443
+ const slideIndex = this.getAttribute('data-bs-slide-to')
561
444
 
562
- event.preventDefault()
445
+ if (slideIndex) {
446
+ carousel.to(slideIndex)
447
+ carousel._maybeEnableCycle()
448
+ return
563
449
  }
564
- }
565
450
 
566
- /**
567
- * ------------------------------------------------------------------------
568
- * Data Api implementation
569
- * ------------------------------------------------------------------------
570
- */
451
+ if (Manipulator.getDataAttribute(this, 'slide') === 'next') {
452
+ carousel.next()
453
+ carousel._maybeEnableCycle()
454
+ return
455
+ }
571
456
 
572
- EventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_SLIDE, Carousel.dataApiClickHandler)
457
+ carousel.prev()
458
+ carousel._maybeEnableCycle()
459
+ })
573
460
 
574
461
  EventHandler.on(window, EVENT_LOAD_DATA_API, () => {
575
462
  const carousels = SelectorEngine.find(SELECTOR_DATA_RIDE)
576
463
 
577
- for (let i = 0, len = carousels.length; i < len; i++) {
578
- Carousel.carouselInterface(carousels[i], Carousel.getInstance(carousels[i]))
464
+ for (const carousel of carousels) {
465
+ Carousel.getOrCreateInstance(carousel)
579
466
  }
580
467
  })
581
468
 
582
469
  /**
583
- * ------------------------------------------------------------------------
584
470
  * jQuery
585
- * ------------------------------------------------------------------------
586
- * add .Carousel to jQuery only if jQuery is present
587
471
  */
588
472
 
589
473
  defineJQueryPlugin(Carousel)