9
import Util from './util'
15
const NAME = 'carousel'
16
const VERSION = '4.6.1'
17
const DATA_KEY = 'bs.carousel'
18
const EVENT_KEY = `.${DATA_KEY}`
19
const DATA_API_KEY = '.data-api'
20
const JQUERY_NO_CONFLICT = $.fn[NAME]
21
const ARROW_LEFT_KEYCODE = 37
22
const ARROW_RIGHT_KEYCODE = 39
23
const TOUCHEVENT_COMPAT_WAIT = 500
24
const SWIPE_THRESHOLD = 40
26
const CLASS_NAME_CAROUSEL = 'carousel'
27
const CLASS_NAME_ACTIVE = 'active'
28
const CLASS_NAME_SLIDE = 'slide'
29
const CLASS_NAME_RIGHT = 'carousel-item-right'
30
const CLASS_NAME_LEFT = 'carousel-item-left'
31
const CLASS_NAME_NEXT = 'carousel-item-next'
32
const CLASS_NAME_PREV = 'carousel-item-prev'
33
const CLASS_NAME_POINTER_EVENT = 'pointer-event'
35
const DIRECTION_NEXT = 'next'
36
const DIRECTION_PREV = 'prev'
37
const DIRECTION_LEFT = 'left'
38
const DIRECTION_RIGHT = 'right'
40
const EVENT_SLIDE = `slide${EVENT_KEY}`
41
const EVENT_SLID = `slid${EVENT_KEY}`
42
const EVENT_KEYDOWN = `keydown${EVENT_KEY}`
43
const EVENT_MOUSEENTER = `mouseenter${EVENT_KEY}`
44
const EVENT_MOUSELEAVE = `mouseleave${EVENT_KEY}`
45
const EVENT_TOUCHSTART = `touchstart${EVENT_KEY}`
46
const EVENT_TOUCHMOVE = `touchmove${EVENT_KEY}`
47
const EVENT_TOUCHEND = `touchend${EVENT_KEY}`
48
const EVENT_POINTERDOWN = `pointerdown${EVENT_KEY}`
49
const EVENT_POINTERUP = `pointerup${EVENT_KEY}`
50
const EVENT_DRAG_START = `dragstart${EVENT_KEY}`
51
const EVENT_LOAD_DATA_API = `load${EVENT_KEY}${DATA_API_KEY}`
52
const EVENT_CLICK_DATA_API = `click${EVENT_KEY}${DATA_API_KEY}`
54
const SELECTOR_ACTIVE = '.active'
55
const SELECTOR_ACTIVE_ITEM = '.active.carousel-item'
56
const SELECTOR_ITEM = '.carousel-item'
57
const SELECTOR_ITEM_IMG = '.carousel-item img'
58
const SELECTOR_NEXT_PREV = '.carousel-item-next, .carousel-item-prev'
59
const SELECTOR_INDICATORS = '.carousel-indicators'
60
const SELECTOR_DATA_SLIDE = '[data-slide], [data-slide-to]'
61
const SELECTOR_DATA_RIDE = '[data-ride="carousel"]'
73
interval: '(number|boolean)',
75
slide: '(boolean|string)',
76
pause: '(string|boolean)',
91
constructor(element, config) {
94
this._activeElement = null
95
this._isPaused = false
96
this._isSliding = false
97
this.touchTimeout = null
101
this._config = this._getConfig(config)
102
this._element = element
103
this._indicatorsElement = this._element.querySelector(SELECTOR_INDICATORS)
104
this._touchSupported = 'ontouchstart' in document.documentElement || navigator.maxTouchPoints > 0
105
this._pointerEvent = Boolean(window.PointerEvent || window.MSPointerEvent)
107
this._addEventListeners()
111
static get VERSION() {
115
static get Default() {
121
if (!this._isSliding) {
122
this._slide(DIRECTION_NEXT)
127
const $element = $(this._element)
130
if (!document.hidden &&
131
($element.is(':visible') && $element.css('visibility') !== 'hidden')) {
137
if (!this._isSliding) {
138
this._slide(DIRECTION_PREV)
144
this._isPaused = true
147
if (this._element.querySelector(SELECTOR_NEXT_PREV)) {
148
Util.triggerTransitionEnd(this._element)
152
clearInterval(this._interval)
153
this._interval = null
158
this._isPaused = false
161
if (this._interval) {
162
clearInterval(this._interval)
163
this._interval = null
166
if (this._config.interval && !this._isPaused) {
167
this._updateInterval()
169
this._interval = setInterval(
170
(document.visibilityState ? this.nextWhenVisible : this.next).bind(this),
171
this._config.interval
177
this._activeElement = this._element.querySelector(SELECTOR_ACTIVE_ITEM)
179
const activeIndex = this._getItemIndex(this._activeElement)
181
if (index > this._items.length - 1 || index < 0) {
185
if (this._isSliding) {
186
$(this._element).one(EVENT_SLID, () => this.to(index))
190
if (activeIndex === index) {
196
const direction = index > activeIndex ?
200
this._slide(direction, this._items[index])
204
$(this._element).off(EVENT_KEY)
205
$.removeData(this._element, DATA_KEY)
210
this._interval = null
211
this._isPaused = null
212
this._isSliding = null
213
this._activeElement = null
214
this._indicatorsElement = null
223
Util.typeCheckConfig(NAME, config, DefaultType)
228
const absDeltax = Math.abs(this.touchDeltaX)
230
if (absDeltax <= SWIPE_THRESHOLD) {
234
const direction = absDeltax / this.touchDeltaX
249
_addEventListeners() {
250
if (this._config.keyboard) {
251
$(this._element).on(EVENT_KEYDOWN, event => this._keydown(event))
254
if (this._config.pause === 'hover') {
256
.on(EVENT_MOUSEENTER, event => this.pause(event))
257
.on(EVENT_MOUSELEAVE, event => this.cycle(event))
260
if (this._config.touch) {
261
this._addTouchEventListeners()
265
_addTouchEventListeners() {
266
if (!this._touchSupported) {
270
const start = event => {
271
if (this._pointerEvent && PointerType[event.originalEvent.pointerType.toUpperCase()]) {
272
this.touchStartX = event.originalEvent.clientX
273
} else if (!this._pointerEvent) {
274
this.touchStartX = event.originalEvent.touches[0].clientX
278
const move = event => {
280
this.touchDeltaX = event.originalEvent.touches && event.originalEvent.touches.length > 1 ?
282
event.originalEvent.touches[0].clientX - this.touchStartX
285
const end = event => {
286
if (this._pointerEvent && PointerType[event.originalEvent.pointerType.toUpperCase()]) {
287
this.touchDeltaX = event.originalEvent.clientX - this.touchStartX
291
if (this._config.pause === 'hover') {
301
if (this.touchTimeout) {
302
clearTimeout(this.touchTimeout)
305
this.touchTimeout = setTimeout(event => this.cycle(event), TOUCHEVENT_COMPAT_WAIT + this._config.interval)
309
$(this._element.querySelectorAll(SELECTOR_ITEM_IMG))
310
.on(EVENT_DRAG_START, e => e.preventDefault())
312
if (this._pointerEvent) {
313
$(this._element).on(EVENT_POINTERDOWN, event => start(event))
314
$(this._element).on(EVENT_POINTERUP, event => end(event))
316
this._element.classList.add(CLASS_NAME_POINTER_EVENT)
318
$(this._element).on(EVENT_TOUCHSTART, event => start(event))
319
$(this._element).on(EVENT_TOUCHMOVE, event => move(event))
320
$(this._element).on(EVENT_TOUCHEND, event => end(event))
325
if (/input|textarea/i.test(event.target.tagName)) {
329
switch (event.which) {
330
case ARROW_LEFT_KEYCODE:
331
event.preventDefault()
334
case ARROW_RIGHT_KEYCODE:
335
event.preventDefault()
342
_getItemIndex(element) {
343
this._items = element && element.parentNode ?
344
[].slice.call(element.parentNode.querySelectorAll(SELECTOR_ITEM)) :
346
return this._items.indexOf(element)
349
_getItemByDirection(direction, activeElement) {
350
const isNextDirection = direction === DIRECTION_NEXT
351
const isPrevDirection = direction === DIRECTION_PREV
352
const activeIndex = this._getItemIndex(activeElement)
353
const lastItemIndex = this._items.length - 1
354
const isGoingToWrap = isPrevDirection && activeIndex === 0 ||
355
isNextDirection && activeIndex === lastItemIndex
357
if (isGoingToWrap && !this._config.wrap) {
361
const delta = direction === DIRECTION_PREV ? -1 : 1
362
const itemIndex = (activeIndex + delta) % this._items.length
364
return itemIndex === -1 ?
365
this._items[this._items.length - 1] : this._items[itemIndex]
368
_triggerSlideEvent(relatedTarget, eventDirectionName) {
369
const targetIndex = this._getItemIndex(relatedTarget)
370
const fromIndex = this._getItemIndex(this._element.querySelector(SELECTOR_ACTIVE_ITEM))
371
const slideEvent = $.Event(EVENT_SLIDE, {
373
direction: eventDirectionName,
378
$(this._element).trigger(slideEvent)
383
_setActiveIndicatorElement(element) {
384
if (this._indicatorsElement) {
385
const indicators = [].slice.call(this._indicatorsElement.querySelectorAll(SELECTOR_ACTIVE))
386
$(indicators).removeClass(CLASS_NAME_ACTIVE)
388
const nextIndicator = this._indicatorsElement.children[
389
this._getItemIndex(element)
393
$(nextIndicator).addClass(CLASS_NAME_ACTIVE)
399
const element = this._activeElement || this._element.querySelector(SELECTOR_ACTIVE_ITEM)
405
const elementInterval = parseInt(element.getAttribute('data-interval'), 10)
407
if (elementInterval) {
408
this._config.defaultInterval = this._config.defaultInterval || this._config.interval
409
this._config.interval = elementInterval
411
this._config.interval = this._config.defaultInterval || this._config.interval
415
_slide(direction, element) {
416
const activeElement = this._element.querySelector(SELECTOR_ACTIVE_ITEM)
417
const activeElementIndex = this._getItemIndex(activeElement)
418
const nextElement = element || activeElement &&
419
this._getItemByDirection(direction, activeElement)
420
const nextElementIndex = this._getItemIndex(nextElement)
421
const isCycling = Boolean(this._interval)
423
let directionalClassName
425
let eventDirectionName
427
if (direction === DIRECTION_NEXT) {
428
directionalClassName = CLASS_NAME_LEFT
429
orderClassName = CLASS_NAME_NEXT
430
eventDirectionName = DIRECTION_LEFT
432
directionalClassName = CLASS_NAME_RIGHT
433
orderClassName = CLASS_NAME_PREV
434
eventDirectionName = DIRECTION_RIGHT
437
if (nextElement && $(nextElement).hasClass(CLASS_NAME_ACTIVE)) {
438
this._isSliding = false
442
const slideEvent = this._triggerSlideEvent(nextElement, eventDirectionName)
443
if (slideEvent.isDefaultPrevented()) {
447
if (!activeElement || !nextElement) {
452
this._isSliding = true
458
this._setActiveIndicatorElement(nextElement)
459
this._activeElement = nextElement
461
const slidEvent = $.Event(EVENT_SLID, {
462
relatedTarget: nextElement,
463
direction: eventDirectionName,
464
from: activeElementIndex,
468
if ($(this._element).hasClass(CLASS_NAME_SLIDE)) {
469
$(nextElement).addClass(orderClassName)
471
Util.reflow(nextElement)
473
$(activeElement).addClass(directionalClassName)
474
$(nextElement).addClass(directionalClassName)
476
const transitionDuration = Util.getTransitionDurationFromElement(activeElement)
479
.one(Util.TRANSITION_END, () => {
481
.removeClass(`${directionalClassName} ${orderClassName}`)
482
.addClass(CLASS_NAME_ACTIVE)
484
$(activeElement).removeClass(`${CLASS_NAME_ACTIVE} ${orderClassName} ${directionalClassName}`)
486
this._isSliding = false
488
setTimeout(() => $(this._element).trigger(slidEvent), 0)
490
.emulateTransitionEnd(transitionDuration)
492
$(activeElement).removeClass(CLASS_NAME_ACTIVE)
493
$(nextElement).addClass(CLASS_NAME_ACTIVE)
495
this._isSliding = false
496
$(this._element).trigger(slidEvent)
505
static _jQueryInterface(config) {
506
return this.each(function () {
507
let data = $(this).data(DATA_KEY)
513
if (typeof config === 'object') {
520
const action = typeof config === 'string' ? config : _config.slide
523
data = new Carousel(this, _config)
524
$(this).data(DATA_KEY, data)
527
if (typeof config === 'number') {
529
} else if (typeof action === 'string') {
530
if (typeof data[action] === 'undefined') {
531
throw new TypeError(`No method named "${action}"`)
535
} else if (_config.interval && _config.ride) {
542
static _dataApiClickHandler(event) {
543
const selector = Util.getSelectorFromElement(this)
549
const target = $(selector)[0]
551
if (!target || !$(target).hasClass(CLASS_NAME_CAROUSEL)) {
559
const slideIndex = this.getAttribute('data-slide-to')
562
config.interval = false
565
Carousel._jQueryInterface.call($(target), config)
568
$(target).data(DATA_KEY).to(slideIndex)
571
event.preventDefault()
579
$(document).on(EVENT_CLICK_DATA_API, SELECTOR_DATA_SLIDE, Carousel._dataApiClickHandler)
581
$(window).on(EVENT_LOAD_DATA_API, () => {
582
const carousels = [].slice.call(document.querySelectorAll(SELECTOR_DATA_RIDE))
583
for (let i = 0, len = carousels.length; i < len; i++) {
584
const $carousel = $(carousels[i])
585
Carousel._jQueryInterface.call($carousel, $carousel.data())
593
$.fn[NAME] = Carousel._jQueryInterface
594
$.fn[NAME].Constructor = Carousel
595
$.fn[NAME].noConflict = () => {
596
$.fn[NAME] = JQUERY_NO_CONFLICT
597
return Carousel._jQueryInterface
600
export default Carousel