]>
Commit | Line | Data |
---|---|---|
91e44d91 S |
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 |