PageSourceSearch

https://zan.kz/lib/intro.js-2.9.3/intro.js

js zan.kz collected 2026-09-24 15:34:48 UTC 84,663 bytes, 2,531 lines download raw bytes

1/**
2 * Intro.js v2.9.3
3 * https://github.com/usablica/intro.js
4 *
5 * Copyright (C) 2017 Afshin Mehrabani (@afshinmeh)
6 */
7
8(function(f) {
9    if (typeof exports === "object" && typeof module !== "undefined") {
10        module.exports = f();
11        // deprecated function
12        // @since 2.8.0
13        module.exports.introJs = function () {
14          console.warn('Deprecated: please use require("intro.js") directly, instead of the introJs method of the function');
15          // introJs()
16          return f().apply(this, arguments);
17        };
18    } else if (typeof define === "function" && define.amd) {
19        define([], f);
20    } else {
21        var g;
22        if (typeof window !== "undefined") {
23            g = window;
24        } else if (typeof global !== "undefined") {
25            g = global;
26        } else if (typeof self !== "undefined") {
27            g = self;
28        } else {
29            g = this;
30        }
31        g.introJs = f();
32    }
33})(function () {
34  //Default config/variables
35  var VERSION = '2.9.3';
36
37  /**
38   * IntroJs main class
39   *
40   * @class IntroJs
41   */
42  function IntroJs(obj) {
43    this._targetElement = obj;
44    this._introItems = [];
45
46    this._options = {
47      /* Next button label in tooltip box */
48      nextLabel: 'Next →',
49      /* Previous button label in tooltip box */
50      prevLabel: '← Back',
51      /* Skip button label in tooltip box */
52      skipLabel: 'Skip',
53      /* Done button label in tooltip box */
54      doneLabel: 'Done',
55      /* Hide previous button in the first step? Otherwise, it will be disabled button. */
56      hidePrev: false,
57      /* Hide next button in the last step? Otherwise, it will be disabled button. */
58      hideNext: false,
59      /* Default tooltip box position */
60      tooltipPosition: 'bottom',
61      /* Next CSS class for tooltip boxes */
62      tooltipClass: '',
63      /* CSS class that is added to the helperLayer */
64      highlightClass: '',
65      /* Close introduction when pressing Escape button? */
66      exitOnEsc: true,
67      /* Close introduction when clicking on overlay layer? */
68      exitOnOverlayClick: true,
69      /* Show step numbers in introduction? */
70      showStepNumbers: true,
71      /* Let user use keyboard to navigate the tour? */
72      keyboardNavigation: true,
73      /* Show tour control buttons? */
74      showButtons: true,
75      /* Show tour bullets? */
76      showBullets: true,
77      /* Show tour progress? */
78      showProgress: false,
79      /* Scroll to highlighted element? */
80      scrollToElement: true,
81      /*
82       * Should we scroll the tooltip or target element?
83       *
84       * Options are: 'element' or 'tooltip'
85       */
86      scrollTo: 'element',
87      /* Padding to add after scrolling when element is not in the viewport (in pixels) */
88      scrollPadding: 30,
89      /* Set the overlay opacity */
90      overlayOpacity: 0.8,
91      /* Precedence of positions, when auto is enabled */
92      positionPrecedence: ["bottom", "top", "right", "left"],
93      /* Disable an interaction with element? */
94      disableInteraction: false,
95      /* Set how much padding to be used around helper element */
96      helperElementPadding: 10,
97      /* Default hint position */
98      hintPosition: 'top-middle',
99      /* Hint button label */
100      hintButtonLabel: 'Got it',
101      /* Adding animation to hints? */
102      hintAnimation: true,
103      /* additional classes to put on the buttons */
104      buttonClass: "introjs-button"
105    };
106  }
107
108  /**
109   * Initiate a new introduction/guide from an element in the page
110   *
111   * @api private
112   * @method _introForElement
113   * @param {Object} targetElm
114   * @param {String} group
115   * @returns {Boolean} Success or not?
116   */
117  function _introForElement(targetElm, group) {
118    var allIntroSteps = targetElm.querySelectorAll("*[data-intro]"),
119        introItems = [];
120
121    if (this._options.steps) {
122      //use steps passed programmatically
123      _forEach(this._options.steps, function (step) {
124        var currentItem = _cloneObject(step);
125
126        //set the step
127        currentItem.step = introItems.length + 1;
128
129        //use querySelector function only when developer used CSS selector
130        if (typeof (currentItem.element) === 'string') {
131          //grab the element with given selector from the page
132          currentItem.element = document.querySelector(currentItem.element);
133        }
134
135        //intro without element
136        if (typeof (currentItem.element) === 'undefined' || currentItem.element === null) {
137          var floatingElementQuery = document.querySelector(".introjsFloatingElement");
138
139          if (floatingElementQuery === null) {
140            floatingElementQuery = document.createElement('div');
141            floatingElementQuery.className = 'introjsFloatingElement';
142
143            document.body.appendChild(floatingElementQuery);
144          }
145
146          currentItem.element  = floatingElementQuery;
147          currentItem.position = 'floating';
148        }
149
150        currentItem.scrollTo = currentItem.scrollTo || this._options.scrollTo;
151
152        if (typeof (currentItem.disableInteraction) === 'undefined') {
153          currentItem.disableInteraction = this._options.disableInteraction;
154        }
155
156        if (currentItem.element !== null) {
157          introItems.push(currentItem);
158        }        
159      }.bind(this));
160
161    } else {
162      //use steps from data-* annotations
163      var elmsLength = allIntroSteps.length;
164      var disableInteraction;
165      
166      //if there's no element to intro
167      if (elmsLength < 1) {
168        return false;
169      }
170
171      _forEach(allIntroSteps, function (currentElement) {
172        
173        // PR #80
174        // start intro for groups of elements
175        if (group && (currentElement.getAttribute("data-intro-group") !== group)) {
176          return;
177        }
178
179        // skip hidden elements
180        if (currentElement.style.display === 'none') {
181          return;
182        }
183
184        var step = parseInt(currentElement.getAttribute('data-step'), 10);
185
186        if (typeof (currentElement.getAttribute('data-disable-interaction')) !== 'undefined') {
187          disableInteraction = !!currentElement.getAttribute('data-disable-interaction');
188        } else {
189          disableInteraction = this._options.disableInteraction;
190        }
191
192        if (step > 0) {
193          introItems[step - 1] = {
194            element: currentElement,
195            intro: currentElement.getAttribute('data-intro'),
196            step: parseInt(currentElement.getAttribute('data-step'), 10),
197            tooltipClass: currentElement.getAttribute('data-tooltipclass'),
198            highlightClass: currentElement.getAttribute('data-highlightclass'),
199            position: currentElement.getAttribute('data-position') || this._options.tooltipPosition,
200            scrollTo: currentElement.getAttribute('data-scrollto') || this._options.scrollTo,
201            disableInteraction: disableInteraction
202          };
203        }
204      }.bind(this));
205
206      //next add intro items without data-step
207      //todo: we need a cleanup here, two loops are redundant
208      var nextStep = 0;
209
210      _forEach(allIntroSteps, function (currentElement) {
211        
212        // PR #80
213        // start intro for groups of elements
214        if (group && (currentElement.getAttribute("data-intro-group") !== group)) {
215          return;
216        }
217        
218        if (currentElement.getAttribute('data-step') === null) {
219
220          while (true) {
221            if (typeof introItems[nextStep] === 'undefined') {
222              break;
223            } else {
224              nextStep++;
225            }
226          } 
227
228          if (typeof (currentElement.getAttribute('data-disable-interaction')) !== 'undefined') {
229            disableInteraction = !!currentElement.getAttribute('data-disable-interaction');
230          } else {
231            disableInteraction = this._options.disableInteraction;
232          }
233
234          introItems[nextStep] = {
235            element: currentElement,
236            intro: currentElement.getAttribute('data-intro'),
237            step: nextStep + 1,
238            tooltipClass: currentElement.getAttribute('data-tooltipclass'),
239            highlightClass: currentElement.getAttribute('data-highlightclass'),
240            position: currentElement.getAttribute('data-position') || this._options.tooltipPosition,
241            scrollTo: currentElement.getAttribute('data-scrollto') || this._options.scrollTo,
242            disableInteraction: disableInteraction
243          };
244        }
245      }.bind(this));
246    }
247
248    //removing undefined/null elements
249    var tempIntroItems = [];
250    for (var z = 0; z < introItems.length; z++) {
251      if (introItems[z]) {
252        // copy non-falsy values to the end of the array
253        tempIntroItems.push(introItems[z]);  
254      } 
255    }
256
257    introItems = tempIntroItems;
258
259    //Ok, sort all items with given steps
260    introItems.sort(function (a, b) {
261      return a.step - b.step;
262    });
263
264    //set it to the introJs object
265    this._introItems = introItems;
266
267    //add overlay layer to the page
268    if(_addOverlayLayer.call(this, targetElm)) {
269      //then, start the show
270      _nextStep.call(this);
271
272      if (this._options.keyboardNavigation) {
273        DOMEvent.on(window, 'keydown', _onKeyDown, this, true);
274      }
275      //for window resize
276      DOMEvent.on(window, 'resize', _onResize, this, true);
277    }
278    return false;
279  }
280
281  function _onResize () {
282    this.refresh.call(this);
283  }
284
285  /**
286  * on keyCode:
287  * https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/keyCode
288  * This feature has been removed from the Web standards.
289  * Though some browsers may still support it, it is in
290  * the process of being dropped.
291  * Instead, you should use KeyboardEvent.code,
292  * if it's implemented.
293  *
294  * jQuery's approach is to test for
295  *   (1) e.which, then
296  *   (2) e.charCode, then
297  *   (3) e.keyCode
298  * https://github.com/jquery/jquery/blob/a6b0705294d336ae2f63f7276de0da1195495363/src/event.js#L638
299  *
300  * @param type var
301  * @return type
302  */
303  function _onKeyDown (e) {
304    var code = (e.code === null) ? e.which : e.code;
305
306    // if code/e.which is null
307    if (code === null) {
308      code = (e.charCode === null) ? e.keyCode : e.charCode;
309    }
310    
311    if ((code === 'Escape' || code === 27) && this._options.exitOnEsc === true) {
312      //escape key pressed, exit the intro
313      //check if exit callback is defined
314      _exitIntro.call(this, this._targetElement);
315    } else if (code === 'ArrowLeft' || code === 37) {
316      //left arrow
317      _previousStep.call(this);
318    } else if (code === 'ArrowRight' || code === 39) {
319      //right arrow
320      _nextStep.call(this);
321    } else if (code === 'Enter' || code === 13) {
322      //srcElement === ie
323      var target = e.target || e.srcElement;
324      if (target && target.className.match('introjs-prevbutton')) {
325        //user hit enter while focusing on previous button
326        _previousStep.call(this);
327      } else if (target && target.className.match('introjs-skipbutton')) {
328        //user hit enter while focusing on skip button
329        if (this._introItems.length - 1 === this._currentStep && typeof (this._introCompleteCallback) === 'function') {
330            this._introCompleteCallback.call(this);
331        }
332
333        _exitIntro.call(this, this._targetElement);
334      } else if (target && target.getAttribute('data-stepnumber')) {
335        // user hit enter while focusing on step bullet
336        target.click();
337      } else {
338        //default behavior for responding to enter
339        _nextStep.call(this);
340      }
341
342      //prevent default behaviour on hitting Enter, to prevent steps being skipped in some browsers
343      if(e.preventDefault) {
344        e.preventDefault();
345      } else {
346        e.returnValue = false;
347      }
348    }
349  }
350
351 /*
352   * makes a copy of the object
353   * @api private
354   * @method _cloneObject
355  */
356  function _cloneObject(object) {
357      if (object === null || typeof (object) !== 'object' || typeof (object.nodeType) !== 'undefined') {
358        return object;
359      }
360      var temp = {};
361      for (var key in object) {
362        if (typeof(window.jQuery) !== 'undefined' && object[key] instanceof window.jQuery) {
363          temp[key] = object[key];
364        } else {
365          temp[key] = _cloneObject(object[key]);
366        }
367      }
368      return temp;
369  }
370  /**
371   * Go to specific step of introduction
372   *
373   * @api private
374   * @method _goToStep
375   */
376  function _goToStep(step) {
377    //because steps starts with zero
378    this._currentStep = step - 2;
379    if (typeof (this._introItems) !== 'undefined') {
380      _nextStep.call(this);
381    }
382  }
383
384  /**
385   * Go to the specific step of introduction with the explicit [data-step] number
386   *
387   * @api private
388   * @method _goToStepNumber
389   */
390  function _goToStepNumber(step) {
391    this._currentStepNumber = step;
392    if (typeof (this._introItems) !== 'undefined') {
393      _nextStep.call(this);
394    }
395  }
396
397  /**
398   * Go to next step on intro
399   *
400   * @api private
401   * @method _nextStep
402   */
403  function _nextStep() {
404    this._direction = 'forward';
405
406    if (typeof (this._currentStepNumber) !== 'undefined') {
407      _forEach(this._introItems, function (item, i) {
408        if( item.step === this._currentStepNumber ) {
409          this._currentStep = i - 1;
410          this._currentStepNumber = undefined;
411        }
412      }.bind(this));
413    }
414
415    if (typeof (this._currentStep) === 'undefined') {
416      this._currentStep = 0;
417    } else {
418      ++this._currentStep;
419    }
420
421    var nextStep = this._introItems[this._currentStep];
422    var continueStep = true;
423
424    if (typeof (this._introBeforeChangeCallback) !== 'undefined') {
425      continueStep = this._introBeforeChangeCallback.call(this, nextStep.element);
426    }
427
428    // if `onbeforechange` returned `false`, stop displaying the element
429    if (continueStep === false) {
430      --this._currentStep;
431      return false;
432    }
433
434    if ((this._introItems.length) <= this._currentStep) {
435      //end of the intro
436      //check if any callback is defined
437      if (typeof (this._introCompleteCallback) === 'function') {
438        this._introCompleteCallback.call(this);
439      }
440      _exitIntro.call(this, this._targetElement);
441      return;
442    }
443
444    _showElement.call(this, nextStep);
445  }
446
447  /**
448   * Go to previous step on intro
449   *
450   * @api private
451   * @method _previousStep
452   */
453  function _previousStep() {
454    this._direction = 'backward';
455
456    if (this._currentStep === 0) {
457      return false;
458    }
459
460    --this._currentStep;
461
462    var nextStep = this._introItems[this._currentStep];
463    var continueStep = true;
464
465    if (typeof (this._introBeforeChangeCallback) !== 'undefined') {
466      continueStep = this._introBeforeChangeCallback.call(this, nextStep.element);
467    }
468
469    // if `onbeforechange` returned `false`, stop displaying the element
470    if (continueStep === false) {
471      ++this._currentStep;
472      return false;
473    }
474
475    _showElement.call(this, nextStep);
476  }
477
478  /**
479   * Update placement of the intro objects on the screen
480   * @api private
481   */
482  function _refresh() {
483    // re-align intros
484    _setHelperLayerPosition.call(this, document.querySelector('.introjs-helperLayer'));
485    _setHelperLayerPosition.call(this, document.querySelector('.introjs-tooltipReferenceLayer'));
486    _setHelperLayerPosition.call(this, document.querySelector('.introjs-disableInteraction'));
487
488    // re-align tooltip
489    if(this._currentStep !== undefined && this._currentStep !== null) {
490      var oldHelperNumberLayer = document.querySelector('.introjs-helperNumberLayer'),
491        oldArrowLayer        = document.querySelector('.introjs-arrow'),
492        oldtooltipContainer  = document.querySelector('.introjs-tooltip');
493      _placeTooltip.call(this, this._introItems[this._currentStep].element, oldtooltipContainer, oldArrowLayer, oldHelperNumberLayer);
494    }
495
496    //re-align hints
497    _reAlignHints.call(this);
498    return this;
499  }
500
501  /**
502   * Exit from intro
503   *
504   * @api private
505   * @method _exitIntro
506   * @param {Object} targetElement
507   * @param {Boolean} force - Setting to `true` will skip the result of beforeExit callback
508   */
509  function _exitIntro(targetElement, force) {
510    var continueExit = true;
511
512    // calling onbeforeexit callback
513    //
514    // If this callback return `false`, it would halt the process
515    if (this._introBeforeExitCallback !== undefined) {
516      continueExit = this._introBeforeExitCallback.call(this);
517    }
518
519    // skip this check if `force` parameter is `true`
520    // otherwise, if `onbeforeexit` returned `false`, don't exit the intro
521    if (!force && continueExit === false) return;
522
523    //remove overlay layers from the page
524    var overlayLayers = targetElement.querySelectorAll('.introjs-overlay');
525
526    if (overlayLayers && overlayLayers.length) {
527      _forEach(overlayLayers, function (overlayLayer) {
528        overlayLayer.style.opacity = 0;
529        window.setTimeout(function () {
530          if (this.parentNode) {
531            this.parentNode.removeChild(this);
532          }
533        }.bind(overlayLayer), 500);
534      }.bind(this));
535    }
536
537    //remove all helper layers
538    var helperLayer = targetElement.querySelector('.introjs-helperLayer');
539    if (helperLayer) {
540      helperLayer.parentNode.removeChild(helperLayer);
541    }
542
543    var referenceLayer = targetElement.querySelector('.introjs-tooltipReferenceLayer');
544    if (referenceLayer) {
545      referenceLayer.parentNode.removeChild(referenceLayer);
546    }
547
548    //remove disableInteractionLayer
549    var disableInteractionLayer = targetElement.querySelector('.introjs-disableInteraction');
550    if (disableInteractionLayer) {
551      disableInteractionLayer.parentNode.removeChild(disableInteractionLayer);
552    }
553
554    //remove intro floating element
555    var floatingElement = document.querySelector('.introjsFloatingElement');
556    if (floatingElement) {
557      floatingElement.parentNode.removeChild(floatingElement);
558    }
559
560    _removeShowElement();
561
562    //remove `introjs-fixParent` class from the elements
563    var fixParents = document.querySelectorAll('.introjs-fixParent');
564    _forEach(fixParents, function (parent) {
565      _removeClass(parent, /introjs-fixParent/g);
566    });
567
568    //clean listeners
569    DOMEvent.off(window, 'keydown', _onKeyDown, this, true);
570    DOMEvent.off(window, 'resize', _onResize, this, true);
571
572    //check if any callback is defined
573    if (this._introExitCallback !== undefined) {
574      this._introExitCallback.call(this);
575    }
576
577    //set the step to zero
578    this._currentStep = undefined;
579  }
580
581  /**
582   * Render tooltip box in the page
583   *
584   * @api private
585   * @method _placeTooltip
586   * @param {HTMLElement} targetElement
587   * @param {HTMLElement} tooltipLayer
588   * @param {HTMLElement} arrowLayer
589   * @param {HTMLElement} helperNumberLayer
590   * @param {Boolean} hintMode
591   */
592  function _placeTooltip(targetElement, tooltipLayer, arrowLayer, helperNumberLayer, hintMode) {
593    var tooltipCssClass = '',
594        currentStepObj,
595        tooltipOffset,
596        targetOffset,
597        windowSize,
598        currentTooltipPosition;
599
600    hintMode = hintMode || false;
601
602    //reset the old style
603    tooltipLayer.style.top        = null;
604    tooltipLayer.style.right      = null;
605    tooltipLayer.style.bottom     = null;
606    tooltipLayer.style.left       = null;
607    tooltipLayer.style.marginLeft = null;
608    tooltipLayer.style.marginTop  = null;
609
610    arrowLayer.style.display = 'inherit';
611
612    if (typeof(helperNumberLayer) !== 'undefined' && helperNumberLayer !== null) {
613      helperNumberLayer.style.top  = null;
614      helperNumberLayer.style.left = null;
615    }
616
617    //prevent error when `this._currentStep` is undefined
618    if (!this._introItems[this._currentStep]) return;
619
620    //if we have a custom css class for each step
621    currentStepObj = this._introItems[this._currentStep];
622    if (typeof (currentStepObj.tooltipClass) === 'string') {
623      tooltipCssClass = currentStepObj.tooltipClass;
624    } else {
625      tooltipCssClass = this._options.tooltipClass;
626    }
627
628    tooltipLayer.className = ('introjs-tooltip ' + tooltipCssClass).replace(/^\s+|\s+$/g, '');
629    tooltipLayer.setAttribute('role', 'dialog');
630
631    currentTooltipPosition = this._introItems[this._currentStep].position;
632
633    // Floating is always valid, no point in calculating
634    if (currentTooltipPosition !== "floating") { 
635      currentTooltipPosition = _determineAutoPosition.call(this, targetElement, tooltipLayer, currentTooltipPosition);
636    }
637
638    var tooltipLayerStyleLeft;
639    targetOffset  = _getOffset(targetElement);
640    tooltipOffset = _getOffset(tooltipLayer);
641    windowSize    = _getWinSize();
642
643    _addClass(tooltipLayer, 'introjs-' + currentTooltipPosition);
644
645    switch (currentTooltipPosition) {
646      case 'top-right-aligned':
647        arrowLayer.className      = 'introjs-arrow bottom-right';
648
649        var tooltipLayerStyleRight = 0;
650        _checkLeft(targetOffset, tooltipLayerStyleRight, tooltipOffset, tooltipLayer);
651        tooltipLayer.style.bottom    = (targetOffset.height +  20) + 'px';
652        break;
653
654      case 'top-middle-aligned':
655        arrowLayer.className      = 'introjs-arrow bottom-middle';
656
657        var tooltipLayerStyleLeftRight = targetOffset.width / 2 - tooltipOffset.width / 2;
658
659        // a fix for middle aligned hints
660        if (hintMode) {
661          tooltipLayerStyleLeftRight += 5;
662        }
663
664        if (_checkLeft(targetOffset, tooltipLayerStyleLeftRight, tooltipOffset, tooltipLayer)) {
665          tooltipLayer.style.right = null;
666          _checkRight(targetOffset, tooltipLayerStyleLeftRight, tooltipOffset, windowSize, tooltipLayer);
667        }
668        tooltipLayer.style.bottom = (targetOffset.height + 20) + 'px';
669        break;
670
671      case 'top-left-aligned':
672      // top-left-aligned is the same as the default top
673      case 'top':
674        arrowLayer.className = 'introjs-arrow bottom';
675
676        tooltipLayerStyleLeft = (hintMode) ? 0 : 15;
677
678        _checkRight(targetOffset, tooltipLayerStyleLeft, tooltipOffset, windowSize, tooltipLayer);
679        tooltipLayer.style.bottom = (targetOffset.height +  20) + 'px';
680        break;
681      case 'right':
682        tooltipLayer.style.left = (targetOffset.width + 20) + 'px';
683        if (targetOffset.top + tooltipOffset.height > windowSize.height) {
684          // In this case, right would have fallen below the bottom of the screen.
685          // Modify so that the bottom of the tooltip connects with the target
686          arrowLayer.className = "introjs-arrow left-bottom";
687          tooltipLayer.style.top = "-" + (tooltipOffset.height - targetOffset.height - 20) + "px";
688        } else {
689          arrowLayer.className = 'introjs-arrow left';
690        }
691        break;
692      case 'left':
693        if (!hintMode && this._options.showStepNumbers === true) {
694          tooltipLayer.style.top = '15px';
695        }
696
697        if (targetOffset.top + tooltipOffset.height > windowSize.height) {
698          // In this case, left would have fallen below the bottom of the screen.
699          // Modify so that the bottom of the tooltip connects with the target
700          tooltipLayer.style.top = "-" + (tooltipOffset.height - targetOffset.height - 20) + "px";
701          arrowLayer.className = 'introjs-arrow right-bottom';
702        } else {
703          arrowLayer.className = 'introjs-arrow right';
704        }
705        tooltipLayer.style.right = (targetOffset.width + 20) + 'px';
706
707        break;
708      case 'floating':
709        arrowLayer.style.display = 'none';
710
711        //we have to adjust the top and left of layer manually for intro items without element
712        tooltipLayer.style.left   = '50%';
713        tooltipLayer.style.top    = '50%';
714        tooltipLayer.style.marginLeft = '-' + (tooltipOffset.width / 2)  + 'px';
715        tooltipLayer.style.marginTop  = '-' + (tooltipOffset.height / 2) + 'px';
716
717        if (typeof(helperNumberLayer) !== 'undefined' && helperNumberLayer !== null) {
718          helperNumberLayer.style.left = '-' + ((tooltipOffset.width / 2) + 18) + 'px';
719          helperNumberLayer.style.top  = '-' + ((tooltipOffset.height / 2) + 18) + 'px';
720        }
721
722        break;
723      case 'bottom-right-aligned':
724        arrowLayer.className      = 'introjs-arrow top-right';
725
726        tooltipLayerStyleRight = 0;
727        _checkLeft(targetOffset, tooltipLayerStyleRight, tooltipOffset, tooltipLayer);
728        tooltipLayer.style.top    = (targetOffset.height +  20) + 'px';
729        break;
730
731      case 'bottom-middle-aligned':
732        arrowLayer.className      = 'introjs-arrow top-middle';
733
734        tooltipLayerStyleLeftRight = targetOffset.width / 2 - tooltipOffset.width / 2;
735
736        // a fix for middle aligned hints
737        if (hintMode) {
738          tooltipLayerStyleLeftRight += 5;
739        }
740
741        if (_checkLeft(targetOffset, tooltipLayerStyleLeftRight, tooltipOffset, tooltipLayer)) {
742          tooltipLayer.style.right = null;
743          _checkRight(targetOffset, tooltipLayerStyleLeftRight, tooltipOffset, windowSize, tooltipLayer);
744        }
745        tooltipLayer.style.top = (targetOffset.height + 20) + 'px';
746        break;
747
748      // case 'bottom-left-aligned':
749      // Bottom-left-aligned is the same as the default bottom
750      // case 'bottom':
751      // Bottom going to follow the default behavior
752      default:
753        arrowLayer.className = 'introjs-arrow top';
754
755        tooltipLayerStyleLeft = 0;
756        _checkRight(targetOffset, tooltipLayerStyleLeft, tooltipOffset, windowSize, tooltipLayer);
757        tooltipLayer.style.top    = (targetOffset.height +  20) + 'px';
758    }
759  }
760
761  /**
762   * Set tooltip left so it doesn't go off the right side of the window
763   *
764   * @return boolean true, if tooltipLayerStyleLeft is ok.  false, otherwise.
765   */
766  function _checkRight(targetOffset, tooltipLayerStyleLeft, tooltipOffset, windowSize, tooltipLayer) {
767    if (targetOffset.left + tooltipLayerStyleLeft + tooltipOffset.width > windowSize.width) {
768      // off the right side of the window
769      tooltipLayer.style.left = (windowSize.width - tooltipOffset.width - targetOffset.left) + 'px';
770      return false;
771    }
772    tooltipLayer.style.left = tooltipLayerStyleLeft + 'px';
773    return true;
774  }
775
776  /**
777   * Set tooltip right so it doesn't go off the left side of the window
778   *
779   * @return boolean true, if tooltipLayerStyleRight is ok.  false, otherwise.
780   */
781  function _checkLeft(targetOffset, tooltipLayerStyleRight, tooltipOffset, tooltipLayer) {
782    if (targetOffset.left + targetOffset.width - tooltipLayerStyleRight - tooltipOffset.width < 0) {
783      // off the left side of the window
784      tooltipLayer.style.left = (-targetOffset.left) + 'px';
785      return false;
786    }
787    tooltipLayer.style.right = tooltipLayerStyleRight + 'px';
788    return true;
789  }
790
791  /**
792   * Determines the position of the tooltip based on the position precedence and availability
793   * of screen space.
794   *
795   * @param {Object}    targetElement
796   * @param {Object}    tooltipLayer
797   * @param {String}    desiredTooltipPosition
798   * @return {String}   calculatedPosition
799   */
800  function _determineAutoPosition(targetElement, tooltipLayer, desiredTooltipPosition) {
801
802    // Take a clone of position precedence. These will be the available
803    var possiblePositions = this._options.positionPrecedence.slice();
804
805    var windowSize = _getWinSize();
806    var tooltipHeight = _getOffset(tooltipLayer).height + 10;
807    var tooltipWidth = _getOffset(tooltipLayer).width + 20;
808    var targetElementRect = targetElement.getBoundingClientRect();
809
810    // If we check all the possible areas, and there are no valid places for the tooltip, the element
811    // must take up most of the screen real estate. Show the tooltip floating in the middle of the screen.
812    var calculatedPosition = "floating";
813
814    /*
815    * auto determine position 
816    */
817
818    // Check for space below
819    if (targetElementRect.bottom + tooltipHeight + tooltipHeight > windowSize.height) {
820      _removeEntry(possiblePositions, "bottom");
821    }
822
823    // Check for space above
824    if (targetElementRect.top - tooltipHeight < 0) {
825      _removeEntry(possiblePositions, "top");
826    }
827
828    // Check for space to the right
829    if (targetElementRect.right + tooltipWidth > windowSize.width) {
830      _removeEntry(possiblePositions, "right");
831    }
832
833    // Check for space to the left
834    if (targetElementRect.left - tooltipWidth < 0) {
835      _removeEntry(possiblePositions, "left");
836    }
837
838    // @var {String}  ex: 'right-aligned'
839    var desiredAlignment = (function (pos) {
840      var hyphenIndex = pos.indexOf('-');
841      if (hyphenIndex !== -1) {
842        // has alignment
843        return pos.substr(hyphenIndex);
844      }
845      return '';
846    })(desiredTooltipPosition || '');
847
848    // strip alignment from position
849    if (desiredTooltipPosition) {
850      // ex: "bottom-right-aligned"
851      // should return 'bottom'
852      desiredTooltipPosition = desiredTooltipPosition.split('-')[0];
853    }
854
855    if (possiblePositions.length) {
856      if (desiredTooltipPosition !== "auto" &&
857          possiblePositions.indexOf(desiredTooltipPosition) > -1) {
858        // If the requested position is in the list, choose that
859        calculatedPosition = desiredTooltipPosition;
860      } else {
861        // Pick the first valid position, in order
862        calculatedPosition = possiblePositions[0];
863      }
864    }
865
866    // only top and bottom positions have optional alignments
867    if (['top', 'bottom'].indexOf(calculatedPosition) !== -1) {
868      calculatedPosition += _determineAutoAlignment(targetElementRect.left, tooltipWidth, windowSize, desiredAlignment);
869    }
870
871    return calculatedPosition;
872  }
873
874  /**
875  * auto-determine alignment
876  * @param {Integer}  offsetLeft
877  * @param {Integer}  tooltipWidth
878  * @param {Object}   windowSize
879  * @param {String}   desiredAlignment
880  * @return {String}  calculatedAlignment
881  */
882  function _determineAutoAlignment (offsetLeft, tooltipWidth, windowSize, desiredAlignment) {
883    var halfTooltipWidth = tooltipWidth / 2,
884      winWidth = Math.min(windowSize.width, window.screen.width),
885      possibleAlignments = ['-left-aligned', '-middle-aligned', '-right-aligned'],
886      calculatedAlignment = '';
887    
888    // valid left must be at least a tooltipWidth
889    // away from right side
890    if (winWidth - offsetLeft < tooltipWidth) {
891      _removeEntry(possibleAlignments, '-left-aligned');
892    }
893
894    // valid middle must be at least half 
895    // width away from both sides
896    if (offsetLeft < halfTooltipWidth || 
897      winWidth - offsetLeft < halfTooltipWidth) {
898      _removeEntry(possibleAlignments, '-middle-aligned');
899    }
900
901    // valid right must be at least a tooltipWidth
902    // width away from left side
903    if (offsetLeft < tooltipWidth) {
904      _removeEntry(possibleAlignments, '-right-aligned');
905    }
906
907    if (possibleAlignments.length) {
908      if (possibleAlignments.indexOf(desiredAlignment) !== -1) {
909        // the desired alignment is valid
910        calculatedAlignment = desiredAlignment;
911      } else {
912        // pick the first valid position, in order
913        calculatedAlignment = possibleAlignments[0];
914      }
915    } else {
916      // if screen width is too small 
917      // for ANY alignment, middle is 
918      // probably the best for visibility
919      calculatedAlignment = '-middle-aligned';
920    }
921
922    return calculatedAlignment;
923  }
924
925  /**
926   * Remove an entry from a string array if it's there, does nothing if it isn't there.
927   *
928   * @param {Array} stringArray
929   * @param {String} stringToRemove
930   */
931  function _removeEntry(stringArray, stringToRemove) {
932    if (stringArray.indexOf(stringToRemove) > -1) {
933      stringArray.splice(stringArray.indexOf(stringToRemove), 1);
934    }
935  }
936
937  /**
938   * Update the position of the helper layer on the screen
939   *
940   * @api private
941   * @method _setHelperLayerPosition
942   * @param {Object} helperLayer
943   */
944  function _setHelperLayerPosition(helperLayer) {
945    if (helperLayer) {
946      //prevent error when `this._currentStep` in undefined
947      if (!this._introItems[this._currentStep]) return;
948
949      var currentElement  = this._introItems[this._currentStep],
950          elementPosition = _getOffset(currentElement.element),
951          widthHeightPadding = this._options.helperElementPadding;
952
953      // If the target element is fixed, the tooltip should be fixed as well.
954      // Otherwise, remove a fixed class that may be left over from the previous
955      // step.
956      if (_isFixed(currentElement.element)) {
957        _addClass(helperLayer, 'introjs-fixedTooltip');
958      } else {
959        _removeClass(helperLayer, 'introjs-fixedTooltip');
960      }
961
962      if (currentElement.position === 'floating') {
963        widthHeightPadding = 0;
964      }
965
966      //set new position to helper layer
967      helperLayer.style.cssText = 'width: ' + (elementPosition.width  + widthHeightPadding)  + 'px; ' +
968                                        'height:' + (elementPosition.height + widthHeightPadding)  + 'px; ' +
969                                        'top:'    + (elementPosition.top    - widthHeightPadding / 2)   + 'px;' +
970                                        'left: '  + (elementPosition.left   - widthHeightPadding / 2)   + 'px;';
971
972    }
973  }
974
975  /**
976   * Add disableinteraction layer and adjust the size and position of the layer
977   *
978   * @api private
979   * @method _disableInteraction
980   */
981  function _disableInteraction() {
982    var disableInteractionLayer = document.querySelector('.introjs-disableInteraction');
983
984    if (disableInteractionLayer === null) {
985      disableInteractionLayer = document.createElement('div');
986      disableInteractionLayer.className = 'introjs-disableInteraction';
987      this._targetElement.appendChild(disableInteractionLayer);
988    }
989
990    _setHelperLayerPosition.call(this, disableInteractionLayer);
991  }
992
993  /**
994   * Setting anchors to behave like buttons
995   *
996   * @api private
997   * @method _setAnchorAsButton
998   */
999  function _setAnchorAsButton(anchor){
1000    anchor.setAttribute('role', 'button');
1001    anchor.tabIndex = 0;
1002  }
1003
1004  /**
1005   * Show an element on the page
1006   *
1007   * @api private
1008   * @method _showElement
1009   * @param {Object} targetElement
1010   */
1011  function _showElement(targetElement) {
1012    if (typeof (this._introChangeCallback) !== 'undefined') {
1013      this._introChangeCallback.call(this, targetElement.element);
1014    }
1015
1016    var self = this,
1017        oldHelperLayer = document.querySelector('.introjs-helperLayer'),
1018        oldReferenceLayer = document.querySelector('.introjs-tooltipReferenceLayer'),
1019        highlightClass = 'introjs-helperLayer',
1020        nextTooltipButton,
1021        prevTooltipButton,
1022        skipTooltipButton,
1023        scrollParent;
1024
1025    //check for a current step highlight class
1026    if (typeof (targetElement.highlightClass) === 'string') {
1027      highlightClass += (' ' + targetElement.highlightClass);
1028    }
1029    //check for options highlight class
1030    if (typeof (this._options.highlightClass) === 'string') {
1031      highlightClass += (' ' + this._options.highlightClass);
1032    }
1033
1034    if (oldHelperLayer !== null) {
1035      var oldHelperNumberLayer = oldReferenceLayer.querySelector('.introjs-helperNumberLayer'),
1036          oldtooltipLayer      = oldReferenceLayer.querySelector('.introjs-tooltiptext'),
1037          oldArrowLayer        = oldReferenceLayer.querySelector('.introjs-arrow'),
1038          oldtooltipContainer  = oldReferenceLayer.querySelector('.introjs-tooltip');
1039          
1040      skipTooltipButton    = oldReferenceLayer.querySelector('.introjs-skipbutton');
1041      prevTooltipButton    = oldReferenceLayer.querySelector('.introjs-prevbutton');
1042      nextTooltipButton    = oldReferenceLayer.querySelector('.introjs-nextbutton');
1043
1044      //update or reset the helper highlight class
1045      oldHelperLayer.className = highlightClass;
1046      //hide the tooltip
1047      oldtooltipContainer.style.opacity = 0;
1048      oldtooltipContainer.style.display = "none";
1049
1050      if (oldHelperNumberLayer !== null) {
1051        var lastIntroItem = this._introItems[(targetElement.step - 2 >= 0 ? targetElement.step - 2 : 0)];
1052
1053        if (lastIntroItem !== null && (this._direction === 'forward' && lastIntroItem.position === 'floating') || (this._direction === 'backward' && targetElement.position === 'floating')) {
1054          oldHelperNumberLayer.style.opacity = 0;
1055        }
1056      }
1057
1058      // scroll to element
1059      scrollParent = _getScrollParent( targetElement.element );
1060
1061      if (scrollParent !== document.body) {
1062        // target is within a scrollable element
1063        _scrollParentToElement(scrollParent, targetElement.element);
1064      }
1065
1066      // set new position to helper layer
1067      _setHelperLayerPosition.call(self, oldHelperLayer);
1068      _setHelperLayerPosition.call(self, oldReferenceLayer);
1069
1070      //remove `introjs-fixParent` class from the elements
1071      var fixParents = document.querySelectorAll('.introjs-fixParent');
1072      _forEach(fixParents, function (parent) {
1073        _removeClass(parent, /introjs-fixParent/g);
1074      });
1075      
1076      //remove old classes if the element still exist
1077      _removeShowElement();
1078
1079      //we should wait until the CSS3 transition is competed (it's 0.3 sec) to prevent incorrect `height` and `width` calculation
1080      if (self._lastShowElementTimer) {
1081        window.clearTimeout(self._lastShowElementTimer);
1082      }
1083
1084      self._lastShowElementTimer = window.setTimeout(function() {
1085        //set current step to the label
1086        if (oldHelperNumberLayer !== null) {
1087          oldHelperNumberLayer.innerHTML = targetElement.step;
1088        }
1089        //set current tooltip text
1090        oldtooltipLayer.innerHTML = targetElement.intro;
1091        //set the tooltip position
1092        oldtooltipContainer.style.display = "block";
1093        _placeTooltip.call(self, targetElement.element, oldtooltipContainer, oldArrowLayer, oldHelperNumberLayer);
1094
1095        //change active bullet
1096        if (self._options.showBullets) {
1097            oldReferenceLayer.querySelector('.introjs-bullets li > a.active').className = '';
1098            oldReferenceLayer.querySelector('.introjs-bullets li > a[data-stepnumber="' + targetElement.step + '"]').className = 'active';
1099        }
1100        oldReferenceLayer.querySelector('.introjs-progress .introjs-progressbar').style.cssText = 'width:' + _getProgress.call(self) + '%;';
1101        oldReferenceLayer.querySelector('.introjs-progress .introjs-progressbar').setAttribute('aria-valuenow', _getProgress.call(self));
1102
1103        //show the tooltip
1104        oldtooltipContainer.style.opacity = 1;
1105        if (oldHelperNumberLayer) oldHelperNumberLayer.style.opacity = 1;
1106
1107        //reset button focus
1108        if (typeof skipTooltipButton !== "undefined" && skipTooltipButton !== null && /introjs-donebutton/gi.test(skipTooltipButton.className)) {
1109          // skip button is now "done" button
1110          skipTooltipButton.focus();
1111        } else if (typeof nextTooltipButton !== "undefined" && nextTooltipButton !== null) {
1112          //still in the tour, focus on next
1113          nextTooltipButton.focus();
1114        }
1115
1116        // change the scroll of the window, if needed
1117        _scrollTo.call(self, targetElement.scrollTo, targetElement, oldtooltipLayer);
1118      }, 350);
1119
1120      // end of old element if-else condition
1121    } else {
1122      var helperLayer       = document.createElement('div'),
1123          referenceLayer    = document.createElement('div'),
1124          arrowLayer        = document.createElement('div'),
1125          tooltipLayer      = document.createElement('div'),
1126          tooltipTextLayer  = document.createElement('div'),
1127          bulletsLayer      = document.createElement('div'),
1128          progressLayer     = document.createElement('div'),
1129          buttonsLayer      = document.createElement('div');
1130
1131      helperLayer.className = highlightClass;
1132      referenceLayer.className = 'introjs-tooltipReferenceLayer';
1133
1134      // scroll to element
1135      scrollParent = _getScrollParent( targetElement.element );
1136
1137      if (scrollParent !== document.body) {
1138        // target is within a scrollable element
1139        _scrollParentToElement(scrollParent, targetElement.element);
1140      }
1141
1142      //set new position to helper layer
1143      _setHelperLayerPosition.call(self, helperLayer);
1144      _setHelperLayerPosition.call(self, referenceLayer);
1145
1146      //add helper layer to target element
1147      this._targetElement.appendChild(helperLayer);
1148      this._targetElement.appendChild(referenceLayer);
1149
1150      arrowLayer.className = 'introjs-arrow';
1151
1152      tooltipTextLayer.className = 'introjs-tooltiptext';
1153      tooltipTextLayer.innerHTML = targetElement.intro;
1154
1155      bulletsLayer.className = 'introjs-bullets';
1156
1157      if (this._options.showBullets === false) {
1158        bulletsLayer.style.display = 'none';
1159      }
1160
1161      var ulContainer = document.createElement('ul');
1162      ulContainer.setAttribute('role', 'tablist');
1163
1164      var anchorClick = function () {
1165          self.goToStep(this.getAttribute('data-stepnumber'));
1166      };
1167
1168      _forEach(this._introItems, function (item, i) {
1169        var innerLi    = document.createElement('li');
1170        var anchorLink = document.createElement('a');
1171        
1172        innerLi.setAttribute('role', 'presentation');
1173        anchorLink.setAttribute('role', 'tab');
1174
1175        anchorLink.onclick = anchorClick;
1176
1177        if (i === (targetElement.step-1)) {
1178          anchorLink.className = 'active';
1179        } 
1180
1181        _setAnchorAsButton(anchorLink);
1182        anchorLink.innerHTML = "&nbsp;";
1183        anchorLink.setAttribute('data-stepnumber', item.step);
1184
1185        innerLi.appendChild(anchorLink);
1186        ulContainer.appendChild(innerLi);
1187      });
1188
1189      bulletsLayer.appendChild(ulContainer);
1190
1191      progressLayer.className = 'introjs-progress';
1192
1193      if (this._options.showProgress === false) {
1194        progressLayer.style.display = 'none';
1195      }
1196      var progressBar = document.createElement('div');
1197      progressBar.className = 'introjs-progressbar';
1198      progressBar.setAttribute('role', 'progress');
1199      progressBar.setAttribute('aria-valuemin', 0);
1200      progressBar.setAttribute('aria-valuemax', 100);
1201      progressBar.setAttribute('aria-valuenow', _getProgress.call(this));
1202      progressBar.style.cssText = 'width:' + _getProgress.call(this) + '%;';
1203
1204      progressLayer.appendChild(progressBar);
1205
1206      buttonsLayer.className = 'introjs-tooltipbuttons';
1207      if (this._options.showButtons === false) {
1208        buttonsLayer.style.display = 'none';
1209      }
1210
1211      tooltipLayer.className = 'introjs-tooltip';
1212      tooltipLayer.appendChild(tooltipTextLayer);
1213      tooltipLayer.appendChild(bulletsLayer);
1214      tooltipLayer.appendChild(progressLayer);
1215
1216      //add helper layer number
1217      var helperNumberLayer = document.createElement('span');
1218      if (this._options.showStepNumbers === true) {
1219        helperNumberLayer.className = 'introjs-helperNumberLayer';
1220        helperNumberLayer.innerHTML = targetElement.step;
1221        referenceLayer.appendChild(helperNumberLayer);
1222      }
1223
1224      tooltipLayer.appendChild(arrowLayer);
1225      referenceLayer.appendChild(tooltipLayer);
1226
1227      //next button
1228      nextTooltipButton = document.createElement('a');
1229
1230      nextTooltipButton.onclick = function() {
1231        if (self._introItems.length - 1 !== self._currentStep) {
1232          _nextStep.call(self);
1233        }
1234      };
1235
1236      _setAnchorAsButton(nextTooltipButton);
1237      nextTooltipButton.innerHTML = this._options.nextLabel;
1238
1239      //previous button
1240      prevTooltipButton = document.createElement('a');
1241
1242      prevTooltipButton.onclick = function() {
1243        if (self._currentStep !== 0) {
1244          _previousStep.call(self);
1245        }
1246      };
1247
1248      _setAnchorAsButton(prevTooltipButton);
1249      prevTooltipButton.innerHTML = this._options.prevLabel;
1250
1251      //skip button
1252      skipTooltipButton = document.createElement('a');
1253      skipTooltipButton.className = this._options.buttonClass + ' introjs-skipbutton ';
1254      _setAnchorAsButton(skipTooltipButton);
1255      skipTooltipButton.innerHTML = this._options.skipLabel;
1256
1257      skipTooltipButton.onclick = function() {
1258        if (self._introItems.length - 1 === self._currentStep && typeof (self._introCompleteCallback) === 'function') {
1259          self._introCompleteCallback.call(self);
1260        }
1261
1262        if (self._introItems.length - 1 !== self._currentStep && typeof (self._introExitCallback) === 'function') {
1263          self._introExitCallback.call(self);
1264        }
1265
1266        if (typeof(self._introSkipCallback) === 'function') {
1267          self._introSkipCallback.call(self);
1268        }
1269
1270        _exitIntro.call(self, self._targetElement);
1271      };
1272
1273      buttonsLayer.appendChild(skipTooltipButton);
1274
1275      //in order to prevent displaying next/previous button always
1276      if (this._introItems.length > 1) {
1277        buttonsLayer.appendChild(prevTooltipButton);
1278        buttonsLayer.appendChild(nextTooltipButton);
1279      }
1280
1281      tooltipLayer.appendChild(buttonsLayer);
1282
1283      //set proper position
1284      _placeTooltip.call(self, targetElement.element, tooltipLayer, arrowLayer, helperNumberLayer);
1285
1286      // change the scroll of the window, if needed
1287      _scrollTo.call(this, targetElement.scrollTo, targetElement, tooltipLayer);
1288
1289      //end of new element if-else condition
1290    }
1291
1292    // removing previous disable interaction layer
1293    var disableInteractionLayer = self._targetElement.querySelector('.introjs-disableInteraction');
1294    if (disableInteractionLayer) {
1295      disableInteractionLayer.parentNode.removeChild(disableInteractionLayer);
1296    }
1297
1298    //disable interaction
1299    if (targetElement.disableInteraction) {
1300      _disableInteraction.call(self);
1301    }
1302
1303    // when it's the first step of tour
1304    if (this._currentStep === 0 && this._introItems.length > 1) {
1305      if (typeof skipTooltipButton !== "undefined" && skipTooltipButton !== null) {
1306        skipTooltipButton.className = this._options.buttonClass + ' introjs-skipbutton';
1307      }
1308      if (typeof nextTooltipButton !== "undefined" && nextTooltipButton !== null) {
1309        nextTooltipButton.className = this._options.buttonClass + ' introjs-nextbutton';
1310      }
1311
1312      if (this._options.hidePrev === true) {
1313        if (typeof prevTooltipButton !== "undefined" && prevTooltipButton !== null) {
1314          prevTooltipButton.className = this._options.buttonClass + ' introjs-prevbutton introjs-hidden';
1315        }
1316        if (typeof nextTooltipButton !== "undefined" && nextTooltipButton !== null) {
1317          _addClass(nextTooltipButton, 'introjs-fullbutton');
1318        }
1319      } else {
1320        if (typeof prevTooltipButton !== "undefined" && prevTooltipButton !== null) {
1321          prevTooltipButton.className = this._options.buttonClass + ' introjs-prevbutton introjs-disabled';
1322        }
1323      }
1324
1325      if (typeof skipTooltipButton !== "undefined" && skipTooltipButton !== null) {
1326        skipTooltipButton.innerHTML = this._options.skipLabel;
1327      }
1328    } else if (this._introItems.length - 1 === this._currentStep || this._introItems.length === 1) {
1329      // last step of tour
1330      if (typeof skipTooltipButton !== "undefined" && skipTooltipButton !== null) {
1331        skipTooltipButton.innerHTML = this._options.doneLabel;
1332        // adding donebutton class in addition to skipbutton
1333        _addClass(skipTooltipButton, 'introjs-donebutton');
1334      }
1335      if (typeof prevTooltipButton !== "undefined" && prevTooltipButton !== null) {
1336        prevTooltipButton.className = this._options.buttonClass + ' introjs-prevbutton';
1337      }
1338
1339      if (this._options.hideNext === true) {
1340        if (typeof nextTooltipButton !== "undefined" && nextTooltipButton !== null) {
1341          nextTooltipButton.className = this._options.buttonClass + ' introjs-nextbutton introjs-hidden';
1342        }
1343        if (typeof prevTooltipButton !== "undefined" && prevTooltipButton !== null) {
1344          _addClass(prevTooltipButton, 'introjs-fullbutton');
1345        }
1346      } else {
1347        if (typeof nextTooltipButton !== "undefined" && nextTooltipButton !== null) {
1348          nextTooltipButton.className = this._options.buttonClass + ' introjs-nextbutton introjs-disabled';
1349        }
1350      }
1351    } else {
1352      // steps between start and end
1353      if (typeof skipTooltipButton !== "undefined" && skipTooltipButton !== null) {
1354        skipTooltipButton.className = this._options.buttonClass + ' introjs-skipbutton';
1355      }
1356      if (typeof prevTooltipButton !== "undefined" && prevTooltipButton !== null) {
1357        prevTooltipButton.className = this._options.buttonClass + ' introjs-prevbutton';
1358      }
1359      if (typeof nextTooltipButton !== "undefined" && nextTooltipButton !== null) {
1360        nextTooltipButton.className = this._options.buttonClass + ' introjs-nextbutton';
1361      }
1362      if (typeof skipTooltipButton !== "undefined" && skipTooltipButton !== null) {
1363        skipTooltipButton.innerHTML = this._options.skipLabel;
1364      }
1365    }
1366
1367    prevTooltipButton.setAttribute('role', 'button');
1368    nextTooltipButton.setAttribute('role', 'button');
1369    skipTooltipButton.setAttribute('role', 'button');
1370
1371    //Set focus on "next" button, so that hitting Enter always moves you onto the next step
1372    if (typeof nextTooltipButton !== "undefined" && nextTooltipButton !== null) {
1373      nextTooltipButton.focus();
1374    }
1375
1376    _setShowElement(targetElement);
1377
1378    if (typeof (this._introAfterChangeCallback) !== 'undefined') {
1379      this._introAfterChangeCallback.call(this, targetElement.element);
1380    }
1381  }
1382
1383  /**
1384   * To change the scroll of `window` after highlighting an element
1385   *
1386   * @api private
1387   * @method _scrollTo
1388   * @param {String} scrollTo
1389   * @param {Object} targetElement
1390   * @param {Object} tooltipLayer
1391   */
1392  function _scrollTo(scrollTo, targetElement, tooltipLayer) {
1393    if (scrollTo === 'off') return;  
1394    var rect;
1395
1396    if (!this._options.scrollToElement) return;
1397
1398    if (scrollTo === 'tooltip') {
1399      rect = tooltipLayer.getBoundingClientRect();
1400    } else {
1401      rect = targetElement.element.getBoundingClientRect();
1402    }
1403
1404    if (!_elementInViewport(targetElement.element)) {
1405      var winHeight = _getWinSize().height;
1406      var top = rect.bottom - (rect.bottom - rect.top);
1407
1408      // TODO (afshinm): do we need scroll padding now?
1409      // I have changed the scroll option and now it scrolls the window to
1410      // the center of the target element or tooltip.
1411
1412      if (top < 0 || targetElement.element.clientHeight > winHeight) {
1413        window.scrollBy(0, rect.top - ((winHeight / 2) -  (rect.height / 2)) - this._options.scrollPadding); // 30px padding from edge to look nice
1414
1415      //Scroll down
1416      } else {
1417        window.scrollBy(0, rect.top - ((winHeight / 2) -  (rect.height / 2)) + this._options.scrollPadding); // 30px padding from edge to look nice
1418      }
1419    }
1420  }
1421
1422  /**
1423   * To remove all show element(s)
1424   *
1425   * @api private
1426   * @method _removeShowElement
1427   */
1428  function _removeShowElement() {
1429    var elms = document.querySelectorAll('.introjs-showElement');
1430
1431    _forEach(elms, function (elm) {
1432      _removeClass(elm, /introjs-[a-zA-Z]+/g);
1433    });
1434  }
1435
1436  /**
1437   * To set the show element
1438   * This function set a relative (in most cases) position and changes the z-index
1439   *
1440   * @api private
1441   * @method _setShowElement
1442   * @param {Object} targetElement
1443   */
1444  function _setShowElement(targetElement) {
1445    var parentElm;
1446    // we need to add this show element class to the parent of SVG elements
1447    // because the SVG elements can't have independent z-index
1448    if (targetElement.element instanceof SVGElement) {
1449      parentElm = targetElement.element.parentNode;
1450
1451      while (targetElement.element.parentNode !== null) {
1452        if (!parentElm.tagName || parentElm.tagName.toLowerCase() === 'body') break;
1453
1454        if (parentElm.tagName.toLowerCase() === 'svg') {
1455          _addClass(parentElm, 'introjs-showElement introjs-relativePosition');
1456        }
1457
1458        parentElm = parentElm.parentNode;
1459      }
1460    }
1461
1462    _addClass(targetElement.element, 'introjs-showElement');
1463
1464    var currentElementPosition = _getPropValue(targetElement.element, 'position');
1465    if (currentElementPosition !== 'absolute' &&
1466        currentElementPosition !== 'relative' &&
1467        currentElementPosition !== 'fixed') {
1468      //change to new intro item
1469      _addClass(targetElement.element, 'introjs-relativePosition');
1470    }
1471
1472    parentElm = targetElement.element.parentNode;
1473    while (parentElm !== null) {
1474      if (!parentElm.tagName || parentElm.tagName.toLowerCase() === 'body') break;
1475
1476      //fix The Stacking Context problem.
1477      //More detail: https://developer.mozilla.org/en-US/docs/Web/Guide/CSS/Understanding_z_index/The_stacking_context
1478      var zIndex = _getPropValue(parentElm, 'z-index');
1479      var opacity = parseFloat(_getPropValue(parentElm, 'opacity'));
1480      var transform = _getPropValue(parentElm, 'transform') || _getPropValue(parentElm, '-webkit-transform') || _getPropValue(parentElm, '-moz-transform') || _getPropValue(parentElm, '-ms-transform') || _getPropValue(parentElm, '-o-transform');
1481      if (/[0-9]+/.test(zIndex) || opacity < 1 || (transform !== 'none' && transform !== undefined)) {
1482        _addClass(parentElm, 'introjs-fixParent');
1483      }
1484
1485      parentElm = parentElm.parentNode;
1486    }
1487  }
1488
1489  /**
1490  * Iterates arrays
1491  *
1492  * @param {Array} arr
1493  * @param {Function} forEachFnc
1494  * @param {Function} completeFnc
1495  * @return {Null}
1496  */
1497  function _forEach(arr, forEachFnc, completeFnc) {
1498    // in case arr is an empty query selector node list
1499    if (arr) {
1500      for (var i = 0, len = arr.length; i < len; i++) {
1501        forEachFnc(arr[i], i);
1502      }
1503    }
1504
1505    if (typeof(completeFnc) === 'function') {
1506      completeFnc();
1507    }
1508  }
1509
1510  /**
1511  * Mark any object with an incrementing number
1512  * used for keeping track of objects
1513  *
1514  * @param Object obj   Any object or DOM Element
1515  * @param String key
1516  * @return Object
1517  */
1518  var _stamp = (function () {
1519    var keys = {};
1520    return function stamp (obj, key) {
1521      
1522      // get group key
1523      key = key || 'introjs-stamp';
1524
1525      // each group increments from 0
1526      keys[key] = keys[key] || 0;
1527
1528      // stamp only once per object
1529      if (obj[key] === undefined) {
1530        // increment key for each new object
1531        obj[key] = keys[key]++;
1532      }
1533
1534      return obj[key];
1535    };
1536  })();
1537
1538  /**
1539  * DOMEvent Handles all DOM events
1540  *
1541  * methods:
1542  *
1543  * on - add event handler
1544  * off - remove event
1545  */
1546  var DOMEvent = (function () {
1547    function DOMEvent () {
1548      var events_key = 'introjs_event';
1549      
1550      /**
1551      * Gets a unique ID for an event listener
1552      *
1553      * @param Object obj
1554      * @param String type        event type
1555      * @param Function listener
1556      * @param Object context
1557      * @return String
1558      */
1559      this._id = function (obj, type, listener, context) {
1560        return type + _stamp(listener) + (context ? '_' + _stamp(context) : '');
1561      };
1562
1563      /**
1564      * Adds event listener
1565      *
1566      * @param Object obj
1567      * @param String type        event type
1568      * @param Function listener
1569      * @param Object context
1570      * @param Boolean useCapture
1571      * @return null
1572      */
1573      this.on = function (obj, type, listener, context, useCapture) {
1574        var id = this._id.apply(this, arguments),
1575            handler = function (e) {
1576              return listener.call(context || obj, e || window.event);
1577            };
1578
1579        if ('addEventListener' in obj) {
1580          obj.addEventListener(type, handler, useCapture);
1581        } else if ('attachEvent' in obj) {
1582          obj.attachEvent('on' + type, handler);
1583        }
1584
1585        obj[events_key] = obj[events_key] || {};
1586        obj[events_key][id] = handler;
1587      };
1588
1589      /**
1590      * Removes event listener
1591      *
1592      * @param Object obj
1593      * @param String type        event type
1594      * @param Function listener
1595      * @param Object context
1596      * @param Boolean useCapture
1597      * @return null
1598      */
1599      this.off = function (obj, type, listener, context, useCapture) {
1600        var id = this._id.apply(this, arguments),
1601            handler = obj[events_key] && obj[events_key][id];
1602
1603        if (!handler) {
1604          return;
1605        }
1606
1607        if ('removeEventListener' in obj) {
1608          obj.removeEventListener(type, handler, useCapture);
1609        } else if ('detachEvent' in obj) {
1610          obj.detachEvent('on' + type, handler);
1611        }
1612
1613        obj[events_key][id] = null;
1614      };
1615    }
1616
1617    return new DOMEvent();
1618  })();
1619
1620  /**
1621   * Append a class to an element
1622   *
1623   * @api private
1624   * @method _addClass
1625   * @param {Object} element
1626   * @param {String} className
1627   * @returns null
1628   */
1629  function _addClass(element, className) {
1630    if (element instanceof SVGElement) {
1631      // svg
1632      var pre = element.getAttribute('class') || '';
1633
1634      element.setAttribute('class', pre + ' ' + className);
1635    } else {
1636      if (element.classList !== undefined) {
1637        // check for modern classList property
1638        var classes = className.split(' ');
1639        _forEach(classes, function (cls) {
1640          element.classList.add( cls );
1641        });
1642      } else if (!element.className.match( className )) {
1643        // check if element doesn't already have className
1644        element.className += ' ' + className;
1645      }
1646    }
1647  }
1648
1649  /**
1650   * Remove a class from an element
1651   *
1652   * @api private
1653   * @method _removeClass
1654   * @param {Object} element
1655   * @param {RegExp|String} classNameRegex can be regex or string
1656   * @returns null
1657   */
1658  function _removeClass(element, classNameRegex) {
1659    if (element instanceof SVGElement) {
1660      var pre = element.getAttribute('class') || '';
1661
1662      element.setAttribute('class', pre.replace(classNameRegex, '').replace(/^\s+|\s+$/g, ''));
1663    } else {
1664      element.className = element.className.replace(classNameRegex, '').replace(/^\s+|\s+$/g, '');
1665    }
1666  }
1667
1668  /**
1669   * Get an element CSS property on the page
1670   * Thanks to JavaScript Kit: http://www.javascriptkit.com/dhtmltutors/dhtmlcascade4.shtml
1671   *
1672   * @api private
1673   * @method _getPropValue
1674   * @param {Object} element
1675   * @param {String} propName
1676   * @returns Element's property value
1677   */
1678  function _getPropValue (element, propName) {
1679    var propValue = '';
1680    if (element.currentStyle) { //IE
1681      propValue = element.currentStyle[propName];
1682    } else if (document.defaultView && document.defaultView.getComputedStyle) { //Others
1683      propValue = document.defaultView.getComputedStyle(element, null).getPropertyValue(propName);
1684    }
1685
1686    //Prevent exception in IE
1687    if (propValue && propValue.toLowerCase) {
1688      return propValue.toLowerCase();
1689    } else {
1690      return propValue;
1691    }
1692  }
1693
1694  /**
1695   * Checks to see if target element (or parents) position is fixed or not
1696   *
1697   * @api private
1698   * @method _isFixed
1699   * @param {Object} element
1700   * @returns Boolean
1701   */
1702  function _isFixed (element) {
1703    var p = element.parentNode;
1704
1705    if (!p || p.nodeName === 'HTML') {
1706      return false;
1707    }
1708
1709    if (_getPropValue(element, 'position') === 'fixed') {
1710      return true;
1711    }
1712
1713    return _isFixed(p);
1714  }
1715
1716  /**
1717   * Provides a cross-browser way to get the screen dimensions
1718   * via: http://stackoverflow.com/questions/5864467/internet-explorer-innerheight
1719   *
1720   * @api private
1721   * @method _getWinSize
1722   * @returns {Object} width and height attributes
1723   */
1724  function _getWinSize() {
1725    if (window.innerWidth !== undefined) {
1726      return { width: window.innerWidth, height: window.innerHeight };
1727    } else {
1728      var D = document.documentElement;
1729      return { width: D.clientWidth, height: D.clientHeight };
1730    }
1731  }
1732
1733  /**
1734   * Check to see if the element is in the viewport or not
1735   * http://stackoverflow.com/questions/123999/how-to-tell-if-a-dom-element-is-visible-in-the-current-viewport
1736   *
1737   * @api private
1738   * @method _elementInViewport
1739   * @param {Object} el
1740   */
1741  function _elementInViewport(el) {
1742    var rect = el.getBoundingClientRect();
1743
1744    return (
1745      rect.top >= 0 &&
1746      rect.left >= 0 &&
1747      (rect.bottom+80) <= window.innerHeight && // add 80 to get the text right
1748      rect.right <= window.innerWidth
1749    );
1750  }
1751
1752  /**
1753   * Add overlay layer to the page
1754   *
1755   * @api private
1756   * @method _addOverlayLayer
1757   * @param {Object} targetElm
1758   */
1759  function _addOverlayLayer(targetElm) {
1760    var overlayLayer = document.createElement('div'),
1761        styleText = '',
1762        self = this;
1763
1764    //set css class name
1765    overlayLayer.className = 'introjs-overlay';
1766
1767    //check if the target element is body, we should calculate the size of overlay layer in a better way
1768    if (!targetElm.tagName || targetElm.tagName.toLowerCase() === 'body') {
1769      styleText += 'top: 0;bottom: 0; left: 0;right: 0;position: fixed;';
1770      overlayLayer.style.cssText = styleText;
1771    } else {
1772      //set overlay layer position
1773      var elementPosition = _getOffset(targetElm);
1774      if (elementPosition) {
1775        styleText += 'width: ' + elementPosition.width + 'px; height:' + elementPosition.height + 'px;
1775 top:' + elementPosition.top + 'px;left: ' + elementPosition.left + 'px;';
1776        overlayLayer.style.cssText = styleText;
1777      }
1778    }
1779
1780    targetElm.appendChild(overlayLayer);
1781
1782    overlayLayer.onclick = function() {
1783      if (self._options.exitOnOverlayClick === true) {
1784        _exitIntro.call(self, targetElm);
1785      }
1786    };
1787
1788    window.setTimeout(function() {
1789      styleText += 'opacity: ' + self._options.overlayOpacity.toString() + ';';
1790      overlayLayer.style.cssText = styleText;
1791    }, 10);
1792
1793    return true;
1794  }
1795
1796  /**
1797   * Removes open hint (tooltip hint)
1798   *
1799   * @api private
1800   * @method _removeHintTooltip
1801   */
1802  function _removeHintTooltip() {
1803    var tooltip = document.querySelector('.introjs-hintReference');
1804
1805    if (tooltip) {
1806      var step = tooltip.getAttribute('data-step');
1807      tooltip.parentNode.removeChild(tooltip);
1808      return step;
1809    }
1810  }
1811
1812  /**
1813   * Start parsing hint items
1814   *
1815   * @api private
1816   * @param {Object} targetElm
1817   * @method _startHint
1818   */
1819  function _populateHints(targetElm) {
1820
1821    this._introItems = [];
1822
1823    if (this._options.hints) {
1824      _forEach(this._options.hints, function (hint) {
1825        var currentItem = _cloneObject(hint);
1826
1827        if (typeof(currentItem.element) === 'string') {
1828          //grab the element with given selector from the page
1829          currentItem.element = document.querySelector(currentItem.element);
1830        }
1831
1832        currentItem.hintPosition = currentItem.hintPosition || this._options.hintPosition;
1833        currentItem.hintAnimation = currentItem.hintAnimation || this._options.hintAnimation;
1834
1835        if (currentItem.element !== null) {
1836          this._introItems.push(currentItem);
1837        }
1838      }.bind(this));
1839    } else {
1840      var hints = targetElm.querySelectorAll('*[data-hint]');
1841
1842      if (!hints || !hints.length) {
1843        return false;
1844      }
1845
1846      //first add intro items with data-step
1847      _forEach(hints, function (currentElement) {
1848        // hint animation
1849        var hintAnimation = currentElement.getAttribute('data-hintanimation');
1850
1851        if (hintAnimation) {
1852          hintAnimation = (hintAnimation === 'true');
1853        } else {
1854          hintAnimation = this._options.hintAnimation;
1855        }
1856
1857        this._introItems.push({
1858          element: currentElement,
1859          hint: currentElement.getAttribute('data-hint'),
1860          hintPosition: currentElement.getAttribute('data-hintposition') || this._options.hintPosition,
1861          hintAnimation: hintAnimation,
1862          tooltipClass: currentElement.getAttribute('data-tooltipclass'),
1863          position: currentElement.getAttribute('data-position') || this._options.tooltipPosition
1864        });
1865      }.bind(this));
1866    }
1867
1868    _addHints.call(this);
1869
1870    /* 
1871    todo:
1872    these events should be removed at some point 
1873    */
1874    DOMEvent.on(document, 'click', _removeHintTooltip, this, false);
1875    DOMEvent.on(window, 'resize', _reAlignHints, this, true);
1876  }
1877
1878  /**
1879   * Re-aligns all hint elements
1880   *
1881   * @api private
1882   * @method _reAlignHints
1883   */
1884  function _reAlignHints() {
1885    _forEach(this._introItems, function (item) {
1886      if (typeof(item.targetElement) === 'undefined') {
1887        return;
1888      }
1889
1890      _alignHintPosition.call(this, item.hintPosition, item.element, item.targetElement);
1891    }.bind(this));
1892  }
1893
1894  /**
1895  * Get a queryselector within the hint wrapper
1896  *
1897  * @param {String} selector
1898  * @return {NodeList|Array}
1899  */
1900  function _hintQuerySelectorAll(selector) {
1901    var hintsWrapper = document.querySelector('.introjs-hints');
1902    return (hintsWrapper) ? hintsWrapper.querySelectorAll(selector) : [];
1903  }
1904
1905  /**
1906   * Hide a hint
1907   *
1908   * @api private
1909   * @method _hideHint
1910   */
1911  function _hideHint(stepId) {
1912    var hint = _hintQuerySelectorAll('.introjs-hint[data-step="' + stepId + '"]')[0];
1913    
1914    _removeHintTooltip.call(this);
1915
1916    if (hint) {
1917      _addClass(hint, 'introjs-hidehint');
1918    }
1919
1920    // call the callback function (if any)
1921    if (typeof (this._hintCloseCallback) !== 'undefined') {
1922      this._hintCloseCallback.call(this, stepId);
1923    }
1924  }
1925
1926  /**
1927   * Hide all hints
1928   *
1929   * @api private
1930   * @method _hideHints
1931   */
1932  function _hideHints() {
1933    var hints = _hintQuerySelectorAll('.introjs-hint');
1934
1935    _forEach(hints, function (hint) {
1936      _hideHint.call(this, hint.getAttribute('data-step'));
1937    }.bind(this));
1938  }
1939
1940  /**
1941   * Show all hints
1942   *
1943   * @api private
1944   * @method _showHints
1945   */
1946  function _showHints() {
1947    var hints = _hintQuerySelectorAll('.introjs-hint');
1948
1949    if (hints && hints.length) {
1950      _forEach(hints, function (hint) {
1951        _showHint.call(this, hint.getAttribute('data-step'));
1952      }.bind(this));
1953    } else {
1954      _populateHints.call(this, this._targetElement);
1955    }
1956  }
1957
1958  /**
1959   * Show a hint
1960   *
1961   * @api private
1962   * @method _showHint
1963   */
1964  function _showHint(stepId) {
1965    var hint = _hintQuerySelectorAll('.introjs-hint[data-step="' + stepId + '"]')[0];
1966
1967    if (hint) {
1968      _removeClass(hint, /introjs-hidehint/g);
1969    }
1970  }
1971
1972  /**
1973   * Removes all hint elements on the page
1974   * Useful when you want to destroy the elements and add them again (e.g. a modal or popup)
1975   *
1976   * @api private
1977   * @method _removeHints
1978   */
1979  function _removeHints() {
1980    var hints = _hintQuerySelectorAll('.introjs-hint');
1981
1982    _forEach(hints, function (hint) {
1983      _removeHint.call(this, hint.getAttribute('data-step'));
1984    }.bind(this));
1985  }
1986
1987  /**
1988   * Remove one single hint element from the page
1989   * Useful when you want to destroy the element and add them again (e.g. a modal or popup)
1990   * Use removeHints if you want to remove all elements.
1991   *
1992   * @api private
1993   * @method _removeHint
1994   */
1995  function _removeHint(stepId) {
1996    var hint = _hintQuerySelectorAll('.introjs-hint[data-step="' + stepId + '"]')[0];
1997
1998    if (hint) {
1999      hint.parentNode.removeChild(hint);
2000    }
2001  }
2002
2003  /**
2004   * Add all available hints to the page
2005   *
2006   * @api private
2007   * @method _addHints
2008   */
2009  function _addHints() {
2010    var self = this;
2011
2012    var hintsWrapper = document.querySelector('.introjs-hints');
2013
2014    if (hintsWrapper === null) {
2015      hintsWrapper = document.createElement('div');
2016      hintsWrapper.className = 'introjs-hints';
2017    }
2018
2019    /**
2020    * Returns an event handler unique to the hint iteration
2021    * 
2022    * @param {Integer} i
2023    * @return {Function}
2024    */
2025    var getHintClick = function (i) {
2026      return function(e) {
2027        var evt = e ? e : window.event;
2028        
2029        if (evt.stopPropagation) {
2030          evt.stopPropagation();
2031        }
2032
2033        if (evt.cancelBubble !== null) {
2034          evt.cancelBubble = true;
2035        }
2036
2037        _showHintDialog.call(self, i);
2038      };
2039    };
2040
2041    _forEach(this._introItems, function(item, i) {
2042      // avoid append a hint twice
2043      if (document.querySelector('.introjs-hint[data-step="' + i + '"]')) {
2044        return;
2045      }
2046
2047      var hint = document.createElement('a');
2048      _setAnchorAsButton(hint);
2049
2050      hint.onclick = getHintClick(i);
2051
2052      hint.className = 'introjs-hint';
2053
2054      if (!item.hintAnimation) {
2055        _addClass(hint, 'introjs-hint-no-anim');
2056      }
2057
2058      // hint's position should be fixed if the target element's position is fixed
2059      if (_isFixed(item.element)) {
2060        _addClass(hint, 'introjs-fixedhint');
2061      }
2062
2063      var hintDot = document.createElement('div');
2064      hintDot.className = 'introjs-hint-dot';
2065      var hintPulse = document.createElement('div');
2066      hintPulse.className = 'introjs-hint-pulse';
2067
2068      hint.appendChild(hintDot);
2069      hint.appendChild(hintPulse);
2070      hint.setAttribute('data-step', i);
2071
2072      // we swap the hint element with target element
2073      // because _setHelperLayerPosition uses `element` property
2074      item.targetElement = item.element;
2075      item.element = hint;
2076
2077      // align the hint position
2078      _alignHintPosition.call(this, item.hintPosition, hint, item.targetElement);
2079
2080      hintsWrapper.appendChild(hint);
2081    }.bind(this));
2082
2083    // adding the hints wrapper
2084    document.body.appendChild(hintsWrapper);
2085
2086    // call the callback function (if any)
2087    if (typeof (this._hintsAddedCallback) !== 'undefined') {
2088      this._hintsAddedCallback.call(this);
2089    }
2090  }
2091
2092  /**
2093   * Aligns hint position
2094   *
2095   * @api private
2096   * @method _alignHintPosition
2097   * @param {String} position
2098   * @param {Object} hint
2099   * @param {Object} element
2100   */
2101  function _alignHintPosition(position, hint, element) {
2102    // get/calculate offset of target element
2103    var offset = _getOffset.call(this, element);
2104    var iconWidth = 20;
2105    var iconHeight = 20;
2106
2107    // align the hint element
2108    switch (position) {
2109      default:
2110      case 'top-left':
2111        hint.style.left = offset.left + 'px';
2112        hint.style.top = offset.top + 'px';
2113        break;
2114      case 'top-right':
2115        hint.style.left = (offset.left + offset.width - iconWidth) + 'px';
2116        hint.style.top = offset.top + 'px';
2117        break;
2118      case 'bottom-left':
2119        hint.style.left = offset.left + 'px';
2120        hint.style.top = (offset.top + offset.height - iconHeight) + 'px';
2121        break;
2122      case 'bottom-right':
2123        hint.style.left = (offset.left + offset.width - iconWidth) + 'px';
2124        hint.style.top = (offset.top + offset.height - iconHeight) + 'px';
2125        break;
2126      case 'middle-left':
2127        hint.style.left = offset.left + 'px';
2128        hint.style.top = (offset.top + (offset.height - iconHeight) / 2) + 'px';
2129        break;
2130      case 'middle-right':
2131        hint.style.left = (offset.left + offset.width - iconWidth) + 'px';
2132        hint.style.top = (offset.top + (offset.height - iconHeight) / 2) + 'px';
2133        break;
2134      case 'middle-middle':
2135        hint.style.left = (offset.left + (offset.width - iconWidth) / 2) + 'px';
2136        hint.style.top = (offset.top + (offset.height - iconHeight) / 2) + 'px';
2137        break;
2138      case 'bottom-middle':
2139        hint.style.left = (offset.left + (offset.width - iconWidth) / 2) + 'px';
2140        hint.style.top = (offset.top + offset.height - iconHeight) + 'px';
2141        break;
2142      case 'top-middle':
2143        hint.style.left = (offset.left + (offset.width - iconWidth) / 2) + 'px';
2144        hint.style.top = offset.top + 'px';
2145        break;
2146    }
2147  }
2148
2149  /**
2150   * Triggers when user clicks on the hint element
2151   *
2152   * @api private
2153   * @method _showHintDialog
2154   * @param {Number} stepId
2155   */
2156  function _showHintDialog(stepId) {
2157    var hintElement = document.querySelector('.introjs-hint[data-step="' + stepId + '"]');
2158    var item = this._introItems[stepId];
2159
2160    // call the callback function (if any)
2161    if (typeof (this._hintClickCallback) !== 'undefined') {
2162      this._hintClickCallback.call(this, hintElement, item, stepId
2162);
2163    }
2164
2165    // remove all open tooltips
2166    var removedStep = _removeHintTooltip.call(this);
2167
2168    // to toggle the tooltip
2169    if (parseInt(removedStep, 10) === stepId) {
2170      return;
2171    }
2172
2173    var tooltipLayer = document.createElement('div');
2174    var tooltipTextLayer = document.createElement('div');
2175    var arrowLayer = document.createElement('div');
2176    var referenceLayer = document.createElement('div');
2177
2178    tooltipLayer.className = 'introjs-tooltip';
2179
2180    tooltipLayer.onclick = function (e) {
2181      //IE9 & Other Browsers
2182      if (e.stopPropagation) {
2183        e.stopPropagation();
2184      }
2185      //IE8 and Lower
2186      else {
2187        e.cancelBubble = true;
2188      }
2189    };
2190
2191    tooltipTextLayer.className = 'introjs-tooltiptext';
2192
2193    var tooltipWrapper = document.createElement('p');
2194    tooltipWrapper.innerHTML = item.hint;
2195
2196    var closeButton = document.createElement('a');
2197    closeButton.className = this._options.buttonClass;
2198    closeButton.setAttribute('role', 'button');
2199    closeButton.innerHTML = this._options.hintButtonLabel;
2200    closeButton.onclick = _hideHint.bind(this, stepId);
2201
2202    tooltipTextLayer.appendChild(tooltipWrapper);
2203    tooltipTextLayer.appendChild(closeButton);
2204
2205    arrowLayer.className = 'introjs-arrow';
2206    tooltipLayer.appendChild(arrowLayer);
2207
2208    tooltipLayer.appendChild(tooltipTextLayer);
2209
2210    // set current step for _placeTooltip function
2211    this._currentStep = hintElement.getAttribute('data-step');
2212
2213    // align reference layer position
2214    referenceLayer.className = 'introjs-tooltipReferenceLayer introjs-hintReference';
2215    referenceLayer.setAttribute('data-step', hintElement.getAttribute('data-step'));
2216    _setHelperLayerPosition.call(this, referenceLayer);
2217
2218    referenceLayer.appendChild(tooltipLayer);
2219    document.body.appendChild(referenceLayer);
2220
2221    //set proper position
2222    _placeTooltip.call(this, hintElement, tooltipLayer, arrowLayer, null, true);
2223  }
2224
2225  /**
2226   * Get an element position on the page
2227   * Thanks to `meouw`: http://stackoverflow.com/a/442474/375966
2228   *
2229   * @api private
2230   * @method _getOffset
2231   * @param {Object} element
2232   * @returns Element's position info
2233   */
2234  function _getOffset(element) {
2235    var body = document.body;
2236    var docEl = document.documentElement;
2237    var scrollTop = window.pageYOffset || docEl.scrollTop || body.scrollTop;
2238    var scrollLeft = window.pageXOffset || docEl.scrollLeft || body.scrollLeft;
2239    var x = element.getBoundingClientRect();
2240    return {
2241      top: x.top + scrollTop,
2242      width: x.width,
2243      height: x.height,
2244      left: x.left + scrollLeft
2245    };
2246  }
2247
2248  /**
2249  * Find the nearest scrollable parent
2250  * copied from https://stackoverflow.com/questions/35939886/find-first-scrollable-parent
2251  *
2252  * @param Element element
2253  * @return Element
2254  */
2255  function _getScrollParent(element) {
2256    var style = window.getComputedStyle(element);
2257    var excludeStaticParent = (style.position === "absolute");
2258    var overflowRegex = /(auto|scroll)/;
2259
2260    if (style.position === "fixed") return document.body;
2261    
2262    for (var parent = element; (parent = parent.parentElement);) {
2263      style = window.getComputedStyle(parent);
2264      if (excludeStaticParent && style.position === "static") {
2265        continue;
2266      }
2267      if (overflowRegex.test(style.overflow + style.overflowY + style.overflowX)) return parent;
2268    }
2269
2270    return document.body;
2271  }
2272
2273  /**
2274  * scroll a scrollable element to a child element
2275  *
2276  * @param Element parent
2277  * @param Element element
2278  * @return Null
2279  */
2280  function _scrollParentToElement (parent, element) {
2281    parent.scrollTop = element.offsetTop - parent.offsetTop;
2282  }
2283
2284  /**
2285   * Gets the current progress percentage
2286   *
2287   * @api private
2288   * @method _getProgress
2289   * @returns current progress percentage
2290   */
2291  function _getProgress() {
2292    // Steps are 0 indexed
2293    var currentStep = parseInt((this._currentStep + 1), 10);
2294    return ((currentStep / this._introItems.length) * 100);
2295  }
2296
2297  /**
2298   * Overwrites obj1's values with obj2's and adds obj2's if non existent in obj1
2299   * via: http://stackoverflow.com/questions/171251/how-can-i-merge-properties-of-two-javascript-objects-dynamically
2300   *
2301   * @param obj1
2302   * @param obj2
2303   * @returns obj3 a new object based on obj1 and obj2
2304   */
2305  function _mergeOptions(obj1,obj2) {
2306    var obj3 = {},
2307      attrname;
2308    for (attrname in obj1) { obj3[attrname] = obj1[attrname]; }
2309    for (attrname in obj2) { obj3[attrname] = obj2[attrname]; }
2310    return obj3;
2311  }
2312
2313  var introJs = function (targetElm) {
2314    var instance;
2315
2316    if (typeof (targetElm) === 'object') {
2317      //Ok, create a new instance
2318      instance = new IntroJs(targetElm);
2319
2320    } else if (typeof (targetElm) === 'string') {
2321      //select the target element with query selector
2322      var targetElement = document.querySelector(targetElm);
2323
2324      if (targetElement) {
2325        instance = new IntroJs(targetElement);
2326      } else {
2327        throw new Error('There is no element with given selector.');
2328      }
2329    } else {
2330      instance = new IntroJs(document.body);
2331    }
2332    // add instance to list of _instances
2333    // passing group to _stamp to increment
2334    // from 0 onward somewhat reliably
2335    introJs.instances[ _stamp(instance, 'introjs-instance') ] = instance;
2336
2337    return instance;
2338  };
2339
2340  /**
2341   * Current IntroJs version
2342   *
2343   * @property version
2344   * @type String
2345   */
2346  introJs.version = VERSION;
2347
2348  /**
2349  * key-val object helper for introJs instances
2350  *
2351  * @property instances
2352  * @type Object
2353  */
2354  introJs.instances = {};
2355
2356  //Prototype
2357  introJs.fn = IntroJs.prototype = {
2358    clone: function () {
2359      return new IntroJs(this);
2360    },
2361    setOption: function(option, value) {
2362      this._options[option] = value;
2363      return this;
2364    },
2365    setOptions: function(options) {
2366      this._options = _mergeOptions(this._options, options);
2367      return this;
2368    },
2369    start: function (group) {
2370      _introForElement.call(this, this._targetElement, group);
2371      return this;
2372    },
2373    goToStep: function(step) {
2374      _goToStep.call(this, step);
2375      return this;
2376    },
2377    addStep: function(options) {
2378      if (!this._options.steps) {
2379        this._options.steps = [];
2380      }
2381
2382      this._options.steps.push(options);
2383
2384      return this;
2385    },
2386    addSteps: function(steps) {
2387      if (!steps.length) return;
2388
2389      for(var index = 0; index < steps.length; index++) {
2390        this.addStep(steps[index]);
2391      }
2392
2393      return this;
2394    },
2395    goToStepNumber: function(step) {
2396      _goToStepNumber.call(this, step);
2397
2398      return this;
2399    },
2400    nextStep: function() {
2401      _nextStep.call(this);
2402      return this;
2403    },
2404    previousStep: function() {
2405      _previousStep.call(this);
2406      return this;
2407    },
2408    exit: function(force) {
2409      _exitIntro.call(this, this._targetElement, force);
2410      return this;
2411    },
2412    refresh: function() {
2413      _refresh.call(this);
2414      return this;
2415    },
2416    onbeforechange: function(providedCallback) {
2417      if (typeof (providedCallback) === 'function') {
2418        this._introBeforeChangeCallback = providedCallback;
2419      } else {
2420        throw new Error('Provided callback for onbeforechange was not a function');
2421      }
2422      return this;
2423    },
2424    onchange: function(providedCallback) {
2425      if (typeof (providedCallback) === 'function') {
2426        this._introChangeCallback = providedCallback;
2427      } else {
2428        throw new Error('Provided callback for onchange was not a function.');
2429      }
2430      return this;
2431    },
2432    onafterchange: function(providedCallback) {
2433      if (typeof (providedCallback) === 'function') {
2434        this._introAfterChangeCallback = providedCallback;
2435      } else {
2436        throw new Error('Provided callback for onafterchange was not a function');
2437      }
2438      return this;
2439    },
2440    oncomplete: function(providedCallback) {
2441      if (typeof (providedCallback) === 'function') {
2442        this._introCompleteCallback = providedCallback;
2443      } else {
2444        throw new Error('Provided callback for oncomplete was not a function.');
2445      }
2446      return this;
2447    },
2448    onhintsadded: function(providedCallback) {
2449      if (typeof (providedCallback) === 'function') {
2450        this._hintsAddedCallback = providedCallback;
2451      } else {
2452        throw new Error('Provided callback for onhintsadded was not a function.');
2453      }
2454      return this;
2455    },
2456    onhintclick: function(providedCallback) {
2457      if (typeof (providedCallback) === 'function') {
2458        this._hintClickCallback = providedCallback;
2459      } else {
2460        throw new Error('Provided callback for onhintclick was not a function.');
2461      }
2462      return this;
2463    },
2464    onhintclose: function(providedCallback) {
2465      if (typeof (providedCallback) === 'function') {
2466        this._hintCloseCallback = providedCallback;
2467      } else {
2468        throw new Error('Provided callback for onhintclose was not a function.');
2469      }
2470      return this;
2471    },
2472    onexit: function(providedCallback) {
2473      if (typeof (providedCallback) === 'function') {
2474        this._introExitCallback = providedCallback;
2475      } else {
2476        throw new Error('Provided callback for onexit was not a function.');
2477      }
2478      return this;
2479    },
2480    onskip: function(providedCallback) {
2481      if (typeof (providedCallback) === 'function') {
2482        this._introSkipCallback = providedCallback;
2483      } else {
2484        throw new Error('Provided callback for onskip was not a function.');
2485      }
2486      return this;
2487    },
2488    onbeforeexit: function(providedCallback) {
2489      if (typeof (providedCallback) === 'function') {
2490        this._introBeforeExitCallback = providedCallback;
2491      } else {
2492        throw new Error('Provided callback for onbeforeexit was not a function.');
2493      }
2494      return this;
2495    },
2496    addHints: function() {
2497      _populateHints.call(this, this._targetElement);
2498      return this;
2499    },
2500    hideHint: function (stepId) {
2501      _hideHint.call(this, stepId);
2502      return this;
2503    },
2504    hideHints: function () {
2505      _hideHints.call(this);
2506      return this;
2507    },
2508    showHint: function (stepId) {
2509      _showHint.call(this, stepId);
2510      return this;
2511    },
2512    showHints: function () {
2513      _showHints.call(this);
2514      return this;
2515    },
2516    removeHints: function () {
2517      _removeHints.call(this);
2518      return this;
2519    },
2520    removeHint: function (stepId) {
2521      _removeHint.call(this, stepId);
2522      return this;
2523    },
2524    showHintDialog: function (stepId) {
2525      _showHintDialog.call(this, stepId);
2526      return this;
2527    }
2528  };
2529
2530  return introJs;
2531});

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.