codekingpro/portable-devtools
115k
1/**2 * --------------------------------------------------------------------------3 * Bootstrap (v4.6.2): carousel.js4 * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)5 * --------------------------------------------------------------------------6 */7 8import $ from 'jquery'9import Util from './util'10 11/**12 * Constants13 */14 15const NAME = 'carousel'16const VERSION = '4.6.2'17const DATA_KEY = 'bs.carousel'18const EVENT_KEY = `.${DATA_KEY}`19const DATA_API_KEY = '.data-api'20const JQUERY_NO_CONFLICT = $.fn[NAME]21const ARROW_LEFT_KEYCODE = 37 // KeyboardEvent.which value for left arrow key22const ARROW_RIGHT_KEYCODE = 39 // KeyboardEvent.which value for right arrow key23const TOUCHEVENT_COMPAT_WAIT = 500 // Time for mouse compat events to fire after touch24const SWIPE_THRESHOLD = 4025 26const CLASS_NAME_CAROUSEL = 'carousel'27const CLASS_NAME_ACTIVE = 'active'28const CLASS_NAME_SLIDE = 'slide'29const CLASS_NAME_RIGHT = 'carousel-item-right'30const CLASS_NAME_LEFT = 'carousel-item-left'31const CLASS_NAME_NEXT = 'carousel-item-next'32const CLASS_NAME_PREV = 'carousel-item-prev'33const CLASS_NAME_POINTER_EVENT = 'pointer-event'34 35const DIRECTION_NEXT = 'next'36const DIRECTION_PREV = 'prev'37const DIRECTION_LEFT = 'left'38const DIRECTION_RIGHT = 'right'39 40const EVENT_SLIDE = `slide${EVENT_KEY}`41const EVENT_SLID = `slid${EVENT_KEY}`42const EVENT_KEYDOWN = `keydown${EVENT_KEY}`43const EVENT_MOUSEENTER = `mouseenter${EVENT_KEY}`44const EVENT_MOUSELEAVE = `mouseleave${EVENT_KEY}`45const EVENT_TOUCHSTART = `touchstart${EVENT_KEY}`46const EVENT_TOUCHMOVE = `touchmove${EVENT_KEY}`47const EVENT_TOUCHEND = `touchend${EVENT_KEY}`48const EVENT_POINTERDOWN = `pointerdown${EVENT_KEY}`49const EVENT_POINTERUP = `pointerup${EVENT_KEY}`50const EVENT_DRAG_START = `dragstart${EVENT_KEY}`51const EVENT_LOAD_DATA_API = `load${EVENT_KEY}${DATA_API_KEY}`52const EVENT_CLICK_DATA_API = `click${EVENT_KEY}${DATA_API_KEY}`53 54const SELECTOR_ACTIVE = '.active'55const SELECTOR_ACTIVE_ITEM = '.active.carousel-item'56const SELECTOR_ITEM = '.carousel-item'57const SELECTOR_ITEM_IMG = '.carousel-item img'58const SELECTOR_NEXT_PREV = '.carousel-item-next, .carousel-item-prev'59const SELECTOR_INDICATORS = '.carousel-indicators'60const SELECTOR_DATA_SLIDE = '[data-slide], [data-slide-to]'61const SELECTOR_DATA_RIDE = '[data-ride="carousel"]'62 63const Default = {64 interval: 5000,65 keyboard: true,66 slide: false,67 pause: 'hover',68 wrap: true,69 touch: true70}71 72const DefaultType = {73 interval: '(number|boolean)',74 keyboard: 'boolean',75 slide: '(boolean|string)',76 pause: '(string|boolean)',77 wrap: 'boolean',78 touch: 'boolean'79}80 81const PointerType = {82 TOUCH: 'touch',83 PEN: 'pen'84}85 86/**87 * Class definition88 */89 90class Carousel {91 constructor(element, config) {92 this._items = null93 this._interval = null94 this._activeElement = null95 this._isPaused = false96 this._isSliding = false97 this.touchTimeout = null98 this.touchStartX = 099 this.touchDeltaX = 0100 101 this._config = this._getConfig(config)102 this._element = element103 this._indicatorsElement = this._element.querySelector(SELECTOR_INDICATORS)104 this._touchSupported = 'ontouchstart' in document.documentElement || navigator.maxTouchPoints > 0105 this._pointerEvent = Boolean(window.PointerEvent || window.MSPointerEvent)106 107 this._addEventListeners()108 }109 110 // Getters111 static get VERSION() {112 return VERSION113 }114 115 static get Default() {116 return Default117 }118 119 // Public120 next() {121 if (!this._isSliding) {122 this._slide(DIRECTION_NEXT)123 }124 }125 126 nextWhenVisible() {127 const $element = $(this._element)128 // Don't call next when the page isn't visible129 // or the carousel or its parent isn't visible130 if (!document.hidden &&131 ($element.is(':visible') && $element.css('visibility') !== 'hidden')) {132 this.next()133 }134 }135 136 prev() {137 if (!this._isSliding) {138 this._slide(DIRECTION_PREV)139 }140 }141 142 pause(event) {143 if (!event) {144 this._isPaused = true145 }146 147 if (this._element.querySelector(SELECTOR_NEXT_PREV)) {148 Util.triggerTransitionEnd(this._element)149 this.cycle(true)150 }151 152 clearInterval(this._interval)153 this._interval = null154 }155 156 cycle(event) {157 if (!event) {158 this._isPaused = false159 }160 161 if (this._interval) {162 clearInterval(this._interval)163 this._interval = null164 }165 166 if (this._config.interval && !this._isPaused) {167 this._updateInterval()168 169 this._interval = setInterval(170 (document.visibilityState ? this.nextWhenVisible : this.next).bind(this),171 this._config.interval172 )173 }174 }175 176 to(index) {177 this._activeElement = this._element.querySelector(SELECTOR_ACTIVE_ITEM)178 179 const activeIndex = this._getItemIndex(this._activeElement)180 181 if (index > this._items.length - 1 || index < 0) {182 return183 }184 185 if (this._isSliding) {186 $(this._element).one(EVENT_SLID, () => this.to(index))187 return188 }189 190 if (activeIndex === index) {191 this.pause()192 this.cycle()193 return194 }195 196 const direction = index > activeIndex ?197 DIRECTION_NEXT :198 DIRECTION_PREV199 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 = null208 this._config = null209 this._element = null210 this._interval = null211 this._isPaused = null212 this._isSliding = null213 this._activeElement = null214 this._indicatorsElement = null215 }216 217 // Private218 _getConfig(config) {219 config = {220 ...Default,221 ...config222 }223 Util.typeCheckConfig(NAME, config, DefaultType)224 return config225 }226 227 _handleSwipe() {228 const absDeltax = Math.abs(this.touchDeltaX)229 230 if (absDeltax <= SWIPE_THRESHOLD) {231 return232 }233 234 const direction = absDeltax / this.touchDeltaX235 236 this.touchDeltaX = 0237 238 // swipe left239 if (direction > 0) {240 this.prev()241 }242 243 // swipe right244 if (direction < 0) {245 this.next()246 }247 }248 249 _addEventListeners() {250 if (this._config.keyboard) {251 $(this._element).on(EVENT_KEYDOWN, event => this._keydown(event))252 }253 254 if (this._config.pause === 'hover') {255 $(this._element)256 .on(EVENT_MOUSEENTER, event => this.pause(event))257 .on(EVENT_MOUSELEAVE, event => this.cycle(event))258 }259 260 if (this._config.touch) {261 this._addTouchEventListeners()262 }263 }264 265 _addTouchEventListeners() {266 if (!this._touchSupported) {267 return268 }269 270 const start = event => {271 if (this._pointerEvent && PointerType[event.originalEvent.pointerType.toUpperCase()]) {272 this.touchStartX = event.originalEvent.clientX273 } else if (!this._pointerEvent) {274 this.touchStartX = event.originalEvent.touches[0].clientX275 }276 }277 278 const move = event => {279 // ensure swiping with one touch and not pinching280 this.touchDeltaX = event.originalEvent.touches && event.originalEvent.touches.length > 1 ?281 0 :282 event.originalEvent.touches[0].clientX - this.touchStartX283 }284 285 const end = event => {286 if (this._pointerEvent && PointerType[event.originalEvent.pointerType.toUpperCase()]) {287 this.touchDeltaX = event.originalEvent.clientX - this.touchStartX288 }289 290 this._handleSwipe()291 if (this._config.pause === 'hover') {292 // If it's a touch-enabled device, mouseenter/leave are fired as293 // part of the mouse compatibility events on first tap - the carousel294 // would stop cycling until user tapped out of it;295 // here, we listen for touchend, explicitly pause the carousel296 // (as if it's the second time we tap on it, mouseenter compat event297 // is NOT fired) and after a timeout (to allow for mouse compatibility298 // events to fire) we explicitly restart cycling299 300 this.pause()301 if (this.touchTimeout) {302 clearTimeout(this.touchTimeout)303 }304 305 this.touchTimeout = setTimeout(event => this.cycle(event), TOUCHEVENT_COMPAT_WAIT + this._config.interval)306 }307 }308 309 $(this._element.querySelectorAll(SELECTOR_ITEM_IMG))310 .on(EVENT_DRAG_START, e => e.preventDefault())311 312 if (this._pointerEvent) {313 $(this._element).on(EVENT_POINTERDOWN, event => start(event))314 $(this._element).on(EVENT_POINTERUP, event => end(event))315 316 this._element.classList.add(CLASS_NAME_POINTER_EVENT)317 } else {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))321 }322 }323 324 _keydown(event) {325 if (/input|textarea/i.test(event.target.tagName)) {326 return327 }328 329 switch (event.which) {330 case ARROW_LEFT_KEYCODE:331 event.preventDefault()332 this.prev()333 break334 case ARROW_RIGHT_KEYCODE:335 event.preventDefault()336 this.next()337 break338 default:339 }340 }341 342 _getItemIndex(element) {343 this._items = element && element.parentNode ?344 [].slice.call(element.parentNode.querySelectorAll(SELECTOR_ITEM)) :345 []346 return this._items.indexOf(element)347 }348 349 _getItemByDirection(direction, activeElement) {350 const isNextDirection = direction === DIRECTION_NEXT351 const isPrevDirection = direction === DIRECTION_PREV352 const activeIndex = this._getItemIndex(activeElement)353 const lastItemIndex = this._items.length - 1354 const isGoingToWrap = isPrevDirection && activeIndex === 0 ||355 isNextDirection && activeIndex === lastItemIndex356 357 if (isGoingToWrap && !this._config.wrap) {358 return activeElement359 }360 361 const delta = direction === DIRECTION_PREV ? -1 : 1362 const itemIndex = (activeIndex + delta) % this._items.length363 364 return itemIndex === -1 ?365 this._items[this._items.length - 1] : this._items[itemIndex]366 }367 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, {372 relatedTarget,373 direction: eventDirectionName,374 from: fromIndex,375 to: targetIndex376 })377 378 $(this._element).trigger(slideEvent)379 380 return slideEvent381 }382 383 _setActiveIndicatorElement(element) {384 if (this._indicatorsElement) {385 const indicators = [].slice.call(this._indicatorsElement.querySelectorAll(SELECTOR_ACTIVE))386 $(indicators).removeClass(CLASS_NAME_ACTIVE)387 388 const nextIndicator = this._indicatorsElement.children[389 this._getItemIndex(element)390 ]391 392 if (nextIndicator) {393 $(nextIndicator).addClass(CLASS_NAME_ACTIVE)394 }395 }396 }397 398 _updateInterval() {399 const element = this._activeElement || this._element.querySelector(SELECTOR_ACTIVE_ITEM)400 401 if (!element) {402 return403 }404 405 const elementInterval = parseInt(element.getAttribute('data-interval'), 10)406 407 if (elementInterval) {408 this._config.defaultInterval = this._config.defaultInterval || this._config.interval409 this._config.interval = elementInterval410 } else {411 this._config.interval = this._config.defaultInterval || this._config.interval412 }413 }414 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)422 423 let directionalClassName424 let orderClassName425 let eventDirectionName426 427 if (direction === DIRECTION_NEXT) {428 directionalClassName = CLASS_NAME_LEFT429 orderClassName = CLASS_NAME_NEXT430 eventDirectionName = DIRECTION_LEFT431 } else {432 directionalClassName = CLASS_NAME_RIGHT433 orderClassName = CLASS_NAME_PREV434 eventDirectionName = DIRECTION_RIGHT435 }436 437 if (nextElement && $(nextElement).hasClass(CLASS_NAME_ACTIVE)) {438 this._isSliding = false439 return440 }441 442 const slideEvent = this._triggerSlideEvent(nextElement, eventDirectionName)443 if (slideEvent.isDefaultPrevented()) {444 return445 }446 447 if (!activeElement || !nextElement) {448 // Some weirdness is happening, so we bail449 return450 }451 452 this._isSliding = true453 454 if (isCycling) {455 this.pause()456 }457 458 this._setActiveIndicatorElement(nextElement)459 this._activeElement = nextElement460 461 const slidEvent = $.Event(EVENT_SLID, {462 relatedTarget: nextElement,463 direction: eventDirectionName,464 from: activeElementIndex,465 to: nextElementIndex466 })467 468 if ($(this._element).hasClass(CLASS_NAME_SLIDE)) {469 $(nextElement).addClass(orderClassName)470 471 Util.reflow(nextElement)472 473 $(activeElement).addClass(directionalClassName)474 $(nextElement).addClass(directionalClassName)475 476 const transitionDuration = Util.getTransitionDurationFromElement(activeElement)477 478 $(activeElement)479 .one(Util.TRANSITION_END, () => {480 $(nextElement)481 .removeClass(`${directionalClassName} ${orderClassName}`)482 .addClass(CLASS_NAME_ACTIVE)483 484 $(activeElement).removeClass(`${CLASS_NAME_ACTIVE} ${orderClassName} ${directionalClassName}`)485 486 this._isSliding = false487 488 setTimeout(() => $(this._element).trigger(slidEvent), 0)489 })490 .emulateTransitionEnd(transitionDuration)491 } else {492 $(activeElement).removeClass(CLASS_NAME_ACTIVE)493 $(nextElement).addClass(CLASS_NAME_ACTIVE)494 495 this._isSliding = false496 $(this._element).trigger(slidEvent)497 }498 499 if (isCycling) {500 this.cycle()501 }502 }503 504 // Static505 static _jQueryInterface(config) {506 return this.each(function () {507 let data = $(this).data(DATA_KEY)508 let _config = {509 ...Default,510 ...$(this).data()511 }512 513 if (typeof config === 'object') {514 _config = {515 ..._config,516 ...config517 }518 }519 520 const action = typeof config === 'string' ? config : _config.slide521 522 if (!data) {523 data = new Carousel(this, _config)524 $(this).data(DATA_KEY, data)525 }526 527 if (typeof config === 'number') {528 data.to(config)529 } else if (typeof action === 'string') {530 if (typeof data[action] === 'undefined') {531 throw new TypeError(`No method named "${action}"`)532 }533 534 data[action]()535 } else if (_config.interval && _config.ride) {536 data.pause()537 data.cycle()538 }539 })540 }541 542 static _dataApiClickHandler(event) {543 const selector = Util.getSelectorFromElement(this)544 545 if (!selector) {546 return547 }548 549 const target = $(selector)[0]550 551 if (!target || !$(target).hasClass(CLASS_NAME_CAROUSEL)) {552 return553 }554 555 const config = {556 ...$(target).data(),557 ...$(this).data()558 }559 const slideIndex = this.getAttribute('data-slide-to')560 561 if (slideIndex) {562 config.interval = false563 }564 565 Carousel._jQueryInterface.call($(target), config)566 567 if (slideIndex) {568 $(target).data(DATA_KEY).to(slideIndex)569 }570 571 event.preventDefault()572 }573}574 575/**576 * Data API implementation577 */578 579$(document).on(EVENT_CLICK_DATA_API, SELECTOR_DATA_SLIDE, Carousel._dataApiClickHandler)580 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())586 }587})588 589/**590 * jQuery591 */592 593$.fn[NAME] = Carousel._jQueryInterface594$.fn[NAME].Constructor = Carousel595$.fn[NAME].noConflict = () => {596 $.fn[NAME] = JQUERY_NO_CONFLICT597 return Carousel._jQueryInterface598}599 600export default Carousel601 