PageSourceSearch

https://cdn.deepdreamgenerator.com/vendor/Blueimp-Gallery-3.3.0/js/blueimp-gallery.js

js deepdreamgenerator.com collected 2026-09-24 09:33:58 UTC 50,273 bytes, 1,551 lines download raw bytes

vendor: 50,273 bytes, lines 1-1551
1/*
2 * blueimp Gallery JS
3 * https://github.com/blueimp/Gallery
4 *
5 * Copyright 2013, Sebastian Tschan
6 * https://blueimp.net
7 *
8 * Swipe implementation based on
9 * https://github.com/bradbirdsall/Swipe
10 *
11 * Licensed under the MIT license:
12 * https://opensource.org/licenses/MIT
13 */
14
15/* global define, DocumentTouch */
16
17/* eslint-disable no-param-reassign */
18
19;(function (factory) {
20  'use strict'
21  if (typeof define === 'function' && define.amd) {
22    // Register as an anonymous AMD module:
23    define(['./blueimp-helper'], factory)
24  } else {
25    // Browser globals:
26    window.blueimp = window.blueimp || {}
27    window.blueimp.Gallery = factory(window.blueimp.helper || window.jQuery)
28  }
29})(function ($) {
30  'use strict'
31
32  /**
33   * Gallery constructor
34   *
35   * @class
36   * @param {Array|NodeList} list Gallery content
37   * @param {object} [options] Gallery options
38   * @returns {object} Gallery object
39   */
40  function Gallery(list, options) {
41    if (document.body.style.maxHeight === undefined) {
42      // document.body.style.maxHeight is undefined on IE6 and lower
43      return null
44    }
45    if (!this || this.options !== Gallery.prototype.options) {
46      // Called as function instead of as constructor,
47      // so we simply return a new instance:
48      return new Gallery(list, options)
49    }
50    if (!list || !list.length) {
51      this.console.log(
52        'blueimp Gallery: No or empty list provided as first argument.',
53        list
54      )
55      return
56    }
57    this.list = list
58    this.num = list.length
59    this.initOptions(options)
60    this.initialize()
61  }
62
63  $.extend(Gallery.prototype, {
64    options: {
65      // The Id, element or querySelector of the gallery widget:
66      container: '#blueimp-gallery',
67      // The tag name, Id, element or querySelector of the slides container:
68      slidesContainer: 'div',
69      // The tag name, Id, element or querySelector of the title element:
70      titleElement: 'h3',
71      // The class to add when the gallery is visible:
72      displayClass: 'blueimp-gallery-display',
73      // The class to add when the gallery controls are visible:
74      controlsClass: 'blueimp-gallery-controls',
75      // The class to add when the gallery only displays one element:
76      singleClass: 'blueimp-gallery-single',
77      // The class to add when the left edge has been reached:
78      leftEdgeClass: 'blueimp-gallery-left',
79      // The class to add when the right edge has been reached:
80      rightEdgeClass: 'blueimp-gallery-right',
81      // The class to add when the automatic slideshow is active:
82      playingClass: 'blueimp-gallery-playing',
83      // The class to add when the browser supports SVG as img (or background):
84      svgasimgClass: 'blueimp-gallery-svgasimg',
85      // The class to add when the browser supports SMIL (animated SVGs):
86      smilClass: 'blueimp-gallery-smil',
87      // The class for all slides:
88      slideClass: 'slide',
89      // The slide class for the active (current index) slide:
90      slideActiveClass: 'slide-active',
91      // The slide class for the previous (before current index) slide:
92      slidePrevClass: 'slide-prev',
93      // The slide class for the next (after current index) slide:
94      slideNextClass: 'slide-next',
95      // The slide class for loading elements:
96      slideLoadingClass: 'slide-loading',
97      // The slide class for elements that failed to load:
98      slideErrorClass: 'slide-error',
99      // The class for the content element loaded into each slide:
100      slideContentClass: 'slide-content',
101      // The class for the "toggle" control:
102      toggleClass: 'toggle',
103      // The class for the "prev" control:
104      prevClass: 'prev',
105      // The class for the "next" control:
106      nextClass: 'next',
107      // The class for the "close" control:
108      closeClass: 'close',
109      // The class for the "play-pause" toggle control:
110      playPauseClass: 'play-pause',
111      // The list object property (or data attribute) with the object type:
112      typeProperty: 'type',
113      // The list object property (or data attribute) with the object title:
114      titleProperty: 'title',
115      // The list object property (or data attribute) with the object alt text:
116      altTextProperty: 'alt',
117      // The list object property (or data attribute) with the object URL:
118      urlProperty: 'href',
119      // The list object property (or data attribute) with the object srcset:
120      srcsetProperty: 'srcset',
121      // The list object property (or data attribute) with the object sizes:
122      sizesProperty: 'sizes',
123      // The list object property (or data attribute) with the object sources:
124      sourcesProperty: 'sources',
125      // The gallery listens for transitionend events before triggering the
126      // opened and closed events, unless the following option is set to false:
127      displayTransition: true,
128      // Defines if the gallery slides are cleared from the gallery modal,
129      // or reused for the next gallery initialization:
130      clearSlides: true,
131      // Toggle the controls on pressing the Enter key:
132      toggleControlsOnEnter: true,
133      // Toggle the controls on slide click:
134      toggleControlsOnSlideClick: true,
135      // Toggle the automatic slideshow interval on pressing the Space key:
136      toggleSlideshowOnSpace: true,
137      // Navigate the gallery by pressing the ArrowLeft and ArrowRight keys:
138      enableKeyboardNavigation: true,
139      // Close the gallery on pressing the Escape key:
140      closeOnEscape: true,
141      // Close the gallery when clicking on an empty slide area:
142      closeOnSlideClick: true,
143      // Close the gallery by swiping up or down:
144      closeOnSwipeUpOrDown: true,
145      // Close the gallery when the URL hash changes:
146      closeOnHashChange: true,
147      // Emulate touch events on mouse-pointer devices such as desktop browsers:
148      emulateTouchEvents: true,
149      // Stop touch events from bubbling up to ancestor elements of the Gallery:
150      stopTouchEventsPropagation: false,
151      // Hide the page scrollbars:
152      hidePageScrollbars: true,
153      // Stops any touches on the container from scrolling the page:
154      disableScroll: true,
155      // Carousel mode (shortcut for carousel specific options):
156      carousel: false,
157      // Allow continuous navigation, moving from last to first
158      // and from first to last slide:
159      continuous: true,
160      // Remove elements outside of the preload range from the DOM:
161      unloadElements: true,
162      // Start with the automatic slideshow:
163      startSlideshow: false,
164      // Delay in milliseconds between slides for the automatic slideshow:
165      slideshowInterval: 5000,
166      // The direction the slides are moving: ltr=LeftToRight or rtl=RightToLeft
167      slideshowDirection: 'ltr',
168      // The starting index as integer.
169      // Can also be an object of the given list,
170      // or an equal object with the same url property:
171      index: 0,
172      // The number of elements to load around the current index:
173      preloadRange: 2,
174      // The transition duration between slide changes in milliseconds:
175      transitionDuration: 300,
176      // The transition duration for automatic slide changes, set to an integer
177      // greater 0 to override the default transition duration:
178      slideshowTransitionDuration: 500,
179      // The event object for which the default action will be canceled
180      // on Gallery initialization (e.g. the click event to open the Gallery):
181      event: undefined,
182      // Callback function executed when the Gallery is initialized.
183      // Is called with the gallery instance as "this" object:
184      onopen: undefined,
185      // Callback function executed when the Gallery has been initialized
186      // and the initialization transition has been completed.
187      // Is called with the gallery instance as "this" object:
188      onopened: undefined,
189      // Callback function executed on slide change.
190      // Is called with the gallery instance as "this" object and the
191      // current index and slide as arguments:
192      onslide: undefined,
193      // Callback function executed after the slide change transition.
194      // Is called with the gallery instance as "this" object and the
195      // current index and slide as arguments:
196      onslideend: undefined,
197      // Callback function executed on slide content load.
198      // Is called with the gallery instance as "this" object and the
199      // slide index and slide element as arguments:
200      onslidecomplete: undefined,
201      // Callback function executed when the Gallery is about to be closed.
202      // Is called with the gallery instance as "this" object:
203      onclose: undefined,
204      // Callback function executed when the Gallery has been closed
205      // and the closing transition has been completed.
206      // Is called with the gallery instance as "this" object:
207      onclosed: undefined
208    },
209
210    carouselOptions: {
211      hidePageScrollbars: false,
212      toggleControlsOnEnter: false,
213      toggleSlideshowOnSpace: false,
214      enableKeyboardNavigation: false,
215      closeOnEscape: false,
216      closeOnSlideClick: false,
217      closeOnSwipeUpOrDown: false,
218      closeOnHashChange: false,
219      disableScroll: false,
220      startSlideshow: true
221    },
222
223    console:
224      window.console && typeof window.console.log === 'function'
225        ? window.console
226        : { log: function () {} },
227
228    // Detect touch, transition, transform and background-size support:
229    support: (function (element) {
230      var support = {
231        source: !!window.HTMLSourceElement,
232        picture: !!window.HTMLPictureElement,
233        svgasimg: document.implementation.hasFeature(
234          'http://www.w3.org/TR/SVG11/feature#Image',
235          '1.1'
236        ),
237        smil:
238          !!document.createElementNS &&
239          /SVGAnimate/.test(
240            document
241              .createElementNS('http://www.w3.org/2000/svg', 'animate')
242              .toString()
243          ),
244        touch:
245          window.ontouchstart !== undefined ||
246          (window.DocumentTouch && document instanceof DocumentTouch)
247      }
248      var transitions = {
249        webkitTransition: {
250          end: 'webkitTransitionEnd',
251          prefix: '-webkit-'
252        },
253        MozTransition: {
254          end: 'transitionend',
255          prefix: '-moz-'
256        },
257        OTransition: {
258          end: 'otransitionend',
259          prefix: '-o-'
260        },
261        transition: {
262          end: 'transitionend',
263          prefix: ''
264        }
265      }
266      var prop
267      for (prop in transitions) {
268        if (
269          Object.prototype.hasOwnProperty.call(transitions, prop) &&
270          element.style[prop] !== undefined
271        ) {
272          support.transition = transitions[prop]
273          support.transition.name = prop
274          break
275        }
276      }
277      /**
278       * Tests browser support
279       */
280      function elementTests() {
281        var transition = support.transition
282        var prop
283        var translateZ
284        document.body.appendChild(element)
285        if (transition) {
286          prop = transition.name.slice(0, -9) + 'ransform'
287          if (element.style[prop] !== undefined) {
288            element.style[prop] = 'translateZ(0)'
289            translateZ = window
290              .getComputedStyle(element)
291              .getPropertyValue(transition.prefix + 'transform')
292            support.transform = {
293              prefix: transition.prefix,
294              name: prop,
295              translate: true,
296              translateZ: !!translateZ && translateZ !== 'none'
297            }
298          }
299        }
300        document.body.removeChild(element)
301      }
302      if (document.body) {
303        elementTests()
304      } else {
305        $(document).on('DOMContentLoaded', elementTests)
306      }
307      return support
308      // Test element, has to be standard HTML and must not be hidden
309      // for the CSS3 tests using window.getComputedStyle to be applicable:
310    })(document.createElement('div')),
311
312    requestAnimationFrame:
313      window.requestAnimationFrame ||
314      window.webkitRequestAnimationFrame ||
315      window.mozRequestAnimationFrame,
316
317    cancelAnimationFrame:
318      window.cancelAnimationFrame ||
319      window.webkitCancelRequestAnimationFrame ||
320      window.webkitCancelAnimationFrame ||
321      window.mozCancelAnimationFrame,
322
323    initialize: function () {
324      this.initStartIndex()
325      if (this.initWidget() === false) {
326        return false
327      }
328      this.initEventListeners()
329      // Load the slide at the given index:
330      this.onslide(this.index)
331      // Manually trigger the slideend event for the initial slide:
332      this.ontransitionend()
333      // Start the automatic slideshow if applicable:
334      if (this.options.startSlideshow) {
335        this.play()
336      }
337    },
338
339    slide: function (to, duration) {
340      window.clearTimeout(this.timeout)
341      var index = this.index
342      var direction
343      var naturalDirection
344      var diff
345      if (index === to || this.num === 1) {
346        return
347      }
348      if (!duration) {
349        duration = this.options.transitionDuration
350      }
351      if (this.support.transform) {
352        if (!this.options.continuous) {
353          to = this.circle(to)
354        }
355        // 1: backward, -1: forward:
356        direction = Math.abs(index - to) / (index - to)
357        // Get the actual position of the slide:
358        if (this.options.continuous) {
359          naturalDirection = direction
360          direction = -this.positions[this.circle(to)] / this.slideWidth
361          // If going forward but to < index, use to = slides.length + to
362          // If going backward but to > index, use to = -slides.length + to
363          if (direction !== naturalDirection) {
364            to = -direction * this.num + to
365          }
366        }
367        diff = Math.abs(index - to) - 1
368        // Move all the slides between index and to in the right direction:
369        while (diff) {
370          diff -= 1
371          this.move(
372            this.circle((to > index ? to : index) - diff - 1),
373            this.slideWidth * direction,
374            0
375          )
376        }
377        to = this.circle(to)
378        this.move(index, this.slideWidth * direction, duration)
379        this.move(to, 0, duration)
380        if (this.options.continuous) {
381          this.move(
382            this.circle(to - direction),
383            -(this.slideWidth * direction),
384            0
385          )
386        }
387      } else {
388        to = this.circle(to)
389        this.animate(index * -this.slideWidth, to * -this.slideWidth, duration)
390      }
391      this.onslide(to)
392    },
393
394    getIndex: function () {
395      return this.index
396    },
397
398    getNumber: function () {
399      return this.num
400    },
401
402    prev: function () {
403      if (this.options.continuous || this.index) {
404        this.slide(this.index - 1)
405      }
406    },
407
408    next: function () {
409      if (this.options.continuous || this.index < this.num - 1) {
410        this.slide(this.index + 1)
411      }
412    },
413
414    play: function (time) {
415      var that = this
416      var nextIndex =
417        this.index + (this.options.slideshowDirection === 'rtl' ? -1 : 1)
418      window.clearTimeout(this.timeout)
419      this.interval = time || this.options.slideshowInterval
420      if (this.elements[this.index] > 1) {
421        this.timeout = this.setTimeout(
422          (!this.requestAnimationFrame && this.slide) ||
423            function (to, duration) {
424              that.animationFrameId = that.requestAnimationFrame.call(
425                window,
426                function () {
427                  that.slide(to, duration)
428                }
429              )
430            },
431          [nextIndex, this.options.slideshowTransitionDuration],
432          this.interval
433        )
434      }
435      this.container.addClass(this.options.playingClass)
436      this.slidesContainer[0].setAttribute('aria-live', 'off')
437      if (this.playPauseElement.length) {
438        this.playPauseElement[0].setAttribute('aria-pressed', 'true')
439      }
440    },
441
442    pause: function () {
443      window.clearTimeout(this.timeout)
444      this.interval = null
445      if (this.cancelAnimationFrame) {
446        this.cancelAnimationFrame.call(window, this.animationFrameId)
447        this.animationFrameId = null
448      }
449      this.container.removeClass(this.options.playingClass)
450      this.slidesContainer[0].setAttribute('aria-live', 'polite')
451      if (this.playPauseElement.length) {
452        this.playPauseElement[0].setAttribute('aria-pressed', 'false')
453      }
454    },
455
456    add: function (list) {
457      var i
458      if (!list.concat) {
459        // Make a real array out of the list to add:
460        list = Array.prototype.slice.call(list)
461      }
462      if (!this.list.concat) {
463        // Make a real array out of the Gallery list:
464        this.list = Array.prototype.slice.call(this.list)
465      }
466      this.list = this.list.concat(list)
467      this.num = this.list.length
468      if (this.num > 2 && this.options.continuous === null) {
469        this.options.continuous = true
470        this.container.removeClass(this.options.leftEdgeClass)
471      }
472      this.container
473        .removeClass(this.options.rightEdgeClass)
474        .removeClass(this.options.singleClass)
475      for (i = this.num - list.length; i < this.num; i += 1) {
476        this.addSlide(i)
477        this.positionSlide(i)
478      }
479      this.positions.length = this.num
480      this.initSlides(true)
481    },
482
483    resetSlides: function () {
484      this.slidesContainer.empty()
485      this.unloadAllSlides()
486      this.slides = []
487    },
488
489    handleClose: function () {
490      var options = this.options
491      this.destroyEventListeners()
492      // Cancel the slideshow:
493      this.pause()
494      this.container[0].style.display = 'none'
495      this.container
496        .removeClass(options.displayClass)
497        .removeClass(options.singleClass)
498        .removeClass(options.leftEdgeClass)
499        .removeClass(options.rightEdgeClass)
500      if (options.hidePageScrollbars) {
501        document.body.style.overflow = this.bodyOverflowStyle
502      }
503      if (this.options.clearSlides) {
504        this.resetSlides()
505      }
506      if (this.options.onclosed) {
507        this.options.onclosed.call(this)
508      }
509    },
510
511    close: function () {
512      var that = this
513      /**
514       * Close handler
515       *
516       * @param {event} event Close event
517       */
518      function closeHandler(event) {
519        if (event.target === that.container[0]) {
520          that.container.off(that.support.transition.end, closeHandler)
521          that.handleClose()
522        }
523      }
524      if (this.options.onclose) {
525        this.options.onclose.call(this)
526      }
527      if (this.support.transition && this.options.displayTransition) {
528        this.container.on(this.support.transition.end, closeHandler)
529        this.container.removeClass(this.options.displayClass)
530      } else {
531        this.handleClose()
532      }
533    },
534
535    circle: function (index) {
536      // Always return a number inside of the slides index range:
537      return (this.num + (index % this.num)) % this.num
538    },
539
540    move: function (index, dist, duration) {
541      this.translateX(index, dist, duration)
542      this.positions[index] = dist
543    },
544
545    translate: function (index, x, y, duration) {
546      if (!this.slides[index]) return
547      var style = this.slides[index].style
548      var transition = this.support.transition
549      var transform = this.support.transform
550      style[transition.name + 'Duration'] = duration + 'ms'
551      style[transform.name] =
552        'translate(' +
553        x +
554        'px, ' +
555        y +
556        'px)' +
557        (transform.translateZ ? ' translateZ(0)' : '')
558    },
559
560    translateX: function (index, x, duration) {
561      this.translate(index, x, 0, duration)
562    },
563
564    translateY: function (index, y, duration) {
565      this.translate(index, 0, y, duration)
566    },
567
568    animate: function (from, to, duration) {
569      if (!duration) {
570        this.slidesContainer[0].style.left = to + 'px'
571        return
572      }
573      var that = this
574      var start = new Date().getTime()
575      var timer = window.setInterval(function () {
576        var timeElap = new Date().getTime() - start
577        if (timeElap > duration) {
578          that.slidesContainer[0].style.left = to + 'px'
579          that.ontransitionend()
580          window.clearInterval(timer)
581          return
582        }
583        that.slidesContainer[0].style.left =
584          (to - from) * (Math.floor((timeElap / duration) * 100) / 100) +
585          from +
586          'px'
587      }, 4)
588    },
589
590    preventDefault: function (event) {
591      if (event.preventDefault) {
592        event.preventDefault()
593      } else {
594        event.returnValue = false
595      }
596    },
597
598    stopPropagation: function (event) {
599      if (event.stopPropagation) {
600        event.stopPropagation()
601      } else {
602        event.cancelBubble = true
603      }
604    },
605
606    onresize: function () {
607      this.initSlides(true)
608    },
609
610    onhashchange: function () {
611      if (this.options.closeOnHashChange) {
612        this.close()
613      }
614    },
615
616    onmousedown: function (event) {
617      // Trigger on clicks of the left mouse button only
618      // and exclude video & audio elements:
619      if (
620        event.which &&
621        event.which === 1 &&
622        event.target.nodeName !== 'VIDEO' &&
623        event.target.nodeName !== 'AUDIO'
624      ) {
625        // Preventing the default mousedown action is required
626        // to make touch emulation work with Firefox:
627        event.preventDefault()
628        ;(event.originalEvent || event).touches = [
629          {
630            pageX: event.pageX,
631            pageY: event.pageY
632          }
633        ]
634        this.ontouchstart(event)
635      }
636    },
637
638    onmousemove: function (event) {
639      if (this.touchStart) {
640        ;(event.originalEvent || event).touches = [
641          {
642            pageX: event.pageX,
643            pageY: event.pageY
644          }
645        ]
646        this.ontouchmove(event)
647      }
648    },
649
650    onmouseup: function (event) {
651      if (this.touchStart) {
652        this.ontouchend(event)
653        delete this.touchStart
654      }
655    },
656
657    onmouseout: function (event) {
658      if (this.touchStart) {
659        var target = event.target
660        var related = event.relatedTarget
661        if (!related || (related !== target && !$.contains(target, related))) {
662          this.onmouseup(event)
663        }
664      }
665    },
666
667    ontouchstart: function (event) {
668      if (this.options.stopTouchEventsPropagation) {
669        this.stopPropagation(event)
670      }
671      // jQuery doesn't copy touch event properties by default,
672      // so we have to access the originalEvent object:
673      var touch = (event.originalEvent || event).touches[0]
674      this.touchStart = {
675        // Remember the initial touch coordinates:
676        x: touch.pageX,
677        y: touch.pageY,
678        // Store the time to determine touch duration:
679        time: Date.now()
680      }
681      // Helper variable to detect scroll movement:
682      this.isScrolling = undefined
683      // Reset delta values:
684      this.touchDelta = {}
685    },
686
687    ontouchmove: function (event) {
688      if (this.options.stopTouchEventsPropagation) {
689        this.stopPropagation(event)
690      }
691      // jQuery doesn't copy touch event properties by default,
692      // so we have to access the originalEvent object:
693      var touches = (event.originalEvent || event).touches
694      var touch = touches[0]
695      var scale = (event.originalEvent || event).scale
696      var index = this.index
697      var touchDeltaX
698      var indices
699      // Ensure this is a one touch swipe and not, e.g. a pinch:
700      if (touches.length > 1 || (scale && scale !== 1)) {
701        return
702      }
703      if (this.options.disableScroll) {
704        event.preventDefault()
705      }
706      // Measure change in x and y coordinates:
707      this.touchDelta = {
708        x: touch.pageX - this.touchStart.x,
709        y: touch.pageY - this.touchStart.y
710      }
711      touchDeltaX = this.touchDelta.x
712      // Detect if this is a vertical scroll movement (run only once per touch):
713      if (this.isScrolling === undefined) {
714        this.isScrolling =
715          this.isScrolling ||
716          Math.abs(touchDeltaX) < Math.abs(this.touchDelta.y)
717      }
718      if (!this.isScrolling) {
719        // Always prevent horizontal scroll:
720        event.preventDefault()
721        // Stop the slideshow:
722        window.clearTimeout(this.timeout)
723        if (this.options.continuous) {
724          indices = [this.circle(index + 1), index, this.circle(index - 1)]
725        } else {
726          // Increase resistance if first slide and sliding left
727          // or last slide and sliding right:
728          this.touchDelta.x = touchDeltaX =
729            touchDeltaX /
730            ((!index && touchDeltaX > 0) ||
731            (index === this.num - 1 && touchDeltaX < 0)
732              ? Math.abs(touchDeltaX) / this.slideWidth + 1
733              : 1)
734          indices = [index]
735          if (index) {
736            indices.push(index - 1)
737          }
738          if (index < this.num - 1) {
739            indices.unshift(index + 1)
740          }
741        }
742        while (indices.length) {
743          index = indices.pop()
744          this.translateX(index, touchDeltaX + this.positions[index], 0)
745        }
746      } else if (!this.options.carousel) {
747        this.translateY(index, this.touchDelta.y + this.positions[index], 0)
748      }
749    },
750
751    ontouchend: function (event) {
752      if (this.options.stopTouchEventsPropagation) {
753        this.stopPropagation(event)
754      }
755      var index = this.index
756      var absTouchDeltaX = Math.abs(this.touchDelta.x)
757      var slideWidth = this.slideWidth
758      var duration = Math.ceil(
759        (this.options.transitionDuration * (1 - absTouchDeltaX / slideWidth)) /
760          2
761      )
762      // Determine if slide attempt triggers next/prev slide:
763      var isValidSlide = absTouchDeltaX > 20
764      // Determine if slide attempt is past start or end:
765      var isPastBounds =
766        (!index && this.touchDelta.x > 0) ||
767        (index === this.num - 1 && this.touchDelta.x < 0)
768      var isValidClose =
769        !isValidSlide &&
770        this.options.closeOnSwipeUpOrDown &&
771        Math.abs(this.touchDelta.y) > 20
772      var direction
773      var indexForward
774      var indexBackward
775      var distanceForward
776      var distanceBackward
777      if (this.options.continuous) {
778        isPastBounds = false
779      }
780      // Determine direction of swipe (true: right, false: left):
781      direction = this.touchDelta.x < 0 ? -1 : 1
782      if (!this.isScrolling) {
783        if (isValidSlide && !isPastBounds) {
784          indexForward = index + direction
785          indexBackward = index - direction
786          distanceForward = slideWidth * direction
787          distanceBackward = -slideWidth * direction
788          if (this.options.continuous) {
789            this.move(this.circle(indexForward), distanceForward, 0)
790            this.move(this.circle(index - 2 * direction), distanceBackward, 0)
791          } else if (indexForward >= 0 && indexForward < this.num) {
792            this.move(indexForward, distanceForward, 0)
793          }
794          this.move(index, this.positions[index] + distanceForward, duration)
795          this.move(
796            this.circle(indexBackward),
797            this.positions[this.circle(indexBackward)] + distanceForward,
798            duration
799          )
800          index = this.circle(indexBackward)
801          this.onslide(index)
802        } else {
803          // Move back into position
804          if (this.options.continuous) {
805            this.move(this.circle(index - 1), -slideWidth, duration)
806            this.move(index, 0, duration)
807            this.move(this.circle(index + 1), slideWidth, duration)
808          } else {
809            if (index) {
810              this.move(index - 1, -slideWidth, duration)
811            }
812            this.move(index, 0, duration)
813            if (index < this.num - 1) {
814              this.move(index + 1, slideWidth, duration)
815            }
816          }
817        }
818      } else {
819        if (isValidClose) {
820          this.close()
821        } else {
822          // Move back into position
823          this.translateY(index, 0, duration)
824        }
825      }
826    },
827
828    ontouchcancel: function (event) {
829      if (this.touchStart) {
830        this.ontouchend(event)
831        delete this.touchStart
832      }
833    },
834
835    ontransitionend: function (event) {
836      var slide = this.slides[this.index]
837      if (!event || slide === event.target) {
838        if (this.interval) {
839          this.play()
840        }
841        this.setTimeout(this.options.onslideend, [this.index, slide])
842      }
843    },
844
845    oncomplete: function (event) {
846      var target = event.target || event.srcElement
847      var parent = target && target.parentNode
848      var index
849      if (!target || !parent) {
850        return
851      }
852      index = this.getNodeIndex(parent)
853      $(parent).removeClass(this.options.slideLoadingClass)
854      if (event.type === 'error') {
855        $(parent).addClass(this.options.slideErrorClass)
856        this.elements[index] = 3 // Fail
857      } else {
858        this.elements[index] = 2 // Done
859      }
860      // Fix for IE7's lack of support for percentage max-height:
861      if (target.clientHeight > this.container[0].clientHeight) {
862        target.style.maxHeight = this.container[0].clientHeight
863      }
864      if (this.interval && this.slides[this.index] === parent) {
865        this.play()
866      }
867      this.setTimeout(this.options.onslidecomplete, [index, parent])
868    },
869
870    onload: function (event) {
871      this.oncomplete(event)
872    },
873
874    onerror: function (event) {
875      this.oncomplete(event)
876    },
877
878    onkeydown: function (event) {
879      switch (event.which || event.keyCode) {
880        case 13: // Enter
881          if (this.options.toggleControlsOnEnter) {
882            this.preventDefault(event)
883            this.toggleControls()
884          }
885          break
886        case 27: // Escape
887          if (this.options.closeOnEscape) {
888            this.close()
889            // prevent Escape from closing other things
890            event.stopImmediatePropagation()
891          }
892          break
893        case 32: // Space
894          if (this.options.toggleSlideshowOnSpace) {
895            this.preventDefault(event)
896            this.toggleSlideshow()
897          }
898          break
899        case 37: // ArrowLeft
900          if (this.options.enableKeyboardNavigation) {
901            this.preventDefault(event)
902            this.prev()
903          }
904          break
905        case 39: // ArrowRight
906          if (this.options.enableKeyboardNavigation) {
907            this.preventDefault(event)
908            this.next()
909          }
910          break
911      }
912    },
913
914    handleClick: function (event) {
915      var options = this.options
916      var target = event.target || event.srcElement
917      var parent = target.parentNode
918      /**
919       * Checks if the target from the close has the given class
920       *
921       * @param {string} className Class name
922       * @returns {boolean} Returns true if the target has the class name
923       */
924      function isTarget(className) {
925        return $(target).hasClass(className) || $(parent).hasClass(className)
926      }
927      if (isTarget(options.toggleClass)) {
928        // Click on "toggle" control
929        this.preventDefault(event)
930        this.toggleControls()
931      } else if (isTarget(options.prevClass)) {
932        // Click on "prev" control
933        this.preventDefault(event)
934        this.prev()
935      } else if (isTarget(options.nextClass)) {
936        // Click on "next" control
937        this.preventDefault(event)
938        this.next()
939      } else if (isTarget(options.closeClass)) {
940        // Click on "close" control
941        this.preventDefault(event)
942        this.close()
943      } else if (isTarget(options.playPauseClass)) {
944        // Click on "play-pause" control
945        this.preventDefault(event)
946        this.toggleSlideshow()
947      } else if (parent === this.slidesContainer[0]) {
948        // Click on slide background
949        if (options.closeOnSlideClick) {
950          this.preventDefault(event)
951          this.close()
952        } else if (options.toggleControlsOnSlideClick) {
953          this.preventDefault(event)
954          this.toggleControls()
955        }
956      } else if (
957        parent.parentNode &&
958        parent.parentNode === this.slidesContainer[0]
959      ) {
960        // Click on displayed element
961        if (options.toggleControlsOnSlideClick) {
962          this.preventDefault(event)
963          this.toggleControls()
964        }
965      }
966    },
967
968    onclick: function (event) {
969      if (
970        this.options.emulateTouchEvents &&
971        this.touchDelta &&
972        (Math.abs(this.touchDelta.x) > 20 || Math.abs(this.touchDelta.y) > 20)
973      ) {
974        delete this.touchDelta
975        return
976      }
977      return this.handleClick(event)
978    },
979
980    updateEdgeClasses: function (index) {
981      if (!index) {
982        this.container.addClass(this.options.leftEdgeClass)
983      } else {
984        this.container.removeClass(this.options.leftEdgeClass)
985      }
986      if (index === this.num - 1) {
987        this.container.addClass(this.options.rightEdgeClass)
988      } else {
989        this.container.removeClass(this.options.rightEdgeClass)
990      }
991    },
992
993    updateActiveSlide: function (oldIndex, newIndex) {
994      var slides = this.slides
995      var options = this.options
996      var list = [
997        {
998          index: newIndex,
999          method: 'addClass',
1000          hidden: false
1001        },
1002        {
1003          index: oldIndex,
1004          method: 'removeClass',
1005          hidden: true
1006        }
1007      ]
1008      var item, index
1009      while (list.length) {
1010        item = list.pop()
1011        $(slides[item.index])[item.method](options.slideActiveClass)
1012        index = this.circle(item.index - 1)
1013        if (options.continuous || index < item.index) {
1014          $(slides[index])[item.method](options.slidePrevClass)
1015        }
1016        index = this.circle(item.index + 1)
1017        if (options.continuous || index > item.index) {
1018          $(slides[index])[item.method](options.slideNextClass)
1019        }
1020      }
1021      this.slides[oldIndex].setAttribute('aria-hidden', 'true')
1022      this.slides[newIndex].removeAttribute('aria-hidden')
1023    },
1024
1025    handleSlide: function (oldIndex, newIndex) {
1026      if (!this.options.continuous) {
1027        this.updateEdgeClasses(newIndex)
1028      }
1029      this.updateActiveSlide(oldIndex, newIndex)
1030      this.loadElements(newIndex)
1031      if (this.options.unloadElements) {
1032        this.unloadElements(oldIndex, newIndex)
1033      }
1034      this.setTitle(newIndex)
1035    },
1036
1037    onslide: function (index) {
1038      this.handleSlide(this.index, index)
1039      this.index = index
1040      this.setTimeout(this.options.onslide, [index, this.slides[index]])
1041    },
1042
1043    setTitle: function (index) {
1044      var firstChild = this.slides[index].firstChild
1045      var text = firstChild.title || firstChild.alt
1046      var titleElement = this.titleElement
1047      if (titleElement.length) {
1048        this.titleElement.empty()
1049        if (text) {
1050          titleElement[0].appendChild(document.createTextNode(text))
1051        }
1052      }
1053    },
1054
1055    setTimeout: function (func, args, wait) {
1056      var that = this
1057      return (
1058        func &&
1059        window.setTimeout(function () {
1060          func.apply(that, args || [])
1061        }, wait || 0)
1062      )
1063    },
1064
1065    imageFactory: function (obj, callback) {
1066      var options = this.options
1067      var that = this
1068      var url = obj
1069      var img = this.imagePrototype.cloneNode(false)
1070      var picture
1071      var called
1072      var sources
1073      var srcset
1074      var sizes
1075      var title
1076      var altText
1077      var i
1078      /**
1079       * Wraps the callback function for the load/error event
1080       *
1081       * @param {event} event load/error event
1082       * @returns {number} timeout ID
1083       */
1084      function callbackWrapper(event) {
1085        if (!called) {
1086          event = {
1087            type: event.type,
1088            target: picture || img
1089          }
1090          if (!event.target.parentNode) {
1091            // Fix for browsers (e.g. IE7) firing the load event for
1092            // cached images before the element could
1093            // be added to the DOM:
1094            return that.setTimeout(callbackWrapper, [event])
1095          }
1096          called = true
1097          $(img).off('load error', callbackWrapper)
1098          callback(event)
1099        }
1100      }
1101      if (typeof url !== 'string') {
1102        url = this.getItemProperty(obj, options.urlProperty)
1103        sources =
1104          this.support.picture &&
1105          this.support.source &&
1106          this.getItemProperty(obj, options.sourcesProperty)
1107        srcset = this.getItemProperty(obj, options.srcsetProperty)
1108        sizes = this.getItemProperty(obj, options.sizesProperty)
1109        title = this.getItemProperty(obj, options.titleProperty)
1110        altText = this.getItemProperty(obj, options.altTextProperty) || title
1111      }
1112      img.draggable = false
1113      if (title) {
1114        img.title = title
1115      }
1116      if (altText) {
1117        img.alt = altText
1118      }
1119      $(img).on('load error', callbackWrapper)
1120      if (sources && sources.length) {
1121        picture = this.picturePrototype.cloneNode(false)
1122        for (i = 0; i < sources.length; i += 1) {
1123          picture.appendChild(
1124            $.extend(this.sourcePrototype.cloneNode(false), sources[i])
1125          )
1126        }
1127        picture.appendChild(img)
1128        $(picture).addClass(options.toggleClass)
1129      }
1130      if (srcset) {
1131        if (sizes) {
1132          img.sizes = sizes
1133        }
1134        img.srcset = srcset
1135      }
1136      img.src = url
1137      if (picture) return picture
1138      return img
1139    },
1140
1141    createElement: function (obj, callback) {
1142      var type = obj && this.getItemProperty(obj, this.options.typeProperty)
1143      var factory =
1144        (type && this[type.split('/')[0] + 'Factory']) || this.imageFactory
1145      var element = obj && factory.call(this, obj, callback)
1146      if (!element) {
1147        element = this.elementPrototype.cloneNode(false)
1148        this.setTimeout(callback, [
1149          {
1150            type: 'error',
1151            target: element
1152          }
1153        ])
1154      }
1155      $(element).addClass(this.options.slideContentClass)
1156      return element
1157    },
1158
1159    iteratePreloadRange: function (index, func) {
1160      var num = this.num
1161      var options = this.options
1162      var limit = Math.min(num, options.preloadRange * 2 + 1)
1163      var j = index
1164      var i
1165      for (i = 0; i < limit; i += 1) {
1166        // First iterate to the current index (0),
1167        // then the next one (+1),
1168        // then the previous one (-1),
1169        // then the next after next (+2),
1170        // then the one before the previous one (-2), etc.:
1171        j += i * (i % 2 === 0 ? -1 : 1)
1172        if (j < 0 || j >= num) {
1173          if (!options.continuous) continue
1174          // Connect the ends of the list to load slide elements for
1175          // continuous iteration:
1176          j = this.circle(j)
1177        }
1178        func.call(this, j)
1179      }
1180    },
1181
1182    loadElement: function (index) {
1183      if (!this.elements[index]) {
1184        if (this.slides[index].firstChild) {
1185          this.elements[index] = $(this.slides[index]).hasClass(
1186            this.options.slideErrorClass
1187          )
1188            ? 3
1189            : 2
1190        } else {
1191          this.elements[index] = 1 // Loading
1192          $(this.slides[index]).addClass(this.options.slideLoadingClass)
1193          this.slides[index].appendChild(
1194            this.createElement(this.list[index], this.proxyListener)
1195          )
1196        }
1197      }
1198    },
1199
1200    loadElements: function (index) {
1201      this.iteratePreloadRange(index, this.loadElement)
1202    },
1203
1204    unloadElements: function (oldIndex, newIndex) {
1205      var preloadRange = this.options.preloadRange
1206      this.iteratePreloadRange(oldIndex, function (i) {
1207        var diff = Math.abs(i - newIndex)
1208        if (diff > preloadRange && diff + preloadRange < this.num) {
1209          this.unloadSlide(i)
1210          delete this.elements[i]
1211        }
1212      })
1213    },
1214
1215    addSlide: function (index) {
1216      var slide = this.slidePrototype.cloneNode(false)
1217      slide.setAttribute('data-index', index)
1218      slide.setAttribute('aria-hidden', 'true')
1219      this.slidesContainer[0].appendChild(slide)
1220      this.slides.push(slide)
1221    },
1222
1223    positionSlide: function (index) {
1224      var slide = this.slides[index]
1225      slide.style.width = this.slideWidth + 'px'
1226      if (this.support.transform) {
1227        slide.style.left = index * -this.slideWidth + 'px'
1228        this.move(
1229          index,
1230          this.index > index
1231            ? -this.slideWidth
1232            : this.index < index
1233            ? this.slideWidth
1234            : 0,
1235          0
1236        )
1237      }
1238    },
1239
1240    initSlides: function (reload) {
1241      var clearSlides, i
1242      if (!reload) {
1243        this.positions = []
1244        this.positions.length = this.num
1245        this.elements = {}
1246        this.picturePrototype =
1247          this.support.picture && document.createElement('picture')
1248        this.sourcePrototype =
1249          this.support.source && document.createElement('source')
1250        this.imagePrototype = document.createElement('img')
1251        this.elementPrototype = document.createElement('div')
1252        this.slidePrototype = this.elementPrototype.cloneNode(false)
1253        $(this.slidePrototype).addClass(this.options.slideClass)
1254        this.slides = this.slidesContainer[0].children
1255        clearSlides =
1256          this.options.clearSlides || this.slides.length !== this.num
1257      }
1258      this.slideWidth = this.container[0].clientWidth
1259      this.slideHeight = this.container[0].clientHeight
1260      this.slidesContainer[0].style.width = this.num * this.slideWidth + 'px'
1261      if (clearSlides) {
1262        this.resetSlides()
1263      }
1264      for (i = 0; i < this.num; i += 1) {
1265        if (clearSlides) {
1266          this.addSlide(i)
1267        }
1268        this.positionSlide(i)
1269      }
1270      // Reposition the slides before and after the given index:
1271      if (this.options.continuous && this.support.transform) {
1272        this.move(this.circle(this.index - 1), -this.slideWidth, 0)
1273        this.move(this.circle(this.index + 1), this.slideWidth, 0)
1274      }
1275      if (!this.support.transform) {
1276        this.slidesContainer[0].style.left =
1277          this.index * -this.slideWidth + 'px'
1278      }
1279    },
1280
1281    unloadSlide: function (index) {
1282      var slide, firstChild
1283      slide = this.slides[index]
1284      firstChild = slide.firstChild
1285      if (firstChild !== null) {
1286        slide.removeChild(firstChild)
1287      }
1288    },
1289
1290    unloadAllSlides: function () {
1291      var i, len
1292      for (i = 0, len = this.slides.length; i < len; i++) {
1293        this.unloadSlide(i)
1294      }
1295    },
1296
1297    toggleControls: function () {
1298      var controlsClass = this.options.controlsClass
1299      if (this.container.hasClass(controlsClass)) {
1300        this.container.removeClass(controlsClass)
1301      } else {
1302        this.container.addClass(controlsClass)
1303      }
1304    },
1305
1306    toggleSlideshow: function () {
1307      if (!this.interval) {
1308        this.play()
1309      } else {
1310        this.pause()
1311      }
1312    },
1313
1314    getNodeIndex: function (element) {
1315      return parseInt(element.getAttribute('data-index'), 10)
1316    },
1317
1318    getNestedProperty: function (obj, property) {
1319      property.replace(
1320        // Matches native JavaScript notation in a String,
1321        // e.g. '["doubleQuoteProp"].dotProp[2]'
1322        // eslint-disable-next-line no-useless-escape
1323        /\[(?:'([^']+)'|"([^"]+)"|(\d+))\]|(?:(?:^|\.)([^\.\[]+))/g,
1324        function (str, singleQuoteProp, doubleQuoteProp, arrayIndex, dotProp) {
1325          var prop =
1326            dotProp ||
1327            singleQuoteProp ||
1328            doubleQuoteProp ||
1329            (arrayIndex && parseInt(arrayIndex, 10))
1330          if (str && obj) {
1331            obj = obj[prop]
1332          }
1333        }
1334      )
1335      return obj
1336    },
1337
1338    getDataProperty: function (obj, property) {
1339      var key
1340      var prop
1341      if (obj.dataset) {
1342        key = property.replace(/-([a-z])/g, function (_, b) {
1343          return b.toUpperCase()
1344        })
1345        prop = obj.dataset[key]
1346      } else if (obj.getAttribute) {
1347        prop = obj.getAttribute(
1348          'data-' + property.replace(/([A-Z])/g, '-$1').toLowerCase()
1349        )
1350      }
1351      if (typeof prop === 'string') {
1352        // eslint-disable-next-line no-useless-escape
1353        if (
1354          /^(true|false|null|-?\d+(\.\d+)?|\{[\s\S]*\}|\[[\s\S]*\])$/.test(prop)
1355        ) {
1356          try {
1357            return $.parseJSON(prop)
1358          } catch (ignore) {
1359            // ignore JSON parsing errors
1360          }
1361        }
1362        return prop
1363      }
1364    },
1365
1366    getItemProperty: function (obj, property) {
1367      var prop = this.getDataProperty(obj, property)
1368      if (prop === undefined) {
1369        prop = obj[property]
1370      }
1371      if (prop === undefined) {
1372        prop = this.getNestedProperty(obj, property)
1373      }
1374      return prop
1375    },
1376
1377    initStartIndex: function () {
1378      var index = this.options.index
1379      var urlProperty = this.options.urlProperty
1380      var i
1381      // Check if the index is given as a list object:
1382      if (index && typeof index !== 'number') {
1383        for (i = 0; i < this.num; i += 1) {
1384          if (
1385            this.list[i] === index ||
1386            this.getItemProperty(this.list[i], urlProperty) ===
1387              this.getItemProperty(index, urlProperty)
1388          ) {
1389            index = i
1390            break
1391          }
1392        }
1393      }
1394      // Make sure the index is in the list range:
1395      this.index = this.circle(parseInt(index, 10) || 0)
1396    },
1397
1398    initEventListeners: function () {
1399      var that = this
1400      var slidesContainer = this.slidesContainer
1401      /**
1402       * Proxy listener
1403       *
1404       * @param {event} event original event
1405       */
1406      function proxyListener(event) {
1407        var type =
1408          that.support.transition && that.support.transition.end === event.type
1409            ? 'transitionend'
1410            : event.type
1411        that['on' + type](event)
1412      }
1413      $(window).on('resize', proxyListener)
1414      $(window).on('hashchange', proxyListener)
1415      $(document.body).on('keydown', proxyListener)
1416      this.container.on('click', proxyListener)
1417      if (this.support.touch) {
1418        slidesContainer.on(
1419          'touchstart touchmove touchend touchcancel',
1420          proxyListener
1421        )
1422      } else if (this.options.emulateTouchEvents && this.support.transition) {
1423        slidesContainer.on(
1424          'mousedown mousemove mouseup mouseout',
1425          proxyListener
1426        )
1427      }
1428      if (this.support.transition) {
1429        slidesContainer.on(this.support.transition.end, proxyListener)
1430      }
1431      this.proxyListener = proxyListener
1432    },
1433
1434    destroyEventListeners: function () {
1435      var slidesContainer = this.slidesContainer
1436      var proxyListener = this.proxyListener
1437      $(window).off('resize', proxyListener)
1438      $(document.body).off('keydown', proxyListener)
1439      this.container.off('click', proxyListener)
1440      if (this.support.touch) {
1441        slidesContainer.off(
1442          'touchstart touchmove touchend touchcancel',
1443          proxyListener
1444        )
1445      } else if (this.options.emulateTouchEvents && this.support.transition) {
1446        slidesContainer.off(
1447          'mousedown mousemove mouseup mouseout',
1448          proxyListener
1449        )
1450      }
1451      if (this.support.transition) {
1452        slidesContainer.off(this.support.transition.end, proxyListener)
1453      }
1454    },
1455
1456    handleOpen: function () {
1457      if (this.options.onopened) {
1458        this.options.onopened.call(this)
1459      }
1460    },
1461
1462    initWidget: function () {
1463      var that = this
1464      /**
1465       * Open handler
1466       *
1467       * @param {event} event Gallery open event
1468       */
1469      function openHandler(event) {
1470        if (event.target === that.container[0]) {
1471          that.container.off(that.support.transition.end, openHandler)
1472          that.handleOpen()
1473        }
1474      }
1475      this.container = $(this.options.container)
1476      if (!this.container.length) {
1477        this.console.log(
1478          'blueimp Gallery: Widget container not found.',
1479          this.options.container
1480        )
1481        return false
1482      }
1483      this.slidesContainer = this.container
1484        .find(this.options.slidesContainer)
1485        .first()
1486      if (!this.slidesContainer.length) {
1487        this.console.log(
1488          'blueimp Gallery: Slides container not found.',
1489          this.options.slidesContainer
1490        )
1491        return false
1492      }
1493      this.titleElement = this.container.find(this.options.titleElement).first()
1494      this.playPauseElement = this.container
1495        .find('.' + this.options.playPauseClass)
1496        .first()
1497      if (this.num === 1) {
1498        this.container.addClass(this.options.singleClass)
1499      }
1500      if (this.support.svgasimg) {
1501        this.container.addClass(this.options.svgasimgClass)
1502      }
1503      if (this.support.smil) {
1504        this.container.addClass(this.options.smilClass)
1505      }
1506      if (this.options.onopen) {
1507        this.options.onopen.call(this)
1508      }
1509      if (this.support.transition && this.options.displayTransition) {
1510        this.container.on(this.support.transition.end, openHandler)
1511      } else {
1512        this.handleOpen()
1513      }
1514      if (this.options.hidePageScrollbars) {
1515        // Hide the page scrollbars:
1516        this.bodyOverflowStyle = document.body.style.overflow
1517        document.body.style.overflow = 'hidden'
1518      }
1519      this.container[0].style.display = 'block'
1520      this.initSlides()
1521      this.container.addClass(this.options.displayClass)
1522    },
1523
1524    initOptions: function (options) {
1525      // Create a copy of the prototype options:
1526      this.options = $.extend({}, this.options)
1527      // Check if carousel mode is enabled:
1528      if (
1529        (options && options.carousel) ||
1530        (this.options.carousel && (!options || options.carousel !== false))
1531      ) {
1532        $.extend(this.options, this.carouselOptions)
1533      }
1534      // Override any given options:
1535      $.extend(this.options, options)
1536      if (this.num < 3) {
1537        // 1 or 2 slides cannot be displayed continuous,
1538        // remember the original option by setting to null instead of false:
1539        this.options.continuous = this.options.continuous ? null : false
1540      }
1541      if (!this.support.transition) {
1542        this.options.emulateTouchEvents = false
1543      }
1544      if (this.options.event) {
1545        this.preventDefault(this.options.event)
1546      }
1547    }
1548  })
1549
1550  return Gallery
1551})

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.