carousel.js 17 KB
Newer Older
fat's avatar
fat committed
1
2
/**
 * --------------------------------------------------------------------------
XhmikosR's avatar
XhmikosR committed
3
 * Bootstrap (v4.3.1): carousel.js
fat's avatar
fat committed
4
5
6
7
 * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
 * --------------------------------------------------------------------------
 */

8
import {
9
  getjQuery,
10
11
  TRANSITION_END,
  emulateTransitionEnd,
12
  getElementFromSelector,
13
14
15
16
17
18
  getTransitionDurationFromElement,
  isVisible,
  makeArray,
  reflow,
  triggerTransitionEnd,
  typeCheckConfig
19
20
21
22
23
} from './util/index'
import Data from './dom/data'
import EventHandler from './dom/event-handler'
import Manipulator from './dom/manipulator'
import SelectorEngine from './dom/selector-engine'
Johann-S's avatar
Johann-S committed
24

Johann-S's avatar
Johann-S committed
25
26
27
28
29
/**
 * ------------------------------------------------------------------------
 * Constants
 * ------------------------------------------------------------------------
 */
fat's avatar
fat committed
30

XhmikosR's avatar
XhmikosR committed
31
32
33
34
35
const NAME = 'carousel'
const VERSION = '4.3.1'
const DATA_KEY = 'bs.carousel'
const EVENT_KEY = `.${DATA_KEY}`
const DATA_API_KEY = '.data-api'
XhmikosR's avatar
XhmikosR committed
36

XhmikosR's avatar
XhmikosR committed
37
38
const ARROW_LEFT_KEYCODE = 37 // KeyboardEvent.which value for left arrow key
const ARROW_RIGHT_KEYCODE = 39 // KeyboardEvent.which value for right arrow key
Johann-S's avatar
Johann-S committed
39
const TOUCHEVENT_COMPAT_WAIT = 500 // Time for mouse compat events to fire after touch
XhmikosR's avatar
XhmikosR committed
40
const SWIPE_THRESHOLD = 40
Johann-S's avatar
Johann-S committed
41
42

const Default = {
XhmikosR's avatar
XhmikosR committed
43
44
45
46
47
48
  interval: 5000,
  keyboard: true,
  slide: false,
  pause: 'hover',
  wrap: true,
  touch: true
Johann-S's avatar
Johann-S committed
49
50
51
}

const DefaultType = {
XhmikosR's avatar
XhmikosR committed
52
53
54
55
56
57
  interval: '(number|boolean)',
  keyboard: 'boolean',
  slide: '(boolean|string)',
  pause: '(string|boolean)',
  wrap: 'boolean',
  touch: 'boolean'
Johann-S's avatar
Johann-S committed
58
59
}

XhmikosR's avatar
XhmikosR committed
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
const DIRECTION_NEXT = 'next'
const DIRECTION_PREV = 'prev'
const DIRECTION_LEFT = 'left'
const DIRECTION_RIGHT = 'right'

const EVENT_SLIDE = `slide${EVENT_KEY}`
const EVENT_SLID = `slid${EVENT_KEY}`
const EVENT_KEYDOWN = `keydown${EVENT_KEY}`
const EVENT_MOUSEENTER = `mouseenter${EVENT_KEY}`
const EVENT_MOUSELEAVE = `mouseleave${EVENT_KEY}`
const EVENT_TOUCHSTART = `touchstart${EVENT_KEY}`
const EVENT_TOUCHMOVE = `touchmove${EVENT_KEY}`
const EVENT_TOUCHEND = `touchend${EVENT_KEY}`
const EVENT_POINTERDOWN = `pointerdown${EVENT_KEY}`
const EVENT_POINTERUP = `pointerup${EVENT_KEY}`
const EVENT_DRAG_START = `dragstart${EVENT_KEY}`
const EVENT_LOAD_DATA_API = `load${EVENT_KEY}${DATA_API_KEY}`
const EVENT_CLICK_DATA_API = `click${EVENT_KEY}${DATA_API_KEY}`

const CLASS_NAME_CAROUSEL = 'carousel'
const CLASS_NAME_ACTIVE = 'active'
const CLASS_NAME_SLIDE = 'slide'
const CLASS_NAME_RIGHT = 'carousel-item-right'
const CLASS_NAME_LEFT = 'carousel-item-left'
const CLASS_NAME_NEXT = 'carousel-item-next'
const CLASS_NAME_PREV = 'carousel-item-prev'
const CLASS_NAME_POINTER_EVENT = 'pointer-event'

const SELECTOR_ACTIVE = '.active'
const SELECTOR_ACTIVE_ITEM = '.active.carousel-item'
const SELECTOR_ITEM = '.carousel-item'
const SELECTOR_ITEM_IMG = '.carousel-item img'
const SELECTOR_NEXT_PREV = '.carousel-item-next, .carousel-item-prev'
const SELECTOR_INDICATORS = '.carousel-indicators'
const SELECTOR_DATA_SLIDE = '[data-slide], [data-slide-to]'
const SELECTOR_DATA_RIDE = '[data-ride="carousel"]'
fat's avatar
fat committed
96

Johann-S's avatar
Johann-S committed
97
const PointerType = {
XhmikosR's avatar
XhmikosR committed
98
99
  TOUCH: 'touch',
  PEN: 'pen'
Johann-S's avatar
Johann-S committed
100
101
}

Johann-S's avatar
Johann-S committed
102
103
104
105
106
107
108
/**
 * ------------------------------------------------------------------------
 * Class Definition
 * ------------------------------------------------------------------------
 */
class Carousel {
  constructor(element, config) {
XhmikosR's avatar
XhmikosR committed
109
110
    this._items = null
    this._interval = null
111
    this._activeElement = null
XhmikosR's avatar
XhmikosR committed
112
113
114
115
116
117
118
119
    this._isPaused = false
    this._isSliding = false
    this.touchTimeout = null
    this.touchStartX = 0
    this.touchDeltaX = 0

    this._config = this._getConfig(config)
    this._element = element
XhmikosR's avatar
XhmikosR committed
120
    this._indicatorsElement = SelectorEngine.findOne(SELECTOR_INDICATORS, this._element)
XhmikosR's avatar
XhmikosR committed
121
122
    this._touchSupported = 'ontouchstart' in document.documentElement || navigator.maxTouchPoints > 0
    this._pointerEvent = Boolean(window.PointerEvent || window.MSPointerEvent)
fat's avatar
fat committed
123

Johann-S's avatar
Johann-S committed
124
    this._addEventListeners()
125
    Data.setData(element, DATA_KEY, this)
Johann-S's avatar
Johann-S committed
126
  }
fat's avatar
fat committed
127

Johann-S's avatar
Johann-S committed
128
  // Getters
129

Johann-S's avatar
Johann-S committed
130
131
132
  static get VERSION() {
    return VERSION
  }
133

Johann-S's avatar
Johann-S committed
134
135
136
  static get Default() {
    return Default
  }
137

Johann-S's avatar
Johann-S committed
138
  // Public
139

Johann-S's avatar
Johann-S committed
140
141
  next() {
    if (!this._isSliding) {
XhmikosR's avatar
XhmikosR committed
142
      this._slide(DIRECTION_NEXT)
143
    }
Johann-S's avatar
Johann-S committed
144
  }
fat's avatar
fat committed
145

Johann-S's avatar
Johann-S committed
146
147
148
  nextWhenVisible() {
    // Don't call next when the page isn't visible
    // or the carousel or its parent isn't visible
149
    if (!document.hidden && isVisible(this._element)) {
Johann-S's avatar
Johann-S committed
150
      this.next()
fat's avatar
fat committed
151
    }
Johann-S's avatar
Johann-S committed
152
  }
fat's avatar
fat committed
153

Johann-S's avatar
Johann-S committed
154
155
  prev() {
    if (!this._isSliding) {
XhmikosR's avatar
XhmikosR committed
156
      this._slide(DIRECTION_PREV)
fat's avatar
fat committed
157
    }
Johann-S's avatar
Johann-S committed
158
  }
fat's avatar
fat committed
159

Johann-S's avatar
Johann-S committed
160
161
162
  pause(event) {
    if (!event) {
      this._isPaused = true
163
164
    }

XhmikosR's avatar
XhmikosR committed
165
    if (SelectorEngine.findOne(SELECTOR_NEXT_PREV, this._element)) {
166
      triggerTransitionEnd(this._element)
Johann-S's avatar
Johann-S committed
167
      this.cycle(true)
fat's avatar
fat committed
168
169
    }

Johann-S's avatar
Johann-S committed
170
171
172
    clearInterval(this._interval)
    this._interval = null
  }
fat's avatar
fat committed
173

Johann-S's avatar
Johann-S committed
174
175
176
177
  cycle(event) {
    if (!event) {
      this._isPaused = false
    }
fat's avatar
fat committed
178

Johann-S's avatar
Johann-S committed
179
    if (this._interval) {
fat's avatar
fat committed
180
181
182
183
      clearInterval(this._interval)
      this._interval = null
    }

Johann-S's avatar
Johann-S committed
184
    if (this._config && this._config.interval && !this._isPaused) {
Johann-S's avatar
Johann-S committed
185
186
187
188
189
190
      this._interval = setInterval(
        (document.visibilityState ? this.nextWhenVisible : this.next).bind(this),
        this._config.interval
      )
    }
  }
fat's avatar
fat committed
191

Johann-S's avatar
Johann-S committed
192
  to(index) {
XhmikosR's avatar
XhmikosR committed
193
    this._activeElement = SelectorEngine.findOne(SELECTOR_ACTIVE_ITEM, this._element)
Johann-S's avatar
Johann-S committed
194
195
196
197
    const activeIndex = this._getItemIndex(this._activeElement)

    if (index > this._items.length - 1 || index < 0) {
      return
fat's avatar
fat committed
198
199
    }

Johann-S's avatar
Johann-S committed
200
    if (this._isSliding) {
XhmikosR's avatar
XhmikosR committed
201
      EventHandler.one(this._element, EVENT_SLID, () => this.to(index))
Johann-S's avatar
Johann-S committed
202
203
      return
    }
fat's avatar
fat committed
204

Johann-S's avatar
Johann-S committed
205
206
207
208
209
    if (activeIndex === index) {
      this.pause()
      this.cycle()
      return
    }
fat's avatar
fat committed
210

XhmikosR's avatar
XhmikosR committed
211
    const direction = index > activeIndex ?
XhmikosR's avatar
XhmikosR committed
212
213
      DIRECTION_NEXT :
      DIRECTION_PREV
fat's avatar
fat committed
214

Johann-S's avatar
Johann-S committed
215
216
    this._slide(direction, this._items[index])
  }
fat's avatar
fat committed
217

Johann-S's avatar
Johann-S committed
218
  dispose() {
219
    EventHandler.off(this._element, EVENT_KEY)
Johann-S's avatar
Johann-S committed
220
    Data.removeData(this._element, DATA_KEY)
Johann-S's avatar
Johann-S committed
221

XhmikosR's avatar
XhmikosR committed
222
223
224
225
226
227
228
    this._items = null
    this._config = null
    this._element = null
    this._interval = null
    this._isPaused = null
    this._isSliding = null
    this._activeElement = null
Johann-S's avatar
Johann-S committed
229
230
    this._indicatorsElement = null
  }
fat's avatar
fat committed
231

Johann-S's avatar
Johann-S committed
232
  // Private
fat's avatar
fat committed
233

Johann-S's avatar
Johann-S committed
234
235
236
237
  _getConfig(config) {
    config = {
      ...Default,
      ...config
fat's avatar
fat committed
238
    }
239
    typeCheckConfig(NAME, config, DefaultType)
Johann-S's avatar
Johann-S committed
240
241
    return config
  }
fat's avatar
fat committed
242

Johann-S's avatar
Johann-S committed
243
244
245
246
247
248
249
250
251
  _handleSwipe() {
    const absDeltax = Math.abs(this.touchDeltaX)

    if (absDeltax <= SWIPE_THRESHOLD) {
      return
    }

    const direction = absDeltax / this.touchDeltaX

252
253
    this.touchDeltaX = 0

Johann-S's avatar
Johann-S committed
254
255
256
257
258
259
260
261
262
263
264
    // swipe left
    if (direction > 0) {
      this.prev()
    }

    // swipe right
    if (direction < 0) {
      this.next()
    }
  }

Johann-S's avatar
Johann-S committed
265
266
  _addEventListeners() {
    if (this._config.keyboard) {
Johann-S's avatar
Johann-S committed
267
      EventHandler
XhmikosR's avatar
XhmikosR committed
268
        .on(this._element, EVENT_KEYDOWN, event => this._keydown(event))
fat's avatar
fat committed
269
270
    }

Johann-S's avatar
Johann-S committed
271
    if (this._config.pause === 'hover') {
Johann-S's avatar
Johann-S committed
272
      EventHandler
XhmikosR's avatar
XhmikosR committed
273
        .on(this._element, EVENT_MOUSEENTER, event => this.pause(event))
Johann-S's avatar
Johann-S committed
274
      EventHandler
XhmikosR's avatar
XhmikosR committed
275
        .on(this._element, EVENT_MOUSELEAVE, event => this.cycle(event))
Johann-S's avatar
Johann-S committed
276
277
    }

Johann-S's avatar
Johann-S committed
278
    if (this._config.touch && this._touchSupported) {
279
280
      this._addTouchEventListeners()
    }
Johann-S's avatar
Johann-S committed
281
282
283
  }

  _addTouchEventListeners() {
XhmikosR's avatar
XhmikosR committed
284
    const start = event => {
Johann-S's avatar
Johann-S committed
285
286
      if (this._pointerEvent && PointerType[event.pointerType.toUpperCase()]) {
        this.touchStartX = event.clientX
287
      } else if (!this._pointerEvent) {
Johann-S's avatar
Johann-S committed
288
        this.touchStartX = event.touches[0].clientX
Johann-S's avatar
Johann-S committed
289
290
      }
    }
Johann-S's avatar
Johann-S committed
291

XhmikosR's avatar
XhmikosR committed
292
    const move = event => {
Johann-S's avatar
Johann-S committed
293
      // ensure swiping with one touch and not pinching
Johann-S's avatar
Johann-S committed
294
      if (event.touches && event.touches.length > 1) {
Johann-S's avatar
Johann-S committed
295
296
        this.touchDeltaX = 0
      } else {
Johann-S's avatar
Johann-S committed
297
        this.touchDeltaX = event.touches[0].clientX - this.touchStartX
Johann-S's avatar
Johann-S committed
298
299
300
      }
    }

XhmikosR's avatar
XhmikosR committed
301
    const end = event => {
Johann-S's avatar
Johann-S committed
302
      if (this._pointerEvent && PointerType[event.pointerType.toUpperCase()]) {
Johann-S's avatar
Johann-S committed
303
        this.touchDeltaX = event.clientX - this.touchStartX
Johann-S's avatar
Johann-S committed
304
      }
Johann-S's avatar
Johann-S committed
305
306
307

      this._handleSwipe()
      if (this._config.pause === 'hover') {
Johann-S's avatar
Johann-S committed
308
309
310
311
312
313
314
        // If it's a touch-enabled device, mouseenter/leave are fired as
        // part of the mouse compatibility events on first tap - the carousel
        // would stop cycling until user tapped out of it;
        // here, we listen for touchend, explicitly pause the carousel
        // (as if it's the second time we tap on it, mouseenter compat event
        // is NOT fired) and after a timeout (to allow for mouse compatibility
        // events to fire) we explicitly restart cycling
Johann-S's avatar
Johann-S committed
315
316
317
318
319

        this.pause()
        if (this.touchTimeout) {
          clearTimeout(this.touchTimeout)
        }
XhmikosR's avatar
XhmikosR committed
320
321

        this.touchTimeout = setTimeout(event => this.cycle(event), TOUCHEVENT_COMPAT_WAIT + this._config.interval)
322
      }
Johann-S's avatar
Johann-S committed
323
324
    }

XhmikosR's avatar
XhmikosR committed
325
326
    makeArray(SelectorEngine.find(SELECTOR_ITEM_IMG, this._element)).forEach(itemImg => {
      EventHandler.on(itemImg, EVENT_DRAG_START, e => e.preventDefault())
Johann-S's avatar
Johann-S committed
327
    })
328

Johann-S's avatar
Johann-S committed
329
    if (this._pointerEvent) {
XhmikosR's avatar
XhmikosR committed
330
331
      EventHandler.on(this._element, EVENT_POINTERDOWN, event => start(event))
      EventHandler.on(this._element, EVENT_POINTERUP, event => end(event))
Johann-S's avatar
Johann-S committed
332

XhmikosR's avatar
XhmikosR committed
333
      this._element.classList.add(CLASS_NAME_POINTER_EVENT)
Johann-S's avatar
Johann-S committed
334
    } else {
XhmikosR's avatar
XhmikosR committed
335
336
337
      EventHandler.on(this._element, EVENT_TOUCHSTART, event => start(event))
      EventHandler.on(this._element, EVENT_TOUCHMOVE, event => move(event))
      EventHandler.on(this._element, EVENT_TOUCHEND, event => end(event))
Johann-S's avatar
Johann-S committed
338
    }
Johann-S's avatar
Johann-S committed
339
  }
fat's avatar
fat committed
340

Johann-S's avatar
Johann-S committed
341
342
343
  _keydown(event) {
    if (/input|textarea/i.test(event.target.tagName)) {
      return
fat's avatar
fat committed
344
345
    }

Johann-S's avatar
Johann-S committed
346
347
348
349
350
351
352
353
354
355
    switch (event.which) {
      case ARROW_LEFT_KEYCODE:
        event.preventDefault()
        this.prev()
        break
      case ARROW_RIGHT_KEYCODE:
        event.preventDefault()
        this.next()
        break
      default:
fat's avatar
fat committed
356
    }
Johann-S's avatar
Johann-S committed
357
  }
fat's avatar
fat committed
358

Johann-S's avatar
Johann-S committed
359
  _getItemIndex(element) {
XhmikosR's avatar
XhmikosR committed
360
    this._items = element && element.parentNode ?
XhmikosR's avatar
XhmikosR committed
361
      makeArray(SelectorEngine.find(SELECTOR_ITEM, element.parentNode)) :
XhmikosR's avatar
XhmikosR committed
362
      []
Johann-S's avatar
Johann-S committed
363

Johann-S's avatar
Johann-S committed
364
365
    return this._items.indexOf(element)
  }
fat's avatar
fat committed
366

Johann-S's avatar
Johann-S committed
367
  _getItemByDirection(direction, activeElement) {
XhmikosR's avatar
XhmikosR committed
368
369
    const isNextDirection = direction === DIRECTION_NEXT
    const isPrevDirection = direction === DIRECTION_PREV
XhmikosR's avatar
XhmikosR committed
370
371
    const activeIndex = this._getItemIndex(activeElement)
    const lastItemIndex = this._items.length - 1
372
373
    const isGoingToWrap = (isPrevDirection && activeIndex === 0) ||
                            (isNextDirection && activeIndex === lastItemIndex)
fat's avatar
fat committed
374

Johann-S's avatar
Johann-S committed
375
376
377
    if (isGoingToWrap && !this._config.wrap) {
      return activeElement
    }
fat's avatar
fat committed
378

XhmikosR's avatar
XhmikosR committed
379
    const delta = direction === DIRECTION_PREV ? -1 : 1
Johann-S's avatar
Johann-S committed
380
    const itemIndex = (activeIndex + delta) % this._items.length
fat's avatar
fat committed
381

XhmikosR's avatar
XhmikosR committed
382
383
384
    return itemIndex === -1 ?
      this._items[this._items.length - 1] :
      this._items[itemIndex]
Johann-S's avatar
Johann-S committed
385
  }
fat's avatar
fat committed
386

Johann-S's avatar
Johann-S committed
387
388
  _triggerSlideEvent(relatedTarget, eventDirectionName) {
    const targetIndex = this._getItemIndex(relatedTarget)
XhmikosR's avatar
XhmikosR committed
389
    const fromIndex = this._getItemIndex(SelectorEngine.findOne(SELECTOR_ACTIVE_ITEM, this._element))
Johann-S's avatar
Johann-S committed
390

XhmikosR's avatar
XhmikosR committed
391
    return EventHandler.trigger(this._element, EVENT_SLIDE, {
Johann-S's avatar
Johann-S committed
392
393
394
395
396
397
      relatedTarget,
      direction: eventDirectionName,
      from: fromIndex,
      to: targetIndex
    })
  }
fat's avatar
fat committed
398

Johann-S's avatar
Johann-S committed
399
400
  _setActiveIndicatorElement(element) {
    if (this._indicatorsElement) {
XhmikosR's avatar
XhmikosR committed
401
      const indicators = SelectorEngine.find(SELECTOR_ACTIVE, this._indicatorsElement)
Johann-S's avatar
Johann-S committed
402
      for (let i = 0; i < indicators.length; i++) {
XhmikosR's avatar
XhmikosR committed
403
        indicators[i].classList.remove(CLASS_NAME_ACTIVE)
Johann-S's avatar
Johann-S committed
404
      }
fat's avatar
fat committed
405

Johann-S's avatar
Johann-S committed
406
407
408
      const nextIndicator = this._indicatorsElement.children[
        this._getItemIndex(element)
      ]
fat's avatar
fat committed
409

Johann-S's avatar
Johann-S committed
410
      if (nextIndicator) {
XhmikosR's avatar
XhmikosR committed
411
        nextIndicator.classList.add(CLASS_NAME_ACTIVE)
fat's avatar
fat committed
412
413
      }
    }
Johann-S's avatar
Johann-S committed
414
  }
fat's avatar
fat committed
415

Johann-S's avatar
Johann-S committed
416
  _slide(direction, element) {
XhmikosR's avatar
XhmikosR committed
417
    const activeElement = SelectorEngine.findOne(SELECTOR_ACTIVE_ITEM, this._element)
Johann-S's avatar
Johann-S committed
418
    const activeElementIndex = this._getItemIndex(activeElement)
419
420
    const nextElement = element || (activeElement &&
      this._getItemByDirection(direction, activeElement))
Johann-S's avatar
Johann-S committed
421

Johann-S's avatar
Johann-S committed
422
423
424
425
426
427
428
    const nextElementIndex = this._getItemIndex(nextElement)
    const isCycling = Boolean(this._interval)

    let directionalClassName
    let orderClassName
    let eventDirectionName

XhmikosR's avatar
XhmikosR committed
429
430
431
432
    if (direction === DIRECTION_NEXT) {
      directionalClassName = CLASS_NAME_LEFT
      orderClassName = CLASS_NAME_NEXT
      eventDirectionName = DIRECTION_LEFT
Johann-S's avatar
Johann-S committed
433
    } else {
XhmikosR's avatar
XhmikosR committed
434
435
436
      directionalClassName = CLASS_NAME_RIGHT
      orderClassName = CLASS_NAME_PREV
      eventDirectionName = DIRECTION_RIGHT
Johann-S's avatar
Johann-S committed
437
    }
Mark Otto's avatar
Mark Otto committed
438

XhmikosR's avatar
XhmikosR committed
439
    if (nextElement && nextElement.classList.contains(CLASS_NAME_ACTIVE)) {
Johann-S's avatar
Johann-S committed
440
441
442
      this._isSliding = false
      return
    }
fat's avatar
fat committed
443

Johann-S's avatar
Johann-S committed
444
    const slideEvent = this._triggerSlideEvent(nextElement, eventDirectionName)
Johann-S's avatar
Johann-S committed
445
    if (slideEvent.defaultPrevented) {
Johann-S's avatar
Johann-S committed
446
447
      return
    }
fat's avatar
fat committed
448

Johann-S's avatar
Johann-S committed
449
450
451
452
    if (!activeElement || !nextElement) {
      // Some weirdness is happening, so we bail
      return
    }
fat's avatar
fat committed
453

Johann-S's avatar
Johann-S committed
454
    this._isSliding = true
fat's avatar
fat committed
455

Johann-S's avatar
Johann-S committed
456
457
458
    if (isCycling) {
      this.pause()
    }
fat's avatar
fat committed
459

Johann-S's avatar
Johann-S committed
460
    this._setActiveIndicatorElement(nextElement)
fat's avatar
fat committed
461

XhmikosR's avatar
XhmikosR committed
462
    if (this._element.classList.contains(CLASS_NAME_SLIDE)) {
Johann-S's avatar
Johann-S committed
463
      nextElement.classList.add(orderClassName)
fat's avatar
fat committed
464

465
      reflow(nextElement)
fat's avatar
fat committed
466

Johann-S's avatar
Johann-S committed
467
468
      activeElement.classList.add(directionalClassName)
      nextElement.classList.add(directionalClassName)
469

Johann-S's avatar
Johann-S committed
470
471
472
473
474
475
476
477
      const nextElementInterval = parseInt(nextElement.getAttribute('data-interval'), 10)
      if (nextElementInterval) {
        this._config.defaultInterval = this._config.defaultInterval || this._config.interval
        this._config.interval = nextElementInterval
      } else {
        this._config.interval = this._config.defaultInterval || this._config.interval
      }

478
      const transitionDuration = getTransitionDurationFromElement(activeElement)
479

Johann-S's avatar
Johann-S committed
480
      EventHandler
481
        .one(activeElement, TRANSITION_END, () => {
Johann-S's avatar
Johann-S committed
482
483
          nextElement.classList.remove(directionalClassName)
          nextElement.classList.remove(orderClassName)
XhmikosR's avatar
XhmikosR committed
484
          nextElement.classList.add(CLASS_NAME_ACTIVE)
fat's avatar
fat committed
485

XhmikosR's avatar
XhmikosR committed
486
          activeElement.classList.remove(CLASS_NAME_ACTIVE)
Johann-S's avatar
Johann-S committed
487
488
          activeElement.classList.remove(orderClassName)
          activeElement.classList.remove(directionalClassName)
fat's avatar
fat committed
489

Johann-S's avatar
Johann-S committed
490
          this._isSliding = false
fat's avatar
fat committed
491

Johann-S's avatar
Johann-S committed
492
          setTimeout(() => {
XhmikosR's avatar
XhmikosR committed
493
            EventHandler.trigger(this._element, EVENT_SLID, {
Johann-S's avatar
Johann-S committed
494
495
496
497
498
499
              relatedTarget: nextElement,
              direction: eventDirectionName,
              from: activeElementIndex,
              to: nextElementIndex
            })
          }, 0)
Johann-S's avatar
Johann-S committed
500
        })
Johann-S's avatar
Johann-S committed
501

502
      emulateTransitionEnd(activeElement, transitionDuration)
Johann-S's avatar
Johann-S committed
503
    } else {
XhmikosR's avatar
XhmikosR committed
504
505
      activeElement.classList.remove(CLASS_NAME_ACTIVE)
      nextElement.classList.add(CLASS_NAME_ACTIVE)
fat's avatar
fat committed
506

Johann-S's avatar
Johann-S committed
507
      this._isSliding = false
XhmikosR's avatar
XhmikosR committed
508
      EventHandler.trigger(this._element, EVENT_SLID, {
Johann-S's avatar
Johann-S committed
509
510
511
512
513
        relatedTarget: nextElement,
        direction: eventDirectionName,
        from: activeElementIndex,
        to: nextElementIndex
      })
Johann-S's avatar
Johann-S committed
514
    }
fat's avatar
fat committed
515

Johann-S's avatar
Johann-S committed
516
517
    if (isCycling) {
      this.cycle()
fat's avatar
fat committed
518
    }
Johann-S's avatar
Johann-S committed
519
  }
fat's avatar
fat committed
520

Johann-S's avatar
Johann-S committed
521
  // Static
fat's avatar
fat committed
522

523
  static carouselInterface(element, config) {
XhmikosR's avatar
XhmikosR committed
524
    let data = Data.getData(element, DATA_KEY)
525
526
527
528
    let _config = {
      ...Default,
      ...Manipulator.getDataAttributes(element)
    }
fat's avatar
fat committed
529

530
531
532
533
    if (typeof config === 'object') {
      _config = {
        ..._config,
        ...config
Johann-S's avatar
Johann-S committed
534
      }
535
    }
fat's avatar
fat committed
536

537
    const action = typeof config === 'string' ? config : _config.slide
fat's avatar
fat committed
538

539
540
541
    if (!data) {
      data = new Carousel(element, _config)
    }
fat's avatar
fat committed
542

543
544
545
546
    if (typeof config === 'number') {
      data.to(config)
    } else if (typeof action === 'string') {
      if (typeof data[action] === 'undefined') {
XhmikosR's avatar
XhmikosR committed
547
        throw new TypeError(`No method named "${action}"`)
fat's avatar
fat committed
548
      }
XhmikosR's avatar
XhmikosR committed
549

550
551
552
553
554
555
556
      data[action]()
    } else if (_config.interval && _config.ride) {
      data.pause()
      data.cycle()
    }
  }

557
  static jQueryInterface(config) {
558
    return this.each(function () {
559
      Carousel.carouselInterface(this, config)
Johann-S's avatar
Johann-S committed
560
561
    })
  }
fat's avatar
fat committed
562

563
  static dataApiClickHandler(event) {
564
    const target = getElementFromSelector(this)
Jacob Thornton's avatar
Jacob Thornton committed
565

XhmikosR's avatar
XhmikosR committed
566
    if (!target || !target.classList.contains(CLASS_NAME_CAROUSEL)) {
Johann-S's avatar
Johann-S committed
567
568
      return
    }
fat's avatar
fat committed
569

Johann-S's avatar
Johann-S committed
570
    const config = {
571
572
      ...Manipulator.getDataAttributes(target),
      ...Manipulator.getDataAttributes(this)
Johann-S's avatar
Johann-S committed
573
574
    }
    const slideIndex = this.getAttribute('data-slide-to')
fat's avatar
fat committed
575

Johann-S's avatar
Johann-S committed
576
577
578
579
    if (slideIndex) {
      config.interval = false
    }

580
    Carousel.carouselInterface(target, config)
fat's avatar
fat committed
581

Johann-S's avatar
Johann-S committed
582
    if (slideIndex) {
Johann-S's avatar
Johann-S committed
583
      Data.getData(target, DATA_KEY).to(slideIndex)
fat's avatar
fat committed
584
    }
Johann-S's avatar
Johann-S committed
585
586

    event.preventDefault()
fat's avatar
fat committed
587
  }
588

589
  static getInstance(element) {
590
591
    return Data.getData(element, DATA_KEY)
  }
Johann-S's avatar
Johann-S committed
592
}
fat's avatar
fat committed
593

Johann-S's avatar
Johann-S committed
594
595
596
597
598
/**
 * ------------------------------------------------------------------------
 * Data Api implementation
 * ------------------------------------------------------------------------
 */
fat's avatar
fat committed
599

Johann-S's avatar
Johann-S committed
600
EventHandler
XhmikosR's avatar
XhmikosR committed
601
  .on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_SLIDE, Carousel.dataApiClickHandler)
fat's avatar
fat committed
602

XhmikosR's avatar
XhmikosR committed
603
604
EventHandler.on(window, EVENT_LOAD_DATA_API, () => {
  const carousels = makeArray(SelectorEngine.find(SELECTOR_DATA_RIDE))
Johann-S's avatar
Johann-S committed
605
  for (let i = 0, len = carousels.length; i < len; i++) {
606
    Carousel.carouselInterface(carousels[i], Data.getData(carousels[i], DATA_KEY))
fat's avatar
fat committed
607
  }
Johann-S's avatar
Johann-S committed
608
609
})

610
611
const $ = getjQuery()

Johann-S's avatar
Johann-S committed
612
613
614
615
/**
 * ------------------------------------------------------------------------
 * jQuery
 * ------------------------------------------------------------------------
Johann-S's avatar
Johann-S committed
616
 * add .carousel to jQuery only if jQuery is present
Johann-S's avatar
Johann-S committed
617
 */
Johann-S's avatar
Johann-S committed
618
/* istanbul ignore if */
619
if ($) {
Johann-S's avatar
Johann-S committed
620
  const JQUERY_NO_CONFLICT = $.fn[NAME]
621
  $.fn[NAME] = Carousel.jQueryInterface
XhmikosR's avatar
XhmikosR committed
622
623
  $.fn[NAME].Constructor = Carousel
  $.fn[NAME].noConflict = () => {
Johann-S's avatar
Johann-S committed
624
    $.fn[NAME] = JQUERY_NO_CONFLICT
625
    return Carousel.jQueryInterface
Johann-S's avatar
Johann-S committed
626
  }
Johann-S's avatar
Johann-S committed
627
}
fat's avatar
fat committed
628
629

export default Carousel