carousel.js 16.6 KB
Newer Older
fat's avatar
fat committed
1
2
/**
 * --------------------------------------------------------------------------
3
 * Bootstrap (v5.0.0-alpha1): carousel.js
4
 * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
fat's avatar
fat committed
5
6
7
 * --------------------------------------------------------------------------
 */

8
import {
9
  getjQuery,
10
11
  TRANSITION_END,
  emulateTransitionEnd,
12
  getElementFromSelector,
13
14
15
16
17
  getTransitionDurationFromElement,
  isVisible,
  reflow,
  triggerTransitionEnd,
  typeCheckConfig
18
19
20
21
22
} 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
23

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

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

36
37
const ARROW_LEFT_KEY = 'ArrowLeft'
const ARROW_RIGHT_KEY = 'ArrowRight'
Johann-S's avatar
Johann-S committed
38
const TOUCHEVENT_COMPAT_WAIT = 500 // Time for mouse compat events to fire after touch
XhmikosR's avatar
XhmikosR committed
39
const SWIPE_THRESHOLD = 40
Johann-S's avatar
Johann-S committed
40
41

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

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

XhmikosR's avatar
XhmikosR committed
59
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
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
95

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

Johann-S's avatar
Johann-S committed
101
102
103
104
105
106
107
/**
 * ------------------------------------------------------------------------
 * Class Definition
 * ------------------------------------------------------------------------
 */
class Carousel {
  constructor(element, config) {
XhmikosR's avatar
XhmikosR committed
108
109
    this._items = null
    this._interval = null
110
    this._activeElement = null
XhmikosR's avatar
XhmikosR committed
111
112
113
114
115
116
117
118
    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
119
    this._indicatorsElement = SelectorEngine.findOne(SELECTOR_INDICATORS, this._element)
XhmikosR's avatar
XhmikosR committed
120
    this._touchSupported = 'ontouchstart' in document.documentElement || navigator.maxTouchPoints > 0
121
    this._pointerEvent = Boolean(window.PointerEvent)
fat's avatar
fat committed
122

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

XhmikosR's avatar
XhmikosR committed
221
222
223
224
225
226
227
    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
228
229
    this._indicatorsElement = null
  }
fat's avatar
fat committed
230

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

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

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

    if (absDeltax <= SWIPE_THRESHOLD) {
      return
    }

    const direction = absDeltax / this.touchDeltaX

251
252
    this.touchDeltaX = 0

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

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

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

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

Johann-S's avatar
Johann-S committed
274
    if (this._config.touch && this._touchSupported) {
275
276
      this._addTouchEventListeners()
    }
Johann-S's avatar
Johann-S committed
277
278
279
  }

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

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

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

      this._handleSwipe()
      if (this._config.pause === 'hover') {
Johann-S's avatar
Johann-S committed
304
305
306
307
308
309
310
        // 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
311
312
313
314
315

        this.pause()
        if (this.touchTimeout) {
          clearTimeout(this.touchTimeout)
        }
XhmikosR's avatar
XhmikosR committed
316
317

        this.touchTimeout = setTimeout(event => this.cycle(event), TOUCHEVENT_COMPAT_WAIT + this._config.interval)
318
      }
Johann-S's avatar
Johann-S committed
319
320
    }

321
    SelectorEngine.find(SELECTOR_ITEM_IMG, this._element).forEach(itemImg => {
XhmikosR's avatar
XhmikosR committed
322
      EventHandler.on(itemImg, EVENT_DRAG_START, e => e.preventDefault())
Johann-S's avatar
Johann-S committed
323
    })
324

Johann-S's avatar
Johann-S committed
325
    if (this._pointerEvent) {
XhmikosR's avatar
XhmikosR committed
326
327
      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
328

XhmikosR's avatar
XhmikosR committed
329
      this._element.classList.add(CLASS_NAME_POINTER_EVENT)
Johann-S's avatar
Johann-S committed
330
    } else {
XhmikosR's avatar
XhmikosR committed
331
332
333
      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
334
    }
Johann-S's avatar
Johann-S committed
335
  }
fat's avatar
fat committed
336

Johann-S's avatar
Johann-S committed
337
338
339
  _keydown(event) {
    if (/input|textarea/i.test(event.target.tagName)) {
      return
fat's avatar
fat committed
340
341
    }

342
343
    switch (event.key) {
      case ARROW_LEFT_KEY:
Johann-S's avatar
Johann-S committed
344
345
346
        event.preventDefault()
        this.prev()
        break
347
      case ARROW_RIGHT_KEY:
Johann-S's avatar
Johann-S committed
348
349
350
351
        event.preventDefault()
        this.next()
        break
      default:
fat's avatar
fat committed
352
    }
Johann-S's avatar
Johann-S committed
353
  }
fat's avatar
fat committed
354

Johann-S's avatar
Johann-S committed
355
  _getItemIndex(element) {
XhmikosR's avatar
XhmikosR committed
356
    this._items = element && element.parentNode ?
357
      SelectorEngine.find(SELECTOR_ITEM, element.parentNode) :
XhmikosR's avatar
XhmikosR committed
358
      []
Johann-S's avatar
Johann-S committed
359

Johann-S's avatar
Johann-S committed
360
361
    return this._items.indexOf(element)
  }
fat's avatar
fat committed
362

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

Johann-S's avatar
Johann-S committed
371
372
373
    if (isGoingToWrap && !this._config.wrap) {
      return activeElement
    }
fat's avatar
fat committed
374

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

XhmikosR's avatar
XhmikosR committed
378
379
380
    return itemIndex === -1 ?
      this._items[this._items.length - 1] :
      this._items[itemIndex]
Johann-S's avatar
Johann-S committed
381
  }
fat's avatar
fat committed
382

Johann-S's avatar
Johann-S committed
383
384
  _triggerSlideEvent(relatedTarget, eventDirectionName) {
    const targetIndex = this._getItemIndex(relatedTarget)
XhmikosR's avatar
XhmikosR committed
385
    const fromIndex = this._getItemIndex(SelectorEngine.findOne(SELECTOR_ACTIVE_ITEM, this._element))
Johann-S's avatar
Johann-S committed
386

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

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

Johann-S's avatar
Johann-S committed
402
403
404
      const nextIndicator = this._indicatorsElement.children[
        this._getItemIndex(element)
      ]
fat's avatar
fat committed
405

Johann-S's avatar
Johann-S committed
406
      if (nextIndicator) {
XhmikosR's avatar
XhmikosR committed
407
        nextIndicator.classList.add(CLASS_NAME_ACTIVE)
fat's avatar
fat committed
408
409
      }
    }
Johann-S's avatar
Johann-S committed
410
  }
fat's avatar
fat committed
411

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

Johann-S's avatar
Johann-S committed
418
419
420
421
422
423
424
    const nextElementIndex = this._getItemIndex(nextElement)
    const isCycling = Boolean(this._interval)

    let directionalClassName
    let orderClassName
    let eventDirectionName

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

XhmikosR's avatar
XhmikosR committed
435
    if (nextElement && nextElement.classList.contains(CLASS_NAME_ACTIVE)) {
Johann-S's avatar
Johann-S committed
436
437
438
      this._isSliding = false
      return
    }
fat's avatar
fat committed
439

Johann-S's avatar
Johann-S committed
440
    const slideEvent = this._triggerSlideEvent(nextElement, eventDirectionName)
Johann-S's avatar
Johann-S committed
441
    if (slideEvent.defaultPrevented) {
Johann-S's avatar
Johann-S committed
442
443
      return
    }
fat's avatar
fat committed
444

Johann-S's avatar
Johann-S committed
445
446
447
448
    if (!activeElement || !nextElement) {
      // Some weirdness is happening, so we bail
      return
    }
fat's avatar
fat committed
449

Johann-S's avatar
Johann-S committed
450
    this._isSliding = true
fat's avatar
fat committed
451

Johann-S's avatar
Johann-S committed
452
453
454
    if (isCycling) {
      this.pause()
    }
fat's avatar
fat committed
455

Johann-S's avatar
Johann-S committed
456
    this._setActiveIndicatorElement(nextElement)
fat's avatar
fat committed
457

XhmikosR's avatar
XhmikosR committed
458
    if (this._element.classList.contains(CLASS_NAME_SLIDE)) {
Johann-S's avatar
Johann-S committed
459
      nextElement.classList.add(orderClassName)
fat's avatar
fat committed
460

461
      reflow(nextElement)
fat's avatar
fat committed
462

Johann-S's avatar
Johann-S committed
463
464
      activeElement.classList.add(directionalClassName)
      nextElement.classList.add(directionalClassName)
465

Johann-S's avatar
Johann-S committed
466
467
468
469
470
471
472
473
      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
      }

474
      const transitionDuration = getTransitionDurationFromElement(activeElement)
475

XhmikosR's avatar
XhmikosR committed
476
477
478
      EventHandler.one(activeElement, TRANSITION_END, () => {
        nextElement.classList.remove(directionalClassName, orderClassName)
        nextElement.classList.add(CLASS_NAME_ACTIVE)
fat's avatar
fat committed
479

XhmikosR's avatar
XhmikosR committed
480
        activeElement.classList.remove(CLASS_NAME_ACTIVE, orderClassName, directionalClassName)
fat's avatar
fat committed
481

XhmikosR's avatar
XhmikosR committed
482
        this._isSliding = false
fat's avatar
fat committed
483

XhmikosR's avatar
XhmikosR committed
484
485
486
487
488
489
490
491
492
        setTimeout(() => {
          EventHandler.trigger(this._element, EVENT_SLID, {
            relatedTarget: nextElement,
            direction: eventDirectionName,
            from: activeElementIndex,
            to: nextElementIndex
          })
        }, 0)
      })
Johann-S's avatar
Johann-S committed
493

494
      emulateTransitionEnd(activeElement, transitionDuration)
Johann-S's avatar
Johann-S committed
495
    } else {
XhmikosR's avatar
XhmikosR committed
496
497
      activeElement.classList.remove(CLASS_NAME_ACTIVE)
      nextElement.classList.add(CLASS_NAME_ACTIVE)
fat's avatar
fat committed
498

Johann-S's avatar
Johann-S committed
499
      this._isSliding = false
XhmikosR's avatar
XhmikosR committed
500
      EventHandler.trigger(this._element, EVENT_SLID, {
Johann-S's avatar
Johann-S committed
501
502
503
504
505
        relatedTarget: nextElement,
        direction: eventDirectionName,
        from: activeElementIndex,
        to: nextElementIndex
      })
Johann-S's avatar
Johann-S committed
506
    }
fat's avatar
fat committed
507

Johann-S's avatar
Johann-S committed
508
509
    if (isCycling) {
      this.cycle()
fat's avatar
fat committed
510
    }
Johann-S's avatar
Johann-S committed
511
  }
fat's avatar
fat committed
512

Johann-S's avatar
Johann-S committed
513
  // Static
fat's avatar
fat committed
514

515
  static carouselInterface(element, config) {
XhmikosR's avatar
XhmikosR committed
516
    let data = Data.getData(element, DATA_KEY)
517
518
519
520
    let _config = {
      ...Default,
      ...Manipulator.getDataAttributes(element)
    }
fat's avatar
fat committed
521

522
523
524
525
    if (typeof config === 'object') {
      _config = {
        ..._config,
        ...config
Johann-S's avatar
Johann-S committed
526
      }
527
    }
fat's avatar
fat committed
528

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

531
532
533
    if (!data) {
      data = new Carousel(element, _config)
    }
fat's avatar
fat committed
534

535
536
537
538
    if (typeof config === 'number') {
      data.to(config)
    } else if (typeof action === 'string') {
      if (typeof data[action] === 'undefined') {
XhmikosR's avatar
XhmikosR committed
539
        throw new TypeError(`No method named "${action}"`)
fat's avatar
fat committed
540
      }
XhmikosR's avatar
XhmikosR committed
541

542
543
544
545
546
547
548
      data[action]()
    } else if (_config.interval && _config.ride) {
      data.pause()
      data.cycle()
    }
  }

549
  static jQueryInterface(config) {
550
    return this.each(function () {
551
      Carousel.carouselInterface(this, config)
Johann-S's avatar
Johann-S committed
552
553
    })
  }
fat's avatar
fat committed
554

555
  static dataApiClickHandler(event) {
556
    const target = getElementFromSelector(this)
Jacob Thornton's avatar
Jacob Thornton committed
557

XhmikosR's avatar
XhmikosR committed
558
    if (!target || !target.classList.contains(CLASS_NAME_CAROUSEL)) {
Johann-S's avatar
Johann-S committed
559
560
      return
    }
fat's avatar
fat committed
561

Johann-S's avatar
Johann-S committed
562
    const config = {
563
564
      ...Manipulator.getDataAttributes(target),
      ...Manipulator.getDataAttributes(this)
Johann-S's avatar
Johann-S committed
565
566
    }
    const slideIndex = this.getAttribute('data-slide-to')
fat's avatar
fat committed
567

Johann-S's avatar
Johann-S committed
568
569
570
571
    if (slideIndex) {
      config.interval = false
    }

572
    Carousel.carouselInterface(target, config)
fat's avatar
fat committed
573

Johann-S's avatar
Johann-S committed
574
    if (slideIndex) {
Johann-S's avatar
Johann-S committed
575
      Data.getData(target, DATA_KEY).to(slideIndex)
fat's avatar
fat committed
576
    }
Johann-S's avatar
Johann-S committed
577
578

    event.preventDefault()
fat's avatar
fat committed
579
  }
580

581
  static getInstance(element) {
582
583
    return Data.getData(element, DATA_KEY)
  }
Johann-S's avatar
Johann-S committed
584
}
fat's avatar
fat committed
585

Johann-S's avatar
Johann-S committed
586
587
588
589
590
/**
 * ------------------------------------------------------------------------
 * Data Api implementation
 * ------------------------------------------------------------------------
 */
fat's avatar
fat committed
591

XhmikosR's avatar
XhmikosR committed
592
EventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_SLIDE, Carousel.dataApiClickHandler)
fat's avatar
fat committed
593

XhmikosR's avatar
XhmikosR committed
594
EventHandler.on(window, EVENT_LOAD_DATA_API, () => {
595
596
  const carousels = SelectorEngine.find(SELECTOR_DATA_RIDE)

Johann-S's avatar
Johann-S committed
597
  for (let i = 0, len = carousels.length; i < len; i++) {
598
    Carousel.carouselInterface(carousels[i], Data.getData(carousels[i], DATA_KEY))
fat's avatar
fat committed
599
  }
Johann-S's avatar
Johann-S committed
600
601
})

602
603
const $ = getjQuery()

Johann-S's avatar
Johann-S committed
604
605
606
607
/**
 * ------------------------------------------------------------------------
 * jQuery
 * ------------------------------------------------------------------------
Johann-S's avatar
Johann-S committed
608
 * add .carousel to jQuery only if jQuery is present
Johann-S's avatar
Johann-S committed
609
 */
Johann-S's avatar
Johann-S committed
610
/* istanbul ignore if */
611
if ($) {
Johann-S's avatar
Johann-S committed
612
  const JQUERY_NO_CONFLICT = $.fn[NAME]
613
  $.fn[NAME] = Carousel.jQueryInterface
XhmikosR's avatar
XhmikosR committed
614
615
  $.fn[NAME].Constructor = Carousel
  $.fn[NAME].noConflict = () => {
Johann-S's avatar
Johann-S committed
616
    $.fn[NAME] = JQUERY_NO_CONFLICT
617
    return Carousel.jQueryInterface
Johann-S's avatar
Johann-S committed
618
  }
Johann-S's avatar
Johann-S committed
619
}
fat's avatar
fat committed
620
621

export default Carousel