]> git.ipfire.org Git - ipfire.org.git/blob - src/scss/bootstrap-4.0.0-alpha.6/js/src/carousel.js
.gitignore: Add .vscode
[ipfire.org.git] / src / scss / bootstrap-4.0.0-alpha.6 / js / src / carousel.js
1 import Util from './util'
2
3
4 /**
5 * --------------------------------------------------------------------------
6 * Bootstrap (v4.0.0-alpha.6): carousel.js
7 * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
8 * --------------------------------------------------------------------------
9 */
10
11 const Carousel = (($) => {
12
13
14 /**
15 * ------------------------------------------------------------------------
16 * Constants
17 * ------------------------------------------------------------------------
18 */
19
20 const NAME = 'carousel'
21 const VERSION = '4.0.0-alpha.6'
22 const DATA_KEY = 'bs.carousel'
23 const EVENT_KEY = `.${DATA_KEY}`
24 const DATA_API_KEY = '.data-api'
25 const JQUERY_NO_CONFLICT = $.fn[NAME]
26 const TRANSITION_DURATION = 600
27 const ARROW_LEFT_KEYCODE = 37 // KeyboardEvent.which value for left arrow key
28 const ARROW_RIGHT_KEYCODE = 39 // KeyboardEvent.which value for right arrow key
29
30 const Default = {
31 interval : 5000,
32 keyboard : true,
33 slide : false,
34 pause : 'hover',
35 wrap : true
36 }
37
38 const DefaultType = {
39 interval : '(number|boolean)',
40 keyboard : 'boolean',
41 slide : '(boolean|string)',
42 pause : '(string|boolean)',
43 wrap : 'boolean'
44 }
45
46 const Direction = {
47 NEXT : 'next',
48 PREV : 'prev',
49 LEFT : 'left',
50 RIGHT : 'right'
51 }
52
53 const Event = {
54 SLIDE : `slide${EVENT_KEY}`,
55 SLID : `slid${EVENT_KEY}`,
56 KEYDOWN : `keydown${EVENT_KEY}`,
57 MOUSEENTER : `mouseenter${EVENT_KEY}`,
58 MOUSELEAVE : `mouseleave${EVENT_KEY}`,
59 LOAD_DATA_API : `load${EVENT_KEY}${DATA_API_KEY}`,
60 CLICK_DATA_API : `click${EVENT_KEY}${DATA_API_KEY}`
61 }
62
63 const ClassName = {
64 CAROUSEL : 'carousel',
65 ACTIVE : 'active',
66 SLIDE : 'slide',
67 RIGHT : 'carousel-item-right',
68 LEFT : 'carousel-item-left',
69 NEXT : 'carousel-item-next',
70 PREV : 'carousel-item-prev',
71 ITEM : 'carousel-item'
72 }
73
74 const Selector = {
75 ACTIVE : '.active',
76 ACTIVE_ITEM : '.active.carousel-item',
77 ITEM : '.carousel-item',
78 NEXT_PREV : '.carousel-item-next, .carousel-item-prev',
79 INDICATORS : '.carousel-indicators',
80 DATA_SLIDE : '[data-slide], [data-slide-to]',
81 DATA_RIDE : '[data-ride="carousel"]'
82 }
83
84
85 /**
86 * ------------------------------------------------------------------------
87 * Class Definition
88 * ------------------------------------------------------------------------
89 */
90
91 class Carousel {
92
93 constructor(element, config) {
94 this._items = null
95 this._interval = null
96 this._activeElement = null
97
98 this._isPaused = false
99 this._isSliding = false
100
101 this._config = this._getConfig(config)
102 this._element = $(element)[0]
103 this._indicatorsElement = $(this._element).find(Selector.INDICATORS)[0]
104
105 this._addEventListeners()
106 }
107
108
109 // getters
110
111 static get VERSION() {
112 return VERSION
113 }
114
115 static get Default() {
116 return Default
117 }
118
119
120 // public
121
122 next() {
123 if (this._isSliding) {
124 throw new Error('Carousel is sliding')
125 }
126 this._slide(Direction.NEXT)
127 }
128
129 nextWhenVisible() {
130 // Don't call next when the page isn't visible
131 if (!document.hidden) {
132 this.next()
133 }
134 }
135
136 prev() {
137 if (this._isSliding) {
138 throw new Error('Carousel is sliding')
139 }
140 this._slide(Direction.PREVIOUS)
141 }
142
143 pause(event) {
144 if (!event) {
145 this._isPaused = true
146 }
147
148 if ($(this._element).find(Selector.NEXT_PREV)[0] &&
149 Util.supportsTransitionEnd()) {
150 Util.triggerTransitionEnd(this._element)
151 this.cycle(true)
152 }
153
154 clearInterval(this._interval)
155 this._interval = null
156 }
157
158 cycle(event) {
159 if (!event) {
160 this._isPaused = false
161 }
162
163 if (this._interval) {
164 clearInterval(this._interval)
165 this._interval = null
166 }
167
168 if (this._config.interval && !this._isPaused) {
169 this._interval = setInterval(
170 (document.visibilityState ? this.nextWhenVisible : this.next).bind(this),
171 this._config.interval
172 )
173 }
174 }
175
176 to(index) {
177 this._activeElement = $(this._element).find(Selector.ACTIVE_ITEM)[0]
178
179 const activeIndex = this._getItemIndex(this._activeElement)
180
181 if (index > this._items.length - 1 || index < 0) {
182 return
183 }
184
185 if (this._isSliding) {
186 $(this._element).one(Event.SLID, () => this.to(index))
187 return
188 }
189
190 if (activeIndex === index) {
191 this.pause()
192 this.cycle()
193 return
194 }
195
196 const direction = index > activeIndex ?
197 Direction.NEXT :
198 Direction.PREVIOUS
199
200 this._slide(direction, this._items[index])
201 }
202
203 dispose() {
204 $(this._element).off(EVENT_KEY)
205 $.removeData(this._element, DATA_KEY)
206
207 this._items = null
208 this._config = null
209 this._element = null
210 this._interval = null
211 this._isPaused = null
212 this._isSliding = null
213 this._activeElement = null
214 this._indicatorsElement = null
215 }
216
217
218 // private
219
220 _getConfig(config) {
221 config = $.extend({}, Default, config)
222 Util.typeCheckConfig(NAME, config, DefaultType)
223 return config
224 }
225
226 _addEventListeners() {
227 if (this._config.keyboard) {
228 $(this._element)
229 .on(Event.KEYDOWN, (event) => this._keydown(event))
230 }
231
232 if (this._config.pause === 'hover' &&
233 !('ontouchstart' in document.documentElement)) {
234 $(this._element)
235 .on(Event.MOUSEENTER, (event) => this.pause(event))
236 .on(Event.MOUSELEAVE, (event) => this.cycle(event))
237 }
238 }
239
240 _keydown(event) {
241 if (/input|textarea/i.test(event.target.tagName)) {
242 return
243 }
244
245 switch (event.which) {
246 case ARROW_LEFT_KEYCODE:
247 event.preventDefault()
248 this.prev()
249 break
250 case ARROW_RIGHT_KEYCODE:
251 event.preventDefault()
252 this.next()
253 break
254 default:
255 return
256 }
257 }
258
259 _getItemIndex(element) {
260 this._items = $.makeArray($(element).parent().find(Selector.ITEM))
261 return this._items.indexOf(element)
262 }
263
264 _getItemByDirection(direction, activeElement) {
265 const isNextDirection = direction === Direction.NEXT
266 const isPrevDirection = direction === Direction.PREVIOUS
267 const activeIndex = this._getItemIndex(activeElement)
268 const lastItemIndex = this._items.length - 1
269 const isGoingToWrap = isPrevDirection && activeIndex === 0 ||
270 isNextDirection && activeIndex === lastItemIndex
271
272 if (isGoingToWrap && !this._config.wrap) {
273 return activeElement
274 }
275
276 const delta = direction === Direction.PREVIOUS ? -1 : 1
277 const itemIndex = (activeIndex + delta) % this._items.length
278
279 return itemIndex === -1 ?
280 this._items[this._items.length - 1] : this._items[itemIndex]
281 }
282
283
284 _triggerSlideEvent(relatedTarget, eventDirectionName) {
285 const slideEvent = $.Event(Event.SLIDE, {
286 relatedTarget,
287 direction: eventDirectionName
288 })
289
290 $(this._element).trigger(slideEvent)
291
292 return slideEvent
293 }
294
295 _setActiveIndicatorElement(element) {
296 if (this._indicatorsElement) {
297 $(this._indicatorsElement)
298 .find(Selector.ACTIVE)
299 .removeClass(ClassName.ACTIVE)
300
301 const nextIndicator = this._indicatorsElement.children[
302 this._getItemIndex(element)
303 ]
304
305 if (nextIndicator) {
306 $(nextIndicator).addClass(ClassName.ACTIVE)
307 }
308 }
309 }
310
311 _slide(direction, element) {
312 const activeElement = $(this._element).find(Selector.ACTIVE_ITEM)[0]
313 const nextElement = element || activeElement &&
314 this._getItemByDirection(direction, activeElement)
315
316 const isCycling = Boolean(this._interval)
317
318 let directionalClassName
319 let orderClassName
320 let eventDirectionName
321
322 if (direction === Direction.NEXT) {
323 directionalClassName = ClassName.LEFT
324 orderClassName = ClassName.NEXT
325 eventDirectionName = Direction.LEFT
326 } else {
327 directionalClassName = ClassName.RIGHT
328 orderClassName = ClassName.PREV
329 eventDirectionName = Direction.RIGHT
330 }
331
332 if (nextElement && $(nextElement).hasClass(ClassName.ACTIVE)) {
333 this._isSliding = false
334 return
335 }
336
337 const slideEvent = this._triggerSlideEvent(nextElement, eventDirectionName)
338 if (slideEvent.isDefaultPrevented()) {
339 return
340 }
341
342 if (!activeElement || !nextElement) {
343 // some weirdness is happening, so we bail
344 return
345 }
346
347 this._isSliding = true
348
349 if (isCycling) {
350 this.pause()
351 }
352
353 this._setActiveIndicatorElement(nextElement)
354
355 const slidEvent = $.Event(Event.SLID, {
356 relatedTarget: nextElement,
357 direction: eventDirectionName
358 })
359
360 if (Util.supportsTransitionEnd() &&
361 $(this._element).hasClass(ClassName.SLIDE)) {
362
363 $(nextElement).addClass(orderClassName)
364
365 Util.reflow(nextElement)
366
367 $(activeElement).addClass(directionalClassName)
368 $(nextElement).addClass(directionalClassName)
369
370 $(activeElement)
371 .one(Util.TRANSITION_END, () => {
372 $(nextElement)
373 .removeClass(`${directionalClassName} ${orderClassName}`)
374 .addClass(ClassName.ACTIVE)
375
376 $(activeElement).removeClass(`${ClassName.ACTIVE} ${orderClassName} ${directionalClassName}`)
377
378 this._isSliding = false
379
380 setTimeout(() => $(this._element).trigger(slidEvent), 0)
381
382 })
383 .emulateTransitionEnd(TRANSITION_DURATION)
384
385 } else {
386 $(activeElement).removeClass(ClassName.ACTIVE)
387 $(nextElement).addClass(ClassName.ACTIVE)
388
389 this._isSliding = false
390 $(this._element).trigger(slidEvent)
391 }
392
393 if (isCycling) {
394 this.cycle()
395 }
396 }
397
398
399 // static
400
401 static _jQueryInterface(config) {
402 return this.each(function () {
403 let data = $(this).data(DATA_KEY)
404 const _config = $.extend({}, Default, $(this).data())
405
406 if (typeof config === 'object') {
407 $.extend(_config, config)
408 }
409
410 const action = typeof config === 'string' ? config : _config.slide
411
412 if (!data) {
413 data = new Carousel(this, _config)
414 $(this).data(DATA_KEY, data)
415 }
416
417 if (typeof config === 'number') {
418 data.to(config)
419 } else if (typeof action === 'string') {
420 if (data[action] === undefined) {
421 throw new Error(`No method named "${action}"`)
422 }
423 data[action]()
424 } else if (_config.interval) {
425 data.pause()
426 data.cycle()
427 }
428 })
429 }
430
431 static _dataApiClickHandler(event) {
432 const selector = Util.getSelectorFromElement(this)
433
434 if (!selector) {
435 return
436 }
437
438 const target = $(selector)[0]
439
440 if (!target || !$(target).hasClass(ClassName.CAROUSEL)) {
441 return
442 }
443
444 const config = $.extend({}, $(target).data(), $(this).data())
445 const slideIndex = this.getAttribute('data-slide-to')
446
447 if (slideIndex) {
448 config.interval = false
449 }
450
451 Carousel._jQueryInterface.call($(target), config)
452
453 if (slideIndex) {
454 $(target).data(DATA_KEY).to(slideIndex)
455 }
456
457 event.preventDefault()
458 }
459
460 }
461
462
463 /**
464 * ------------------------------------------------------------------------
465 * Data Api implementation
466 * ------------------------------------------------------------------------
467 */
468
469 $(document)
470 .on(Event.CLICK_DATA_API, Selector.DATA_SLIDE, Carousel._dataApiClickHandler)
471
472 $(window).on(Event.LOAD_DATA_API, () => {
473 $(Selector.DATA_RIDE).each(function () {
474 const $carousel = $(this)
475 Carousel._jQueryInterface.call($carousel, $carousel.data())
476 })
477 })
478
479
480 /**
481 * ------------------------------------------------------------------------
482 * jQuery
483 * ------------------------------------------------------------------------
484 */
485
486 $.fn[NAME] = Carousel._jQueryInterface
487 $.fn[NAME].Constructor = Carousel
488 $.fn[NAME].noConflict = function () {
489 $.fn[NAME] = JQUERY_NO_CONFLICT
490 return Carousel._jQueryInterface
491 }
492
493 return Carousel
494
495 })(jQuery)
496
497 export default Carousel