PageSourceSearch

https://okloza.ru/tools/iexModal/iexModal.bundle.js?1686937070108323

js okloza.ru collected 2026-10-07 04:10:19 UTC 108,323 bytes, 2,992 lines download raw bytes

1(function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i<t.length;i++)o(t[i]);return o}return r})()({1:[function(require,module,exports){
2/**
3 * iexModal
4 * @constructor
5 */
6window.IexModal = function($){
7    var app = this;
8
9    app.PerfectScrollbar = require('./perfect-srollbar.js');
10
11    app.$window = $(window);
12    app.$document = $(document);
13    app.$html = $('html');
14    app.$body = $('body');
15
16    app.classWrap = 'iexmodal';
17
18    // Классы элементов интерфейса
19    app.classPopup = app.classWrap+'-popup';
20    app.classClose = app.classWrap+'-close';
21    app.classCloseAll = app.classWrap+'-close-all';
22    app.classInner = app.classWrap+'-inner';
23    app.classInnerWrap = app.classWrap+'-inner-wrap';
24    app.classBtn = app.classWrap+'-show';
25    app.classSprite = app.classWrap+'-sprite';
26    app.classContent = app.classWrap+'-content';
27    app.classContentWrap = app.classWrap+'-content-wrap';
28    app.classDebugWrap = app.classWrap+'-debug-wrap';
29    app.classDebugCode = app.classWrap+'-debug-code';
30
31    // Классы-модификаторы (классы состояния)
32    app.classOpened = app.classWrap+'-opened';
33    app.classOuterScroll = app.classWrap+'-outerscroll';
34    app.classLoading = app.classWrap+'-loading';
35    app.classBottom = app.classWrap+'-bottom';
36    app.classOverflowY = app.classWrap+'-overflow-y';
37    app.classOverflowX = app.classWrap+'-overflow-x';
38
39    app.popupWidthDefault = 450; // px
40    // в качестве app.popupHeightDefault используется высота контента модала
41    app.popupHeightDefaultForTopBottom = 250; // px
42    app.popupIndentX = 10;
43    app.popupIndentY = 10;
44
45    app.windowWidth = 0;
46    app.windowHeight = 0;
47
48    app.fadeInTime = 500;
49    app.fadeOutTime = 300;
50    app.ajaxTimeOut = 25000;
51
52    app.errLogMsg = 'iexModal: Ошибка показа окна!';
53    app.errAjaxMsg = 'iexModal: Ошибка загрузки контента!';
54    app.errScriptLoadingMsg = 'iexModal: Отменена повторная инициализация скрипта! Уже загружена другая копия/версия iexModal, - ее настройки и состояние затронуты не были!';
55    app.repAjaxMsg = 'iexModal: Отчет об ajax-запросе';
56
57    /**
58     * Поддерживаемые варианты получения html-контента для показа в модальном окне
59     * @type {string[]}
60     */
61    app.loadTypes = [ 
62        'url',
63        'html',
64        'id',
65        'selector', 
66        'jquery'
67    ];
68
69    /**
70     * Атрибуты, которые разрешено считывать с кнопки/ссылки показа окна и враппера контента
71     * и соответсвующие им параметры для app.show()
72     */
73    app.attrsAndParams = {
74        'href':                          'url',
75        'data-iexmodal-html':            'html',
76        'data-iexmodal-selector':        'selector',
77        'data-iexmodal-id':              'id',
78        'data-iexmodal-mode':            'mode',
79        'data-iexmodal-classes':         'classes',
80        'data-iexmodal-width':           'width',
81        'data-iexmodal-height':          'height',
82        'data-iexmodal-overlay':         'overlay',
83        'data-iexmodal-content-wrap':    'contentWrap',
84        'data-iexmodal-content-scroll':  'contentScroll',
85        'data-iexmodal-document-scroll': 'documentScroll',
86        'data-iexmodal-outer-scroll':    'outerScroll',
87        'data-iexmodal-timeout':         'timeout',
88        'data-iexmodal-ajax-report':     'ajaxReport',
89        'data-iexmodal-calc-zindex':     'calcZindex',
90        'data-iexmodal-animation-prefix': 'animationPrefix',
91    };
92
93    /**
94     * Атрибуты из app.attrsAndParams, которые разрешено использовать модулю iexForm на кнопках/ссылках показа всплывающих форм
95     */
96    app.iexFormAttrs = [
97        'data-iexmodal-mode',
98        'data-iexmodal-classes',
99        'data-iexmodal-width',
100        'data-iexmodal-height',
101        'data-iexmodal-overlay',
102        'data-iexmodal-content-scroll',
103        'data-iexmodal-document-scroll',
104        'data-iexmodal-outer-scroll',
105        'data-iexmodal-timeout',
106        'data-iexmodal-ajax-report',
107        'data-iexmodal-calc-zindex',
108        'data-iexmodal-animation-prefix'
109    ];
110
111    /**
112     * Названия разрешенных пользовательских коллбэÐ
112ºÐ¾Ð²
113     * @type {Array}
114     */
115    app.userCallbackNames = [
116        'onBeforeShow',
117        'onAfterContentLoad',
118        'onAfterShow',
119        'onBeforeClose',
120        'onAfterClose'
121    ];
122
123    /**
124     * Массив разрешенных параметров для app.show().
125     * Перечень параметров берется из:
126     * 1) из самого массива
127     * 2) app.attrsAndParams (эти параметры можно передавать как через API так и через data-атрибуты)
128     * 3) app.userCallbackNames (эти параметры можно передавать только через API)
129     * Заполняется в app.__init().
130     */
131    app.params = [
132        'jquery'
133    ];
134
135    /**
136     * Colorbox
137     */
138    app.$colorboxOverlay = null;
139    app.$colorboxWindow = null;
140    app.colorboxFound = false;
141
142    /**
143     * Возможные режимы показа модального окна.
144     * @property {object}
145     */
146    app.modes = {
147        'default':'default',
148        'fullscreen':'fullscreen',
149        'top':'top',
150        'right':'right',
151        'bottom':'bottom',
152        'left':'left',
153        'alert':'alert'
154    };
155
156    /**
157     * Объект-коллекция модальных окон
158     * @type {object}
159     */
160    app.modals = {};
161
162    /**
163     * Id самого верхнего окна (по z-index)
164     * @type {boolean}
165     */
166    app.modalsTopId = false;
167
168    /**
169     * В мобильном IE console отсутствует и вызов его методов вызовет ошибки
170     * Попутно используем app.debugPanel.log()
171     */
172    app.log = function(){
173        if ( window.console && window.console.log ) {
174            console.log.apply(console, arguments);
175        }
176        app.debugPanel.log.apply(app.debugPanel, arguments);
177    };
178
179    app.getIEVersion = function () {
180        var sAgent = window.navigator.userAgent;
181        var Idx = sAgent.indexOf("MSIE");
182
183        // IE, return version number.
184        if (Idx > 0) {
185            return parseInt(sAgent.substring(Idx + 5, sAgent.indexOf(".", Idx)));
186
187        // IE 11 then look for Updated user agent string.
188        } else if (!!navigator.userAgent.match(/Trident\/7\./)) {
189            return 11;
190
191        // not IE
192        } else {
193            return 0;
194        }
195    };
196
197    /**
198     * Панель (окно) отладки для мобильных платформ (вместо console.log).
199     *
200     * Можно использовать после вызова:
201     *      app.debugPanel.on()
202     * Пример использования:
203     *      app.debugPanel.log($x, $y, $z)
204     * Стили можно править через CSS или:
205     *      app.debugPanel.$wrap.css('стили')
206     */
207    app.DebugPanel = function(){
208        var panel = this;
209
210        panel.active = false;
211        panel.time = false;
212        panel.$wrap = $();
213        panel.msg = '-----------------------\niexModal: Панель отладки\n-----------------------';
214        panel.html = '<div class="iexmodal-debug-panel">'+panel.msg+'</div>';
215
216        panel._add = function () {
217            panel.$wrap = $(panel.html).appendTo(app.$body);
218        };
219        panel._remove = function () {
220            panel.$wrap.remove();
221            panel.$wrap = $();
222        };
223
224        panel._ts = function(){
225            var time = new Date();
226
227            function format2(val){
228                return ("0"+val).slice(-2);
229            }
230            function format3(val){
231                return (val+"00").slice(0,3);
232            }
233
234            return (
235                format2(time.getHours()) + ':' +
236                format2(time.getMinutes()) + ':' +
237                format2(time.getSeconds()) + ':' +
238                format3(time.getMilliseconds())
239            )
240        };
241
242        panel.clear = function(){
243            panel.$wrap.html(panel.msg)
244        };
245
246        panel.on = function(params){
247            params = params || {};
248            panel.time = (typeof params.time === 'boolean') ? params.time : false;
249
250            if ( panel.$wrap.length ) {
251                panel._remove();
252            }
253            panel._add();
254            panel.active = true;
255        };
256        panel.off = function(){
257            panel._remove();
258            panel.active = false;
259        };
260
261        panel.log = function(){
262            if ( !panel.active ) return;
263
264            var time = panel.time ? panel._ts() + ' ' : '';
265
266            var args = [].slice.call(arguments);
267            var text = '';
268            $(args).each(function (i,val) {
269                text += typeof val === 'object' ? JSON.stringify(val) : val;
270                text += ' ';
271            });
272
273            panel.$wrap.text(panel.$wrap.text() + '\n' + time + text);
274            panel.$wrap[0].scrollTop = panel.$wrap[0].scrollHeight;
275        };
276    };
277    app.debugPanel = new app.DebugPanel();
278
279    /**
280     * Возвращает объект модального окна
281     * - с заданным wrapId
282     * - самого верхнего, если wrapId не задан
283     *
284     * @param wrapId {string} Необязательный параметр
285     * @return {*} Объект модального окна или false
286     */
287    app.getModal = function (wrapId) {
288        var modal = false;
289        if (wrapId) {
290            if (typeof wrapId === 'string') {
291                if (app.modals[wrapId]) {
292                    modal = app.modals[wrapId];
293                }
294            }
295        } else {
296            if (app.modalsTopId && app.modals[app.modalsTopId]) {
297                modal = app.modals[app.modalsTopId];
298            }
299        }
300        return modal;
301    };
302
303    /**
304     * Имеется ли окно с заданным id в списке открытых
305     *
306     * @param id
307     * @return {*}
308     */
309    app.isOpened = function (id){
310        return id && app.modals[id];
311    };
312
313    /**
314     * Возвращает UID из 5 цифр
315     * @return {number}
316     */
317    app.getUid = function(){
318        return Math.floor(10000 + Math.random()*89999);
319    };
320
321    app.inArray = function (val, arr) {
322        if ( typeof arr !== 'object') return false;
323        return arr.indexOf(val) > -1;
324    };
325
326    /**
327     * Достает первое по порядку значение в объекте
328     * @param obj
329     * @returns {*}
330     */
331    app.firstInObj = function (obj) {
332        if (typeof obj !== 'object') return false;
333        return obj[Object.keys(obj)[0]];
334    };
335
336    /**
337     * Достает ключ последнего по порядку элемента в объекте
338     * @param obj
339     * @returns {*}
340     */
341    app.lastKeyInObj = function (obj) {
342        if (typeof obj !== 'object') {
343            return false;
344        }
345        var keys = Object.keys(obj);
346        return keys.length ? keys[keys.length - 1] : false;
347    };
348
349    /**
350     * Возвращает последний элемент в массиве или false
351     * @param arr {object}
352     * @returns {object|false}
353     */
354    app.lastInArr = function (arr) {
355        if (typeof arr !== 'object') return false;
356        return arr.length ? arr[ arr.length-1 ] : false;
357    };
358
359    /**
360     * Получает допустимый режим показа окна из кнопки/ссылки
361     * @returns {string}
362     */
363    app.getMode = function (mode) {
364        if (!mode) return app.firstInObj(app.modes);
365        return app.modes[mode] ? mode : app.firstInObj(app.modes);
366    };
367
368    /**
369     * Получает CSS-класс-модификатор для режима показа
370     * @returns {string}
371     */
372    app.getModeClass = function (mode) {
373        return mode==='default' ? '' : app.classWrap+'-'+mode;
374    };
375
376    /**
377     * Вычисление ширины скробара (у каждого браузера она своя)
378     * Позаимствовано из bootstrap.modal.js
379     */
380    app.getScrollbarWidth = function () {
381        var divStyles =
382            'position: absolute; '+
383            'top: -9999px; '+
384            'width: 50px; '+
385            'height: 50px; '+
386            'overflow: scroll;';
387        var scrollDiv = document.createElement('div');
388        scrollDiv.setAttribute('style', divStyles);
389        app.$body.append(scrollDiv);
390        var scrollbarWidth = scrollDiv.offsetWidth - scrollDiv.clientWidth;
391        app.$body[0].removeChild(scrollDiv);
392        return scrollbarWidth
393    };
394
395    /**
396     * Интеграция с Colorbox.
397     * Нужна когда из iexModal-окна открываются Сolorbox-окна.
398     * Устанавливаем Сolorbox-окну z-index больший чем у iexModal-окна.
399     */
400    app.onColorboxOpen = function(){
401        if (app.modalsTopId) {
402            var zIndex = app.getWrapZindex();
403            app.$colorboxOverlay.css('z-index', zIndex+1);
404            app.$colorboxWindow.css('z-index', zIndex+2);
405        }
406    };
407    app.onColorboxClosed = function(){
408        if (app.modalsTopId) {
409            app.$colorboxOverlay.css('z-index', '');
410            app.$colorboxWindow.css('z-index', '');
411        }
412    };
413    app.colorboxInit = function(){
414        app.$colorboxOverlay = $('#cboxOverlay');
415        app.$colorboxWindow = $('#colorbox');
416        app.colorboxFound = app.$colorboxOverlay.length && app.$colorboxWindow.length;
417        if (app.colorboxFound) {
418            app.$document
419                .on('cbox_open', app.onColorboxOpen)
420                .on('cbox_closed', app.onColorboxClosed);
421        }
422    };
423
424    /**
425     * Текущий размер окна браузера в виде объекта {width: 450px, height: 600px}.
426     * @return {object}
427     */
428    app.getWindowSize = function () {
429        if (window.visualViewport) {
430            app.windowWidth = window.visualViewport.width;
431            app.windowHeight = window.visualViewport.height;
432        } else {
433            app.windowWidth = app.$window.width();
434            app.windowHeight = app.$window.height();
435            // Алтернатива для вышерасположенный кода, если он срабатывает некорректно (замечено на некторых сайтах)
436            // app.windowWidth = document.body.clientWidth;
437            // app.windowHeight = document.body.clientHeight;
438        }
439        return {width: app.windowWidth, height: app.windowHeight}
440    };
441
442    /**
443     * Получить максимальный z-index на странице
444     * @return {int}
445     */
446    app.getMaxZIndex = function (){
447        var maxZ = -1;
448        $('body *').not('.iexmodal-debug-panel').each(function(i,el){
449            var pos = $(el).css('position');
450            if(pos === 'absolute' || pos === 'fixed'){
451                var z = parseInt($(el).css('z-index')) || 1;
452                maxZ = Math.max(z, maxZ);
453            }
454        });
455        return maxZ;
456    };
457
458    app.getWrapZindex = function () {
459        var zMax = app.getMaxZIndex();
460        /*
461        var zCur = parseInt( app.$wrap.css('z-index') );
462        return (zMax===zCur ? zMax : zMax+1);
463        */
464        return zMax+1;
465    };
466
467    /**
468     * Корректировка размера и положения окна по горизонтали
469     * Не вызывается для режимов fullscreen, top и bottom
470     *
471     * @param modal {object} Объект модального окна из app.modals
472     */
473    app.correctPositionX = function (modal) {
474        var maxPopupWidth = modal.width || app.popupWidthDefault;
475        var breakpointWidth = parseInt(maxPopupWidth) + app.popupIndentX * 2;
476        var overflowX = app.windowWidth <= breakpointWidth;
477
478        if (!overflowX && modal.overflowX) {
479            modal.$wrap.removeClass(app.classOverflowX);
480        }
481        if (overflowX && !modal.overflowX) {
482            modal.$wrap.addClass(app.classOverflowX);
483            modal.$popup.css('width', '');
484            if (!modal.outerScroll) {
485                modal.$popup.css('margin-left', '');
486            }
487        }
488        if (
489            !overflowX && !modal.overflowX ||
490            !overflowX && modal.overflowX
491        ) {
492            modal.$popup.css('width', maxPopupWidth);
493            if (
494                !modal.outerScroll &&
495                modal.mode === 'default'
496            ) {
497                modal.$popup.css('margin-left', '-' + parseInt(maxPopupWidth)/2 + 'px');
498            }
499        }
500
501        modal.overflowX = overflowX;
502    };
503
504    /**
505     * Возвращает высоту контента попапа в сумме с вертикальными паддингами modal.$inner
506     *
507     * @param modal {object} Объект модального окна из app.modals
508     * @return {int} Пиксели
509     */
510    app.getContentHeight = function(modal) {
511        return Math.ceil(
512            modal.$innerWrap.outerHeight() +
513            parseInt(modal.$inner.css('padding-top')) +
514            parseInt(modal.$inner.css('padding-bottom'))
515        );
516    };
517
518    /**
519     * Корректировка размера и положения окна по вертикали
520     * Не вызывается для режимов fullscreen, right и left
521     *
522     * @param modal {object} Объект модального окна из app.modals
523     */
524    app.correctPositionY = function (modal) {
525        function setPopupSize() {
526            if (modal.overflowModeEnable && !modal.overflowY) {
527                modal.$wrap.addClass(app.classOverflowY);
528                modal.$popup.css('margin-top', '').css('height', '');
529                modal.overflowY = true;
530            }
531            if (!modal.overflowModeEnable) {
532                if (modal.mode === 'default') {
533                    modal.$popup.css('margin-top', '-' + parseInt(modal.popupMaxHeight/2) + 'px');
534                }
535                modal.$popup.css('height', modal.popupMaxHeight+'px');
536                if (modal.overflowY) {
537                    modal.$wrap.removeClass(app.classOverflowY);
538                    modal.overflowY = false;
539                }
540            }
541        }
542
543        if (modal.positionFirstMesurement) {
544            modal.popupStoredTop = false;
545            modal.popupStoredBottom = false;
546            modal.popupContentHeight = app.getContentHeight(modal);
547            modal.popupMaxHeight = app.getPopupMaxHeight(modal);
548            modal.identYCoeff = app.inArray(modal.mode, ['top','bottom']) ? 1 : 2;
549            modal.breakpointHeight = modal.popupMaxHeight + app.popupIndentY * modal.identYCoeff;
550
551            modal.positionFirstMesurement = false;
552        }
553
554        modal.overflowModeEnable = modal.breakpointHeight >= app.windowHeight;
555
556        setPopupSize();
557
558        if (modal.mode === 'alert') {
559            if (!modal.overflowY && !modal.overflowModeEnable) {
560                var popupClientRect = modal.$popup[0].getBoundingClientRect();
561                if ((popupClientRect.bottom + app.popupIndentY) > app.windowHeight) {
562                    modal.overflowModeEnable = true;
563                    modal.popupStoredBottom = popupClientRect.bottom + app.popupIndentY;
564                }
565                if (!modal.overflowModeEnable && popupClientRect.top < app.popupIndentY) {
566                    modal.overflowModeEnable = true;
567                    modal.popupStoredTop = popupClientRect.top;
568                }
569                setPopupSize();
570            }
571            if (modal.overflowY) {
572                if (
573                    modal.overflowModeEnable &&
574                    modal.popupStoredBottom !== false &&
575                    modal.popupStoredBottom < app.windowHeight
576                ) {
577                    modal.overflowModeEnable = false;
578                    modal.popupStoredBottom = false;
579                }
580                if (
581                    modal.overflowModeEnable &&
582                    modal.popupStoredTop !== false &&
583                    modal.popupStoredTop > app.popupIndentY
584                ) {
585                    modal.overflowModeEnable = false;
586                    modal.popupStoredTop = false;
587                }
588                setPopupSize();
589            }
590        }
591    };
592
593    /**
594     * Корректировка размерров и положения открытых окон.
595     * Автоматически выполняется при каждом изменении размера окна браузера (onresize) и повороте мобильного устройства (onorientationchange).
596     * Нужно вызывать после изменения количества контента в окне/окнах, чтобы расположение на экране оставалось оптимальным.
597     *
598     * @return {boolean}
599     */
600    app.correctPosition = function(){
601        if ( !app.modalsTopId ) return false;
602
603        $.each(app.modals, function (wrapId, modal) {
604            if (modal.mode === 'fullscreen') {
605                return true;
606            }
607
608            // Установка ширины дожна выполняться ДО установки высоты, чтобы inline/inline-block
609            // элементы правильно распределились по строкам и дали правильную высоту контента
610            if (!app.inArray(modal.mode, ['top', 'bottom'])) {
611                app.correctPositionX(modal);
612            }
613            if (!app.inArray(modal.mode, ['left', 'right'])) {
614                app.correctPositionY(modal);
615            }
616            if (modal.ps) {
617                modal.ps.update();
618            }
619        });
620
621        return false;
622    };
623
624    /**
625     * Возвращает максимально возможную высоту попапа
626     *
627     * @param modal {object} Объект модального окна из app.modals
628     * @return {object} app
629     */
630    app.getPopupMaxHeight = function(modal){
631        var height = parseInt(modal.height);
632        if (
633            !height &&
634            app.inArray(modal.mode, ['top','bottom'])
635        ) {
636            height = app.popupHeightDefaultForTopBottom;
637        }
638        if (!height) {
639            height = modal.popupContentHeight; // в качестве app.popupHeightDefault используется высота контента модала
640        }
641        return height;
642    };
643
644    app.setPopupHeightOnceAfterLoad = false;
645
646    /**
647     * Рендерит html-код для модального окна, добавляет его начало документа и возвращает jQuery-ссылку
648     *
649     * @param id {string} Уникальный id модального окна из app.modals
650     * @return {jQuery} jQuery-ссылка на вставленный html
651     */
652    app.insertWrap = function(id) {
653        var html =
654            '<div class="' + app.classWrap + '" data-iexmodal-id="' + id + '">\n'+
655            '    <div class="' + app.classClose + ' iexmodal-close-outer">\n'+
656            '        <svg class="iexmodal-icon"><use xlink:href="#close"></use></svg>\n'+
657            '    </div>\n'+
658            '    <div class="' + app.classPopup + '">\n'+
659            '        <div class="' + app.classInner + '"><div class="'+app.classInnerWrap + '"></div></div>\n'+
660            '        <div class="' + app.classClose + ' iexmodal-close-inner">\n'+
661            '            <svg class="iexmodal-icon"><use xlink:href="#close"></use></svg>\n'+
662            '        </div>\n'+
663            '    </div>\n'+
664            '    <div class="iexmodal-spinner"></div>\n'+
665            '</div>';
666        return $(html).appendTo('body');
667    };
668
669    /**
670     * Вставляет SVG-спрайт в начало документа и возвращает jQuery-ссылку
671     *
672     * @return {jQuery}
673     */
674    app.insertSprite = function() {
675        var html =
676            '<svg class="' + app.classSprite + '" aria-hidden="true" style="position: absolute; width: 0; height: 0; overflow: hidden;" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">\n'+
677            '    <defs>\n'+
678            '        <symbol id="close" viewBox="0 0 18 18">\n'+
679            '            <title>close</title>\n'+
680            '            <line x1="2" y1="2" x2="16" y2="16"/>\n'+
681            '            <line x1="2" y1="16" x2="16" y2="2"/>\n'+
682            '        </symbol>\n'+
683            '    </defs>\n'+
684            '</svg>';
685        return $(html).appendTo('body');
686    };
687
688    /**
689     * jQuery-ссылка на вставленный в документ SVG-спрайт.
690     *
691     * @type {*}
692     */
693    app.$sprite = false;
694
695    /**
696     * Используется при app.loadType равным 'selector' или 'jquery'
697     *
698     * @return {jQuery}
699     */
700    app.insertPlaceholder = function($sibling) {
701        var html = '<div class="iexmodal-content-placeholder"></div>';
702        return $(html).insertAfter($sibling);
703    };
704
705    /**
706     * Получение data-атрибутов кнопки/ссылки
707     * в виде объекта c параметрами для app.show()
708     *
709     * @param $btn - jQuery-ссылка на кнопку/ссылку
710     * @return {*}
711     */
712    app.parseBtnAttrs = function ($btn) {
713        if (!($btn instanceof jQuery && $btn.length)) return false;
714        var obParsed = {};
715        $.each(app.attrsAndParams, function (btnAttr, showParam) {
716            obParsed[showParam] = $btn.attr(btnAttr) || ''; // $.data() может отдавать закэшированные (старые) значения при уже изменившемся DOM
717        });
718        return obParsed;
719    };
720
721    /**
722     * Подсчет количества переданных способов загрузки html-контента.
723     * Используется в app.validateParams() для проверки, что передан ровно один способ.
724     *
725     * @param params Параметры для app.show()
726     * @return {number}
727     */
728    app.loadTypesCnt = function (params) {
729        var cnt = 0;
730        $.each(app.loadTypes, function (key,val) {
731            cnt += params[val] ? 1 : 0;
732        });
733        return cnt;
734    };
735
736    /**
737     * Получение способа загрузки html-контента из параметров app.show()
738     *
739     * @param params Параметры для app.show()
740     * @return {boolean}
741     */
742    app.getLoadType = function (params) {
743        var loadType = false;
744        $.each(app.loadTypes, function (key,val) {
745            if ( !!params[val] ) {
746                loadType = val;
747                return false
748            }
749        });
750        return loadType;
751    };
752
753    app.getContentWrap = function(id){
754        return app.$body.find('.'+app.classContent+'[data-iexmodal-id="' + id + '"]').first();
755    };
756
757    /**
758     * Отчет об аякс-запросе, выполненного методом $.ajax()
759     *
760     * @param url {string} Урл загружаемого контента
761     * @param jqXHR {jqXHR} jqXHR-объект, который вернул метод $.ajax()
762     * @return {string} Html-код с отчетом
763     */
764    app.ajaxReport = function(url, jqXHR){
765        if ( !url || !jqXHR ) return '';
766
767        var responseText = jqXHR.responseJSON ? JSON.stringify(jqXHR.responseJSON) : jqXHR.responseText;
768        responseText = $('<div></div>').text(responseText).html();
769
770        return (
771            '<div class="' + app.classDebugWrap + '">' +
772            'URL: <a href="' + url + '" target="_blank">' + url + '</a><br>' +
773            'Timeout: ' + app.ajaxTimeOut/1000 + ' sec<br>' +
774            '<br>' +
775            'jqXHR.readyState: ' + jqXHR.readyState + '<br>' +
776            'jqXHR.status: ' + jqXHR.status + '<br>' +
777            'jqXHR.statusText: ' + jqXHR.statusText +
778            '<br><br>' +
779            'jqXHR.responseText / jqXHR.responseJSON:' +
780            '<div class="' + app.classDebugCode + '">' + responseText + '</div>' +
781            '</div>'
782        );
783    };
784
785    /**
786     * Валидация параметров для app.show().
787     * @param rawParams {object} Объект с 'сырыми' параметрами для проверки-исправления
788     * @return {object} Объект с проверенными-исправленными параметрами
789     */
790    app.validateParams = function(rawParams){
791        if (!rawParams) {
792            app.log(app.errLogMsg+' Не задан объект с параметрами. Смотрите документацию по iexModal.show().');
793            return false;
794        }
795        var validParams = {};
796
797        $.each(app.params, function(key, param) {
798            if ( app.inArray(param, app.userCallbackNames) ) {
799                if (typeof rawParams[param] === 'function') {
800                    validParams[param] = rawParams[param];
801                }
802            } else if ( app.inArray(param, ['jquery','selector']) || (typeof rawParams[param] === 'boolean' ) ) {
803                validParams[param] = rawParams[param];
804            } else {
805                validParams[param] = $.trim(rawParams[param] || '');
806            }
807
808            if (
809                param==='url' &&
810                validParams[param] &&
811                (
812                    validParams[param].indexOf('#')===0 ||
813                    validParams[param].indexOf('javascript:')>-1
814                )
815            ) {
816                validParams[param] = '';
817            }
818
819            if ( param==='mode' ) {
820                validParams[param] = app.getMode(validParams[param]);
821            }
822
823            if (
824                (param==='overlay' || param==='ajaxReport') &&
825                (typeof validParams[param] !== 'boolean')
826            ) {
827                validParams[param] = validParams[param]==='true';
828            }
829
830            if (
831                (param==='contentWrap' || param==='contentScroll' || param==='calcZindex') &&
832                (typeof validParams[param] !== 'boolean')
833            ) {
834                validParams[param] = validParams[param]!=='false';
835            }
836
837            if (
838                (param==='documentScroll' || param==='outerScroll') &&
839                (typeof validParams[param] !== 'boolean')
840            ) {
841                validParams[param] = validParams[param]==='true';
842            }
843
844            if ( param==='timeout' ) {
845                validParams[param] = parseInt(validParams[param]) || 0;
846            }
847
848            if ( param==='animationPrefix' ) {
849                validParams[param] = validParams[param].length ? validParams[param] : app.classWrap;
850            }
851        });
852
853        if (validParams['mode'] && validParams['mode'] !== 'default') {
854            validParams['classes'] = validParams['classes'] ? ' '+validParams['classes'] : '';
855            validParams['classes'] = app.getModeClass(validParams['mode']) + validParams['classes'];
856        }
857
858        if (validParams['outerScroll']) {
859            validParams['classes'] += ' ' + app.classOuterScroll;
860        }
861
862        // if (validParams['mode'] === 'fullscreen') {
863        //     validParams['width'] = '';
864        //     validParams['height'] = '';
865        // }
866
867        var
868            errMsg = '',
869            loadType = app.getLoadType(validParams),
870            loadTypesCnt = app.loadTypesCnt(validParams);
871
872        if (
873            !loadTypesCnt
874        ) {
875            errMsg = 'Не задан способ загрузки html-контента: ' + app.loadTypes.join(', ');
876
877        } else if (
878            app.loadTypesCnt(validParams) > 1
879        ) {
880            errMsg = 'Из списка параметров ' + app.loadTypes.join(', ') + ' одновременно можно передавать только ОДИН!';
881
882        } else if (
883            loadType==='selector' &&
884            !$(validParams['selector']).length
885        ) {
886            errMsg = 'Элемент с селектором "' + validParams['selector'] + '" не найден на странице!';
887
888        } else if (
889            loadType==='jquery' &&
890            !(validParams['jquery'] instanceof jQuery)
891        ) {
892            errMsg = 'Элемент, переданный в параметр jquery, не является экземпляром jQuery!';
893
894        } else if (
895            loadType==='jquery' &&
896            !validParams['jquery'].length
897        ) {
898            errMsg = 'Элемент, переданный в параметр jquery, не найден на странице!';
899
900        } else if (
901            loadType==='id' &&
902            !app.getContentWrap(validParams['id']).length
903        ) {
904            errMsg = 'Не найден враппер контента с data-iexmodal-id="' + validParams['id'] + '".';
905
906        } else if (
907            validParams['outerScroll'] &&
908            !app.inArray(validParams['mode'], ['default','fullscreen'])
909        ) {
910            errMsg = 'Опция с прокруткой всего модала и внешним скроллбаром (data-iexmodal-outer-scroll="true") работает только в режимах default и fullscreen!';
911        }
912
913        if ( errMsg ) {
914            app.log(app.errLogMsg +' ' + errMsg + ' Переданные параметры:', validParams);
915            return false
916        }
917
918        return validParams;
919    };
920
921    /**
922     * Добавляет модальное окно в HTML-кода и коллекцию app.modals
923     *
924     * @param params - параметры для app.show()
925     * @return {object} - объект созданного модального окна
926     */
927    app.create = function (params) {
928        var loadType = app.getLoadType(params);
929        var wrapId = loadType + '-' + ( loadType==='id' ? params.id : app.getUid() );
930        var $wrap = app.insertWrap(wrapId);
931
932        // перечень всех возможных полей объекта modal
933        var modal = app.modals[wrapId] = {
934            wrapId: wrapId,
935            loadType: loadType,
936            timeoutId: false,
937            contentArray: [],
938            $wrap: $wrap,
939            $popup: $wrap.find('.'+app.classPopup),
940            $inner: $wrap.find('.'+app.classInner),
941            $innerWrap: $wrap.find('.'+app.classInnerWrap),
942            $currWrapper: null,
943            $currContent: null,
944            $contentPlaceholder: null,
945            wrapInClass: app.animation.wrapClass(params, 'in'),
946            wrapOutClass: app.animation.wrapClass(params, 'out'),
947            popupInClass: app.animation.popupClass(params, 'in'),
948            popupOutClass: app.animation.popupClass(params, 'out'),
949            animInDone: false,
950            animOutDone: false,
951            mouseLeftButtonPushed: false,
952            overflowY: false,
953            overflowX: false,
954            positionFirstMesurement: true,
955            ps: null,
956            psDragging: false,
957            
958            // приходят из параметров метода show() и атрибутов data-iexmodal-*
959            url: null,
960            html: null,
961            selector: null,
962            id: null,
963            mode: null,
964            classes: null,
965            width: null,
966            height: null,
967            overlay: null,
968            contentScroll: null,
969            documentScroll: false,
970            outerScroll: false,
971            timeout: null,
972            ajaxReport: null,
973            calcZindex: null,
974            animationPrefix: null,
975            
976            // коллбэки
977            onBeforeShow: ()=>{},
978            onAfterContentLoad: ()=>{},
979            onAfterShow: ()=>{},
980            onBeforeClose: ()=>{},
981            onAfterClose: ()=>{}
982        };
983        modal = $.extend(modal, params);
984
985        modal.$popup.on('webkitAnimationEnd animationend', function (e) {
986            if (
987                !modal.animInDone &&
988                [modal.wrapInClass, modal.popupInClass].includes(e.originalEvent.animationName)
989            ) {
990                modal.animInDone = true;
991                app.animation.onInEnd(modal);
992            }
993
994            if (
995                !modal.animOutDone &&
996                [modal.wrapOutClass, modal.popupOutClass].includes(e.originalEvent.animationName)
997            ) {
998                modal.animOutDone = true;
999                app.animation.onOutEnd(modal);
1000            }
1001        });
1002
1003        // Заботимся чтобы во время перетаскивании скроллбара мышью случайно не кликался .iexmodal и окно не закрывалось
1004        function resetWrapSetts() {
1005            modal.psDragging = false;
1006            modal.$wrap.css('cursor','');
1007        }
1008
1009        if ( !modal.outerScroll && modal.contentScroll ) {
1010            modal.ps = new app.PerfectScrollbar( modal.$inner[0], {
1011                    wheelSpeed: 1, // сделать зависимой длинны (высоты контента) = parseInt(modal.$inner.outerHeight() / modal.$popup.outerHeight() * 2)
1012                    wheelPropagation: false,
1013                    minScrollbarLength: 60
1014                }
1015            );
1016            modal.$wrap.find('.ps__thumb-y')
1017                .on('mousedown', function (e) {
1018                    // e.which = 1 (левая), 2 (средня), 3 (правая)
1019                    modal.psDragging = true;
1020                    modal.$wrap.css('cursor','default');
1021                })
1022                .on('mouseup', function (e) {
1023                    resetWrapSetts();
1024                });
1025            modal.$wrap.find('.ps').on('mouseup', function (e) {
1026                if ( modal.psDragging ) {
1027                    resetWrapSetts();
1028                }
1029            });
1030        }
1031
1032        modal.$wrap
1033            .on('mousedown', function (e) { // если нажали именно над оверлеем и именно левую кнопку
1034                modal.mouseLeftButtonPushed = ( e.target === this && e.which === 1 );
1035            })
1036            .on('click', function (e) {
1037                if ( e.target === this ) {
1038                    if ( modal.ps && modal.psDragging ) {
1039                        resetWrapSetts();
1040                    } else {
1041                        if ( modal.mouseLeftButtonPushed ) {
1042                            app.close();
1043                        }
1044                    }
1045                }
1046            });
1047
1048        app.modalsTopId = wrapId;
1049
1050        return modal;
1051    };
1052
1053    /**
1054     * Перенос контента модального окна для типов загрузки 'selector' и 'jquery'
1055     * - moveToPopup() в модальное окно
1056     * - moveBack() обратно во враппер контента
1057     */
1058    app.contentArray = {
1059
1060        moveToPopup: function (modal) {
1061
1062            function arrayContentPush($el) {
1063                modal.contentArray.push({
1064                    '$currContent': $el,
1065                    '$contentPlaceholder': app.insertPlaceholder($el),
1066                });
1067                $el.appendTo( modal.$innerWrap );
1068            }
1069
1070            if ( modal.loadType==='jquery' ) {
1071                modal.jquery.each(function (i, el) {
1072                   arrayContentPush($(el))
1073                });
1074            }
1075
1076            // Cелекторы с запятыми разбиваем на отдельные jQuery-объекты, чтобы сохранить порядок выборки блоков
1077            // с контентом именно таким, как указано в селекторе, в противном случае порядок будет таким как в DOM-дереве.
1078            if ( modal.loadType==='selector' ) {
1079                var arSelectors = modal.selector.split(',');
1080
1081                $.each(arSelectors, function (index,selector) {
1082                    selector = selector.trim();
1083                    if (selector) {
1084                        $(selector).each(function (i, el) {
1085                            arrayContentPush($(el))
1086                        });
1087                    }
1088                });
1089            }
1090        },
1091
1092        moveBack: function (modal) {
1093
1094            $.each( modal.contentArray, function (key, item) {
1095                item.$currContent.insertAfter(item.$contentPlaceholder);
1096                item.$contentPlaceholder.remove();
1097            });
1098
1099        }
1100
1101    };
1102
1103    /**
1104     * Удаляет модальное окно из HTML-кода и коллекции app.modals
1105     *
1106     * @param {object} modal - удаляемое модальное окно (объект из коллекции app.modals)
1107     * @return {boolean}
1108     */
1109    app.remove = function (modal) {
1110        if (
1111            typeof modal !== 'object' ||
1112            typeof app.modals[modal.wrapId] !== 'object'
1113        ) {
1114            return false;
1115        }
1116
1117        if (modal.loadType==='selector' || modal.loadType==='jquery') {
1118            app.contentArray.moveBack(modal);
1119        }
1120        if (modal.loadType==='id') {
1121            modal.$currContent.appendTo( modal.$currWrapper );
1122        }
1123        if ( modal.ps ) {
1124            modal.ps.destroy();
1125            modal.ps = null; // to make sure garbages are collected
1126        }
1127
1128        modal.$wrap.remove();
1129        delete app.modals[modal.wrapId];
1130        app.modalsTopId = app.lastKeyInObj(app.modals);
1131
1132        return true;
1133    };
1134
1135    /**
1136     * Функционал для анимации
1137     *
1138     * @constructor
1139     */
1140    app.Animation = function(){
1141        var anim = this;
1142
1143        anim.timeIn = 400;
1144        anim.timeOut = 300;
1145
1146        anim.commonClass = function (modal) {
1147            return modal.animationPrefix + '-animate'
1148        };
1149
1150        anim.wrapClass = function(params, dir){
1151            return params.animationPrefix + '-animate-wrap-' + dir;
1152        };
1153
1154        anim.popupClass = function(params, dir){
1155            return params.animationPrefix + '-animate-' + params.mode + '-' + dir;
1156        };
1157
1158        anim.wrapIn = function(modal) {
1159            if (modal.calcZindex) {
1160                modal.$wrap.css('z-index', app.getWrapZindex());
1161            }
1162            modal.$wrap
1163                .css({
1164                    'display': 'block',
1165                    'animation-duration': anim.timeIn+'ms'
1166                })
1167                .addClass( modal.classes + ' ' + app.classLoading + ' ' + anim.commonClass(modal) + ' ' + modal.wrapInClass )
1168        };
1169
1170        anim.popupIn = function(modal) {
1171            modal.$popup
1172                .css('animation-timing-function', 'ease-out')
1173                .css('animation-duration', anim.timeIn+'ms')
1174                .addClass( anim.commonClass(modal) + ' ' + modal.popupInClass );
1175            modal.$wrap.removeClass( app.classLoading );
1176        };
1177
1178        anim.allOut = function(modal) {
1179            modal.$wrap
1180                .css('opacity', 1)
1181                .removeClass(modal.wrapInClass)
1182                .css('animation-duration', anim.timeOut+'ms')
1183                .addClass(modal.wrapOutClass);
1184            modal.$popup
1185                //.css('opacity', 1)
1186                .removeClass(modal.popupInClass)
1187                .css('animation-timing-function', 'ease-in')
1188                .css('animation-duration', anim.timeOut+'ms')
1189                .addClass( modal.popupOutClass );
1190        };
1191
1192        anim.onInEnd = function(modal){
1193            app.callbacks.onAfterShow(modal);
1194        };
1195
1196        anim.onOutEnd = function(modal){
1197            app.callbacks.onAfterClose(modal);
1198        };
1199    };
1200    app.animation = new app.Animation();
1201
1202    /**
1203     * Показ модального окна
1204     * @param {object} params
1205     * - selector {string} - jQuery-селектор на враппер с html-контентом
1206     * - jquery {jquery} - jQuery-ссылка на враппер с html-контентом
1207     * - html {string} - html-строка с контентом
1208     * - url {string} - урл загружаемого контента
1209     * - id {string}- Значение атрибута data-iexmodal-id враппера с контентом, который нужно показать
1210     * - mode {string} - Режим показа модального окна из app.modes (по-умолчанию 'default')
1211     * - classes {string}- Список CSS-классов (через пробел), которые нужно добавить к модальному окну
1212     * - width {string}- Ширина модального окна с единицами измерения (px, %, em и т.д.). По-умолчанию ширина равна 450px. Игнорируется при modal.mode !== 'default'.
1213     * - overlay {boolean} - true - новое окно нужно открыть поверх текущего, не закрывая его, false - сначала закрыть текущее окно (по-умолчанию)
1214     * - contentScroll {string} - Прокручивать контент, если он не умещается в окне: true | false (по-умолчанию true)
1215     * - documentScroll {string}
1215 - Прокручивать документ: true | false (по-умолчанию false)
1216     * - timeout {integer} - Время в секундах, после которого окно само должно закрыться (по-умолчанию 0 сек)
1217     * - ajaxReport {boolean} - Включить отчет об ajax-запросе
1218     * - onBeforeShow {function}
1219     * - onAfterContentLoad {function}
1220     * - onAfterShow {function}
1221     * - onBeforeClose {function}
1222     * - onAfterClose {function}
1223     * Из списка параметров app.loadTypes одновременно можно передать только ОДИН!
1224     *
1225     * @return {boolean}
1226     */
1227    app.show = function (params) {
1228        params = app.validateParams(params);
1229        if ( !params ) {
1230            return false;
1231        }
1232
1233        if ( !params.overlay && app.modalsTopId ) {
1234            app.callbacks._onAfterClose = function () {
1235                app.callbacks._onAfterClose = function () {};
1236                app.show(params);
1237            };
1238            app.close();
1239            return true;
1240        }
1241
1242        var modal = app.create(params);
1243
1244        app.callbacks.onBeforeShow(modal);
1245        app.animation.wrapIn(modal);
1246
1247        if (modal.loadType==='selector' || modal.loadType==='jquery') {
1248            app.contentArray.moveToPopup(modal);
1249            app.callbacks.onAfterContentLoad(modal);
1250        }
1251
1252        if (modal.loadType==='html') {
1253            modal.$innerWrap.html(modal.html);
1254            app.callbacks.onAfterContentLoad(modal);
1255        }
1256
1257        if (modal.loadType==='url') {
1258            var modalId = modal.wrapId;
1259            $.ajax({
1260                url: modal.url,
1261                timeout: 25000, // ms, по-умолчанию таймаут отсутствует, зависит от браузера и может быть очень большим
1262                dataType: 'html',
1263                success: function (data, textStatus, jqXHR) {
1264                    if ( !app.isOpened(modalId) ) return; // если окно закрыли раньше срабатывания данного колбэка
1265
1266                    var report = '';
1267                    if (modal.ajaxReport) {
1268                        report =
1269                            '<b>' + app.repAjaxMsg + '</b>' +
1270                            '<br><br>' +
1271                            app.ajaxReport(modal.url, jqXHR) +
1272                            '<br><br>';
1273                    }
1274
1275                    modal.$innerWrap.html(report + data);
1276                    app.callbacks.onAfterContentLoad(modal);
1277                },
1278                error: function(jqXHR) {
1279                    if ( !app.isOpened(modalId) ) return; // если окно закрыли раньше срабатывания данного колбэка
1280
1281                    var report =
1282                        '<b>' + app.errAjaxMsg + '</b>' +
1283                        '<br><br>' +
1284                        app.ajaxReport(modal.url, jqXHR);
1285
1286                    modal.$innerWrap.html(report);
1287                    app.callbacks.onAfterContentLoad(modal);
1288                }
1289            });
1290        }
1291
1292        if (modal.loadType==='id') {
1293            modal.$currWrapper = app.getContentWrap(modal.id);
1294
1295            var wrapClasses = $.trim( modal.$currWrapper.attr('data-iexmodal-classes') );
1296            if ( wrapClasses ) {
1297                modal.$wrap.addClass( wrapClasses );
1298            }
1299
1300            // Переносится в модальное окно только один элемент - дополнительная обертка контента, это особенность работы $.appendTo()
1301            modal.$currContent = modal.$currWrapper.children('.'+app.classContentWrap);
1302            modal.$currContent.appendTo( modal.$innerWrap );
1303            app.callbacks.onAfterContentLoad(modal);
1304        }
1305
1306        return true;
1307    };
1308
1309    /**
1310     * Аналог String.repeat(), поддерживаемый в IE11
1311     *
1312     * @param str Строка
1313     * @param cnt Количество повторений
1314     * @return {string}
1315     */
1316    app.strRepeat = function(str, cnt){
1317        if ( !str || !cnt ) return '';
1318        return Array(cnt + 1).join('<i></i>')
1319    };
1320
1321    /**
1322     * Генератор нескольких одновременно открытых окон
1323     * Удобен для тестирования адативности и отладки
1324     * @param count Количество генерируемых окон
1325     */
1326    app.devShowMany = function (count) {
1327        var width, textTotal, textChunk;
1328        for (var $i=1; $i<=count; $i++) {
1329            width = (count +1 -$i)*100+200 + 'px';
1330            textChunk = 'Адаптивность у данного окна включается при ширине окна браузера < ' + width + '. ';
1331            textTotal = '<p>' + ( app.strRepeat(textChunk, (count-$i+1)*3) ) + '</p>';
1332            iexModal.show({
1333                'html': '<h2>Окно '+ width + '</h2><a href="#" class="iexmodal-close">Закрыть текущее окно</a>' + textTotal + '<a href="#" class="iexmodal-close-all">Закрыть все окна</a>',
1334                'width': width,
1335                'overlay': 'true'
1336            });
1337        }
1338    };
1339
1340    /**
1341     * Закрыть модальное окно
1342     * Закрывается окно переданное первым параметром, либо (без параметра) самое верхнее.
1343     * @param modal {object|string} Объект модального окна или строка c id
1344     * @return {boolean}
1345     */
1346    app.close = function (modal) {
1347        if (typeof modal !== 'object') {
1348            modal = app.getModal(modal);
1349        }
1350        if (typeof modal !== 'object') {
1351            return false;
1352        }
1353
1354        clearTimeout(modal.timeoutId);
1355        app.callbacks.onBeforeClose(modal);
1356        app.animation.allOut(modal);
1357
1358        return true;
1359    };
1360
1361    /**
1362     * Закрыть все модальные окна
1363     * @return {boolean}
1364     */
1365    app.closeAll = function () {
1366        $.each( app.modals, function (id, modal) {
1367            app.close(modal);
1368        });
1369        return true;
1370    };
1371
1372    /**
1373     * Функционал для блокировки прокрутки документа при открытом модальном окне
1374     *
1375     * @constructor
1376     */
1377    app.DocumentScroll = function(){
1378        var docScroll = this;
1379
1380        this.disabled = false;
1381        this.scrollCurr = [0,0]; // [left,top]
1382        this.isIE = app.getIEVersion() > 0;
1383
1384        var userAgent = window.navigator.userAgent;
1385        var isMobile = /iP(ad|hone)/i.test(userAgent);
1386        var isWebkit = /WebKit/i.test(userAgent);
1387        var isChrome = /CriOS/i.test(userAgent);
1388        var isOpera = /OPiOS/i.test(userAgent);
1389        this.isMobileSafari = isMobile && isWebkit && !isChrome && !isOpera;
1390
1391        this.scrollGet = function () {
1392            return [
1393                app.$window.scrollLeft(),
1394                app.$window.scrollTop()
1395            ]
1396        };
1397
1398        this.scrollSave = function () {
1399            docScroll.scrollCurr = docScroll.scrollGet();
1400        };
1401
1402        // Вместо preventDefault() для scroll
1403        // Для scroll preventDefault() не работает потому, что событие происходит ПОСЛЕ прокрутки
1404        this.scrollRestore = function () {
1405            app.$html.scrollLeft( docScroll.scrollCurr[0] );
1406            app.$html.scrollTop( docScroll.scrollCurr[1] );
1407        };
1408
1409        /**
1410         * Массив строк, при наличии которых в контенте модального окна считаем,
1411         * что в окне есть поля формы, а значит не нужно блокировать необходимые для этих полей клавиши
1412         *
1413         * @type {string[]}
1414         */
1415        this.formTags = [
1416            'input', 'textarea'
1417        ];
1418
1419        /**
1420         * Клавиши (их коды), которые будут блокироваться при открытом модальном окне во избежание прокрутки страницы
1421         * ---
1422         * 32 spacebar
1423         * 33 pageup
1424         * 34 pagedown
1425         * 35 end
1426         * 36 home
1427         * 37 left
1428         * 38 up
1429         * 39 right
1430         * 40 down
1431         * ---
1432         *
1433         * @type {{default: number[], form: number[]}}
1434         */
1435        this.keyCodeSets = {
1436            'default': [32, 33, 34, 35, 36, 37, 38, 39, 40],
1437            'form': [33, 34] // для корректной работы полей форм в модальном окне
1438        };
1439
1440        this.keyCodes = [];
1441
1442        this.setKeyCodes = function () {
1443            var
1444                html = app.getModal().$innerWrap.html(),
1445                regex = new RegExp( docScroll.formTags.join('|') );
1446
1447            docScroll.keyCodes = docScroll.keyCodeSets['default'];
1448            if ( regex.exec(html) ) {
1449                docScroll.keyCodes = docScroll.keyCodeSets['form'];
1450            }
1451        };
1452
1453        this.handleTouch = function(event) {
1454            event.preventDefault();
1455        };
1456
1457        this.handleWheel = function(event) {
1458            event.preventDefault();
1459        };
1460
1461        this.handleKeydown = function(event) {
1462            if ( docScroll.keyCodes.indexOf(event.keyCode) > -1 ) {
1463                event.preventDefault();
1464            }
1465        };
1466
1467        this.handleScrollbar = function(event) {
1468            if (!docScroll.isMobileSafari) { // в сафари прокрутку не компенсируем во избежание рывков при переключении между полями формы
1469                docScroll.scrollRestore(); // Для scroll preventDefault() не работает потому, что событие происходит ПОСЛЕ прокрутки
1470            }
1471        };
1472
1473        this.off = function () {
1474            if ( docScroll.disabled || docScroll.isIE ){ // в IE не работают нормально addEventListener и removeEventListener
1475                return;
1476            }
1477
1478            docScroll.scrollSave();
1479            docScroll.setKeyCodes();
1480
1481            app.$html.css('touch-action', 'none');
1482            app.$html[0].addEventListener('mousewheel DOMMouseScroll', docScroll.handleWheel, { passive: false });
1483            app.$html[0].addEventListener('keydown', docScroll.handleKeydown, { passive: false });
1484            app.$html[0].addEventListener('touchmove', docScroll.handleTouch, {passive: false});
1485            window.addEventListener('scroll', docScroll.handleScrollbar, { passive: false });
1486
1487            docScroll.disabled = true;
1488        };
1489
1490        this.on = function () {
1491            if ( !docScroll.disabled || docScroll.isIE ){
1492                return;
1493            }
1494
1495            app.$html.css('touch-action', 'auto');
1496            app.$html[0].removeEventListener('mousewheel DOMMouseScroll', docScroll.handleWheel);
1497            app.$html[0].removeEventListener('keydown', docScroll.handleKeydown);
1498            app.$html[0].removeEventListener('touchmove', docScroll.handleTouch);
1499            window.removeEventListener('scroll', docScroll.handleScrollbar);
1500
1501            docScroll.disabled = false;
1502        }
1503    };
1504    app.documentScroll = new app.DocumentScroll;
1505
1506    /**
1507     * Системные коллбэки, запускающие одноименные пользовательские.
1508     * Выполняются в приведенном порядке, сверху-вниз.
1509     */
1510    app.callbacks = {
1511
1512        onBeforeShow: function(modal){
1513            modal.onBeforeShow(modal);
1514
1515            if (modal.outerScroll) {
1516                app.$body
1517                    .addClass(app.classOpened)
1518                    .css('padding-right', app.scrollbarWidth+'px')
1519            }
1520        },
1521
1522        onAfterContentLoad: function (modal) {
1523            modal.onAfterContentLoad(modal);
1524
1525            if (app.modalsTopId) {
1526                if (
1527                    !modal.outerScroll &&
1528                    modal.mode !== 'alert'
1529                ) {
1530                    app.documentScroll.off();
1531                }
1532                app.$window.on('resize orientationchange', app.windowRelatedActions);
1533                if (window.visualViewport) {
1534                    window.visualViewport.addEventListener('resize', app.windowRelatedActions);
1535                }
1536            }
1537
1538            app.windowRelatedActions();
1539            app.animation.popupIn(modal);
1540        },
1541
1542        onAfterShow: function(modal){
1543            if ( modal.timeout ) {
1544                modal.timeoutId = setTimeout(function () {
1545                    app.close(modal);
1546                }, modal.timeout*1000)
1547            }
1548            modal.onAfterShow(modal);
1549        },
1550
1551        onBeforeClose: function(modal){
1552            modal.onBeforeClose(modal);
1553        },
1554
1555        onAfterClose: function(modal){
1556            if (app.modalsTopId) {
1557                if (
1558                    !modal.outerScroll &&
1559                    modal.mode !== 'alert'
1560                ) {
1561                    app.documentScroll.on();
1562                }
1563                app.$window.off('resize orientationchange', app.windowRelatedActions);
1564                if (window.visualViewport) {
1565                    window.visualViewport.removeEventListener('resize', app.windowRelatedActions);
1566                }
1567                if (modal.outerScroll) {
1568                    app.$body
1569                        .removeClass(app.classOpened)
1570                        .css('padding-right', '')
1571                }
1572            }
1573
1574            modal.onAfterClose(modal);
1575            app.remove(modal);
1576            app.callbacks._onAfterClose(modal);
1577        },
1578
1579        // Используется в app.show() для предварительного закрытия текущего окна
1580        // и показа нового только после завершения анимации у текущего
1581        _onAfterClose: function () {}
1582    };
1583
1584    /**
1585     * Обработчик нажатия Escape.
1586     * Вещаем его в $(document).ready() во избежание клонирования при повторном выполнении iexModal.init()
1587     */
1588    app.handleEscape = function (e) {
1589        if (app.modalsTopId && e.keyCode===27) {
1590            app.close();
1591        }
1592    };
1593
1594    /**
1595     * Действия, связанные с событиями resize и orientationchange
1596     */
1597    app.windowRelatedActions = function(){
1598        app.getWindowSize();
1599        app.correctPosition();
1600    };
1601
1602    /**
1603     * Публичный этап инициализация модуля, который можно выполнять повторно.
1604     * Автоматически выполняется при каждой загрузке страницы.
1605     */
1606    app.init = function () {
1607
1608        app.$body.find('.'+app.classContent+':not([data-iexmodal-content-wrap="false"])')
1609        // предварительно удаляем дополнительные обертки контента
1610        // для возможности повторного выполнения app.init()
1611            .each(function () {
1612                var $firstChild = $(this).children('.'+app.classContentWrap).children().first();
1613                if ( $firstChild.parent().is('.'+app.classContentWrap) ) {
1614                    $firstChild.unwrap();
1615                }
1616            })
1617            // создаем дополнительные вложенные обертки контента, которые и будут переноситься в модальное окно методом $.appendTo()
1618            .wrapInner('<div class="'+app.classContentWrap+'"></div>');
1619
1620        app.colorboxInit();
1621        app.getWindowSize();
1622    };
1623
1624    /**
1625     * Приватный этап инициализация модуля, который нельзя/нежелательно выполнять повторно.
1626     * Автоматически выполняется при каждой загрузке страницы.
1627     * Некоторые обработчики вешаем здесь чтобы:
1628     * 1) работало делегирование
1629     * 2) избежать клонирования обработчиков при повторной публичной инициализации iexModal.init()
1630     *
1631     * @private
1632     */
1633    app.__init = function () {
1634        app.$sprite = app.insertSprite();
1635
1636        app.$window.on('keyup', app.handleEscape);
1637
1638        app.$body
1639            .on('click', '.'+app.classBtn, function (e) {
1640                app.show( app.parseBtnAttrs( $(this) ) );
1641                e.preventDefault();
1642            })
1643            .on('click touchstart', '.'+app.classClose, function () { // touchstart - для Safari, т.к. в нем вместо click чаще touchmove происходит (в 3-х случаях из 4-
1643х)
1644                app.close();
1645            })
1646            .on('click', '.'+app.classCloseAll, app.closeAll);
1647
1648        app.scrollbarWidth = app.getScrollbarWidth();
1649
1650        $.each(app.attrsAndParams, function (key,val){
1651            app.params.push(val);
1652        });
1653        $.each(app.userCallbackNames, function (key,val){
1654            app.params.push(val);
1655        });
1656    };
1657
1658    app.init();
1659    app.__init();
1660};
1661
1662$(document).ready(function () {
1663    if (!!window.IexModal && !!window.jQuery) {
1664        if (!!window.iexModal) {
1665            console.log(iexModal.errScriptLoadingMsg);
1666        } else {
1667            window.iexModal = new IexModal(jQuery);
1668        }
1669    }
1670});
1671
1672/**
1673 * END: iexModal
1674 */
1675
1676},{"./perfect-srollbar.js":2}],2:[function(require,module,exports){
1677/*!
1678 * perfect-scrollbar v1.3.0
1679 * (c) 2017 Hyunje Jun
1680 * @license MIT
1681 */
1682(function (global, factory) {
1683    typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
1684        typeof define === 'function' && define.amd ? define(factory) :
1685            (global.PerfectScrollbar = factory());
1686}(this, (function () { 'use strict';
1687
1688    function get(element) {
1689        return getComputedStyle(element);
1690    }
1691
1692    function set(element, obj) {
1693        for (var key in obj) {
1694            var val = obj[key];
1695            if (typeof val === 'number') {
1696                val = val + "px";
1697            }
1698            element.style[key] = val;
1699        }
1700        return element;
1701    }
1702
1703    function div(className) {
1704        var div = document.createElement('div');
1705        div.className = className;
1706        return div;
1707    }
1708
1709    var elMatches =
1710        typeof Element !== 'undefined' &&
1711        (Element.prototype.matches ||
1712            Element.prototype.webkitMatchesSelector ||
1713            Element.prototype.msMatchesSelector);
1714
1715    function matches(element, query) {
1716        if (!elMatches) {
1717            throw new Error('No element matching method supported');
1718        }
1719
1720        return elMatches.call(element, query);
1721    }
1722
1723    function remove(element) {
1724        if (element.remove) {
1725            element.remove();
1726        } else {
1727            if (element.parentNode) {
1728                element.parentNode.removeChild(element);
1729            }
1730        }
1731    }
1732
1733    function queryChildren(element, selector) {
1734        return Array.prototype.filter.call(element.children, function (child) { return matches(child, selector); }
1735        );
1736    }
1737
1738    var cls = {
1739        main: 'ps',
1740        element: {
1741            thumb: function (x) { return ("ps__thumb-" + x); },
1742            rail: function (x) { return ("ps__rail-" + x); },
1743            consuming: 'ps__child--consume',
1744        },
1745        state: {
1746            focus: 'ps--focus',
1747            active: function (x) { return ("ps--active-" + x); },
1748            scrolling: function (x) { return ("ps--scrolling-" + x); },
1749        },
1750    };
1751
1752    /*
1753     * Helper methods
1754     */
1755    var scrollingClassTimeout = { x: null, y: null };
1756
1757    function addScrollingClass(i, x) {
1758        var classList = i.element.classList;
1759        var className = cls.state.scrolling(x);
1760
1761        if (classList.contains(className)) {
1762            clearTimeout(scrollingClassTimeout[x]);
1763        } else {
1764            classList.add(className);
1765        }
1766    }
1767
1768    function removeScrollingClass(i, x) {
1769        scrollingClassTimeout[x] = setTimeout(
1770            function () { return i.isAlive && i.element.classList.remove(cls.state.scrolling(x)); },
1771            i.settings.scrollingThreshold
1772        );
1773    }
1774
1775    function setScrollingClassInstantly(i, x) {
1776        addScrollingClass(i, x);
1777        removeScrollingClass(i, x);
1778    }
1779
1780    var EventElement = function EventElement(element) {
1781        this.element = element;
1782        this.handlers = {};
1783    };
1784
1785    var prototypeAccessors = { isEmpty: { configurable: true } };
1786
1787    EventElement.prototype.bind = function bind (eventName, handler) {
1788        if (typeof this.handlers[eventName] === 'undefined') {
1789            this.handlers[eventName] = [];
1790        }
1791        this.handlers[eventName].push(handler);
1792        this.element.addEventListener(eventName, handler, false);
1793    };
1794
1795    EventElement.prototype.unbind = function unbind (eventName, target) {
1796        var this$1 = this;
1797
1798        this.handlers[eventName] = this.handlers[eventName].filter(function (handler) {
1799            if (target && handler !== target) {
1800                return true;
1801            }
1802            this$1.element.removeEventListener(eventName, handler, false);
1803            return false;
1804        });
1805    };
1806
1807    EventElement.prototype.unbindAll = function unbindAll () {
1808        var this$1 = this;
1809
1810        for (var name in this$1.handlers) {
1811            this$1.unbind(name);
1812        }
1813    };
1814
1815    prototypeAccessors.isEmpty.get = function () {
1816        var this$1 = this;
1817
1818        return Object.keys(this.handlers).every(
1819            function (key) { return this$1.handlers[key].length === 0; }
1820        );
1821    };
1822
1823    Object.defineProperties( EventElement.prototype, prototypeAccessors );
1824
1825    var EventManager = function EventManager() {
1826        this.eventElements = [];
1827    };
1828
1829    EventManager.prototype.eventElement = function eventElement (element) {
1830        var ee = this.eventElements.filter(function (ee) { return ee.element === element; })[0];
1831        if (!ee) {
1832            ee = new EventElement(element);
1833            this.eventElements.push(ee);
1834        }
1835        return ee;
1836    };
1837
1838    EventManager.prototype.bind = function bind (element, eventName, handler) {
1839        this.eventElement(element).bind(eventName, handler);
1840    };
1841
1842    EventManager.prototype.unbind = function unbind (element, eventName, handler) {
1843        var ee = this.eventElement(element);
1844        ee.unbind(eventName, handler);
1845
1846        if (ee.isEmpty) {
1847            // remove
1848            this.eventElements.splice(this.eventElements.indexOf(ee), 1);
1849        }
1850    };
1851
1852    EventManager.prototype.unbindAll = function unbindAll () {
1853        this.eventElements.forEach(function (e) { return e.unbindAll(); });
1854        this.eventElements = [];
1855    };
1856
1857    EventManager.prototype.once = function once (element, eventName, handler) {
1858        var ee = this.eventElement(element);
1859        var onceHandler = function (evt) {
1860            ee.unbind(eventName, onceHandler);
1861            handler(evt);
1862        };
1863        ee.bind(eventName, onceHandler);
1864    };
1865
1866    function createEvent(name) {
1867        if (typeof window.CustomEvent === 'function') {
1868            return new CustomEvent(name);
1869        } else {
1870            var evt = document.createEvent('CustomEvent');
1871            evt.initCustomEvent(name, false, false, undefined);
1872            return evt;
1873        }
1874    }
1875
1876    var processScrollDiff = function(
1877        i,
1878        axis,
1879        diff,
1880        useScrollingClass,
1881        forceFireReachEvent
1882    ) {
1883        if ( useScrollingClass === void 0 ) useScrollingClass = true;
1884        if ( forceFireReachEvent === void 0 ) forceFireReachEvent = false;
1885
1886        var fields;
1887        if (axis === 'top') {
1888            fields = [
1889                'contentHeight',
1890                'containerHeight',
1891                'scrollTop',
1892                'y',
1893                'up',
1894                'down' ];
1895        } else if (axis === 'left') {
1896            fields = [
1897                'contentWidth',
1898                'containerWidth',
1899                'scrollLeft',
1900                'x',
1901                'left',
1902                'right' ];
1903        } else {
1904            throw new Error('A proper axis should be provided');
1905        }
1906
1907        processScrollDiff$1(i, diff, fields, useScrollingClass, forceFireReachEvent);
1908    };
1909
1910    function processScrollDiff$1(
1911        i,
1912        diff,
1913        ref,
1914        useScrollingClass,
1915        forceFireReachEvent
1916    ) {
1917        var contentHeight = ref[0];
1918        var containerHeight = ref[1];
1919        var scrollTop = ref[2];
1920        var y = ref[3];
1921        var up = ref[4];
1922        var down = ref[5];
1923        if ( useScrollingClass === void 0 ) useScrollingClass = true;
1924        if ( forceFireReachEvent === void 0 ) forceFireReachEvent = false;
1925
1926        var element = i.element;
1927
1928        // reset reach
1929        i.reach[y] = null;
1930
1931        // 1 for subpixel rounding
1932        if (element[scrollTop] < 1) {
1933            i.reach[y] = 'start';
1934        }
1935
1936        // 1 for subpixel rounding
1937        if (element[scrollTop] > i[contentHeight] - i[containerHeight] - 1) {
1938            i.reach[y] = 'end';
1939        }
1940
1941        if (diff) {
1942            element.dispatchEvent(createEvent(("ps-scroll-" + y)));
1943
1944            if (diff < 0) {
1945                element.dispatchEvent(createEvent(("ps-scroll-" + up)));
1946            } else if (diff > 0) {
1947                element.dispatchEvent(createEvent(("ps-scroll-" + down))
vendor: 29,959 bytes, lines 1947-2830
1947);
1948            }
1949
1950            if (useScrollingClass) {
1951                setScrollingClassInstantly(i, y);
1952            }
1953        }
1954
1955        if (i.reach[y] && (diff || forceFireReachEvent)) {
1956            element.dispatchEvent(createEvent(("ps-" + y + "-reach-" + (i.reach[y]))));
1957        }
1958    }
1959
1960    function toInt(x) {
1961        return parseInt(x, 10) || 0;
1962    }
1963
1964    function isEditable(el) {
1965        return (
1966            matches(el, 'input,[contenteditable]') ||
1967            matches(el, 'select,[contenteditable]') ||
1968            matches(el, 'textarea,[contenteditable]') ||
1969            matches(el, 'button,[contenteditable]')
1970        );
1971    }
1972
1973    function outerWidth(element) {
1974        var styles = get(element);
1975        return (
1976            toInt(styles.width) +
1977            toInt(styles.paddingLeft) +
1978            toInt(styles.paddingRight) +
1979            toInt(styles.borderLeftWidth) +
1980            toInt(styles.borderRightWidth)
1981        );
1982    }
1983
1984    var env = {
1985        isWebKit:
1986            typeof document !== 'undefined' &&
1987            'WebkitAppearance' in document.documentElement.style,
1988        supportsTouch:
1989            typeof window !== 'undefined' &&
1990            ('ontouchstart' in window ||
1991                (window.DocumentTouch && document instanceof window.DocumentTouch)),
1992        supportsIePointer:
1993            typeof navigator !== 'undefined' && navigator.msMaxTouchPoints,
1994        isChrome:
1995            typeof navigator !== 'undefined' &&
1996            /Chrome/i.test(navigator && navigator.userAgent),
1997    };
1998
1999    var updateGeometry = function(i) {
2000        var element = i.element;
2001
2002        i.containerWidth = element.clientWidth;
2003        i.containerHeight = element.clientHeight;
2004        i.contentWidth = element.scrollWidth;
2005        i.contentHeight = element.scrollHeight;
2006
2007        if (!element.contains(i.scrollbarXRail)) {
2008            // clean up and append
2009            queryChildren(element, cls.element.rail('x')).forEach(function (el) { return remove(el); }
2010            );
2011            element.appendChild(i.scrollbarXRail);
2012        }
2013        if (!element.contains(i.scrollbarYRail)) {
2014            // clean up and append
2015            queryChildren(element, cls.element.rail('y')).forEach(function (el) { return remove(el); }
2016            );
2017            element.appendChild(i.scrollbarYRail);
2018        }
2019
2020        if (
2021            !i.settings.suppressScrollX &&
2022            i.containerWidth + i.settings.scrollXMarginOffset < i.contentWidth
2023        ) {
2024            i.scrollbarXActive = true;
2025            i.railXWidth = i.containerWidth - i.railXMarginWidth;
2026            i.railXRatio = i.containerWidth / i.railXWidth;
2027            i.scrollbarXWidth = getThumbSize(
2028                i,
2029                toInt(i.railXWidth * i.containerWidth / i.contentWidth)
2030            );
2031            i.scrollbarXLeft = toInt(
2032                (i.negativeScrollAdjustment + element.scrollLeft) *
2033                (i.railXWidth - i.scrollbarXWidth) /
2034                (i.contentWidth - i.containerWidth)
2035            );
2036        } else {
2037            i.scrollbarXActive = false;
2038        }
2039
2040        if (
2041            !i.settings.suppressScrollY &&
2042            i.containerHeight + i.settings.scrollYMarginOffset < i.contentHeight
2043        ) {
2044            i.scrollbarYActive = true;
2045            i.railYHeight = i.containerHeight - i.railYMarginHeight;
2046            i.railYRatio = i.containerHeight / i.railYHeight;
2047            i.scrollbarYHeight = getThumbSize(
2048                i,
2049                toInt(i.railYHeight * i.containerHeight / i.contentHeight)
2050            );
2051            i.scrollbarYTop = toInt(
2052                element.scrollTop *
2053                (i.railYHeight - i.scrollbarYHeight) /
2054                (i.contentHeight - i.containerHeight)
2055            );
2056        } else {
2057            i.scrollbarYActive = false;
2058        }
2059
2060        if (i.scrollbarXLeft >= i.railXWidth - i.scrollbarXWidth) {
2061            i.scrollbarXLeft = i.railXWidth - i.scrollbarXWidth;
2062        }
2063        if (i.scrollbarYTop >= i.railYHeight - i.scrollbarYHeight) {
2064            i.scrollbarYTop = i.railYHeight - i.scrollbarYHeight;
2065        }
2066
2067        updateCss(element, i);
2068
2069        if (i.scrollbarXActive) {
2070            element.classList.add(cls.state.active('x'));
2071        } else {
2072            element.classList.remove(cls.state.active('x'));
2073            i.scrollbarXWidth = 0;
2074            i.scrollbarXLeft = 0;
2075            element.scrollLeft = 0;
2076        }
2077        if (i.scrollbarYActive) {
2078            element.classList.add(cls.state.active('y'));
2079        } else {
2080            element.classList.remove(cls.state.active('y'));
2081            i.scrollbarYHeight = 0;
2082            i.scrollbarYTop = 0;
2083            element.scrollTop = 0;
2084        }
2085    };
2086
2087    function getThumbSize(i, thumbSize) {
2088        if (i.settings.minScrollbarLength) {
2089            thumbSize = Math.max(thumbSize, i.settings.minScrollbarLength);
2090        }
2091        if (i.settings.maxScrollbarLength) {
2092            thumbSize = Math.min(thumbSize, i.settings.maxScrollbarLength);
2093        }
2094        return thumbSize;
2095    }
2096
2097    function updateCss(element, i) {
2098        var xRailOffset = { width: i.railXWidth };
2099        if (i.isRtl) {
2100            xRailOffset.left =
2101                i.negativeScrollAdjustment +
2102                element.scrollLeft +
2103                i.containerWidth -
2104                i.contentWidth;
2105        } else {
2106            xRailOffset.left = element.scrollLeft;
2107        }
2108        if (i.isScrollbarXUsingBottom) {
2109            xRailOffset.bottom = i.scrollbarXBottom - element.scrollTop;
2110        } else {
2111            xRailOffset.top = i.scrollbarXTop + element.scrollTop;
2112        }
2113        set(i.scrollbarXRail, xRailOffset);
2114
2115        var yRailOffset = { top: element.scrollTop, height: i.railYHeight };
2116        if (i.isScrollbarYUsingRight) {
2117            if (i.isRtl) {
2118                yRailOffset.right =
2119                    i.contentWidth -
2120                    (i.negativeScrollAdjustment + element.scrollLeft) -
2121                    i.scrollbarYRight -
2122                    i.scrollbarYOuterWidth;
2123            } else {
2124                yRailOffset.right = i.scrollbarYRight - element.scrollLeft;
2125            }
2126        } else {
2127            if (i.isRtl) {
2128                yRailOffset.left =
2129                    i.negativeScrollAdjustment +
2130                    element.scrollLeft +
2131                    i.containerWidth * 2 -
2132                    i.contentWidth -
2133                    i.scrollbarYLeft -
2134                    i.scrollbarYOuterWidth;
2135            } else {
2136                yRailOffset.left = i.scrollbarYLeft + element.scrollLeft;
2137            }
2138        }
2139        set(i.scrollbarYRail, yRailOffset);
2140
2141        set(i.scrollbarX, {
2142            left: i.scrollbarXLeft,
2143            width: i.scrollbarXWidth - i.railBorderXWidth,
2144        });
2145        set(i.scrollbarY, {
2146            top: i.scrollbarYTop,
2147            height: i.scrollbarYHeight - i.railBorderYWidth,
2148        });
2149    }
2150
2151    var clickRail = function(i) {
2152        i.event.bind(i.scrollbarY, 'mousedown', function (e) { return e.stopPropagation(); });
2153        i.event.bind(i.scrollbarYRail, 'mousedown', function (e) {
2154            var positionTop =
2155                e.pageY -
2156                window.pageYOffset -
2157                i.scrollbarYRail.getBoundingClientRect().top;
2158            var direction = positionTop > i.scrollbarYTop ? 1 : -1;
2159
2160            i.element.scrollTop += direction * i.containerHeight;
2161            updateGeometry(i);
2162
2163            e.stopPropagation();
2164        });
2165
2166        i.event.bind(i.scrollbarX, 'mousedown', function (e) { return e.stopPropagation(); });
2167        i.event.bind(i.scrollbarXRail, 'mousedown', function (e) {
2168            var positionLeft =
2169                e.pageX -
2170                window.pageXOffset -
2171                i.scrollbarXRail.getBoundingClientRect().left;
2172            var direction = positionLeft > i.scrollbarXLeft ? 1 : -1;
2173
2174            i.element.scrollLeft += direction * i.containerWidth;
2175            updateGeometry(i);
2176
2177            e.stopPropagation();
2178        });
2179    };
2180
2181    var dragThumb = function(i) {
2182        bindMouseScrollHandler(i, [
2183            'containerWidth',
2184            'contentWidth',
2185            'pageX',
2186            'railXWidth',
2187            'scrollbarX',
2188            'scrollbarXWidth',
2189            'scrollLeft',
2190            'x' ]);
2191        bindMouseScrollHandler(i, [
2192            'containerHeight',
2193            'contentHeight',
2194            'pageY',
2195            'railYHeight',
2196            'scrollbarY',
2197            'scrollbarYHeight',
2198            'scrollTop',
2199            'y' ]);
2200    };
2201
2202    function bindMouseScrollHandler(
2203        i,
2204        ref
2205    ) {
2206        var containerHeight = ref[0];
2207        var contentHeight = ref[1];
2208        var pageY = ref[2];
2209        var railYHeight = ref[3];
2210        var scrollbarY = ref[4];
2211        var scrollbarYHeight = ref[5];
2212        var scrollTop = ref[6];
2213        var y = ref[7];
2214
2215        var element = i.element;
2216
2217        var startingScrollTop = null;
2218        var startingMousePageY = null;
2219        var scrollBy = null;
2220
2221        function mouseMoveHandler(e) {
2222            element[scrollTop] =
2223                startingScrollTop + scrollBy * (e[pageY] - startingMousePageY);
2224            addScrollingClass(i, y);
2225            updateGeometry(i);
2226
2227            e.stopPropagation();
2228            e.preventDefault();
2229        }
2230
2231        function mouseUpHandler() {
2232            removeScrollingClass(i, y);
2233            i.event.unbind(i.ownerDocument, 'mousemove', mouseMoveHandler);
2234        }
2235
2236        i.event.bind(i[scrollbarY], 'mousedown', function (e) {
2237            startingScrollTop = element[scrollTop];
2238            startingMousePageY = e[pageY];
2239            scrollBy =
2240                (i[contentHeight] - i[containerHeight]) /
2241                (i[railYHeight] - i[scrollbarYHeight]);
2242
2243            i.event.bind(i.ownerDocument, 'mousemove', mouseMoveHandler);
2244            i.event.once(i.ownerDocument, 'mouseup', mouseUpHandler);
2245
2246            e.stopPropagation();
2247            e.preventDefault();
2248        });
2249    }
2250
2251    var keyboard = function(i) {
2252        var element = i.element;
2253
2254        var elementHovered = function () { return matches(element, ':hover'); };
2255        var scrollbarFocused = function () { return matches(i.scrollbarX, ':focus') || matches(i.scrollbarY, ':focus'); };
2256
2257        function shouldPreventDefault(deltaX, deltaY) {
2258            var scrollTop = element.scrollTop;
2259            if (deltaX === 0) {
2260                if (!i.scrollbarYActive) {
2261                    return false;
2262                }
2263                if (
2264                    (scrollTop === 0 && deltaY > 0) ||
2265                    (scrollTop >= i.contentHeight - i.containerHeight && deltaY < 0)
2266                ) {
2267                    return !i.settings.wheelPropagation;
2268                }
2269            }
2270
2271            var scrollLeft = element.scrollLeft;
2272            if (deltaY === 0) {
2273                if (!i.scrollbarXActive) {
2274                    return false;
2275                }
2276                if (
2277                    (scrollLeft === 0 && deltaX < 0) ||
2278                    (scrollLeft >= i.contentWidth - i.containerWidth && deltaX > 0)
2279                ) {
2280                    return !i.settings.wheelPropagation;
2281                }
2282            }
2283            return true;
2284        }
2285
2286        i.event.bind(i.ownerDocument, 'keydown', function (e) {
2287            if (
2288                (e.isDefaultPrevented && e.isDefaultPrevented()) ||
2289                e.defaultPrevented
2290            ) {
2291                return;
2292            }
2293
2294            if (!elementHovered() && !scrollbarFocused()) {
2295                return;
2296            }
2297
2298            var activeElement = document.activeElement
2299                ? document.activeElement
2300                : i.ownerDocument.activeElement;
2301            if (activeElement) {
2302                if (activeElement.tagName === 'IFRAME') {
2303                    activeElement = activeElement.contentDocument.activeElement;
2304                } else {
2305                    // go deeper if element is a webcomponent
2306                    while (activeElement.shadowRoot) {
2307                        activeElement = activeElement.shadowRoot.activeElement;
2308                    }
2309                }
2310                if (isEditable(activeElement)) {
2311                    return;
2312                }
2313            }
2314
2315            var deltaX = 0;
2316            var deltaY = 0;
2317
2318            switch (e.which) {
2319                case 37: // left
2320                    if (e.metaKey) {
2321                        deltaX = -i.contentWidth;
2322                    } else if (e.altKey) {
2323                        deltaX = -i.containerWidth;
2324                    } else {
2325                        deltaX = -30;
2326                    }
2327                    break;
2328                case 38: // up
2329                    if (e.metaKey) {
2330                        deltaY = i.contentHeight;
2331                    } else if (e.altKey) {
2332                        deltaY = i.containerHeight;
2333                    } else {
2334                        deltaY = 30;
2335                    }
2336                    break;
2337                case 39: // right
2338                    if (e.metaKey) {
2339                        deltaX = i.contentWidth;
2340                    } else if (e.altKey) {
2341                        deltaX = i.containerWidth;
2342                    } else {
2343                        deltaX = 30;
2344                    }
2345                    break;
2346                case 40: // down
2347                    if (e.metaKey) {
2348                        deltaY = -i.contentHeight;
2349                    } else if (e.altKey) {
2350                        deltaY = -i.containerHeight;
2351                    } else {
2352                        deltaY = -30;
2353                    }
2354                    break;
2355                case 32: // space bar
2356                    if (e.shiftKey) {
2357                        deltaY = i.containerHeight;
2358                    } else {
2359                        deltaY = -i.containerHeight;
2360                    }
2361                    break;
2362                case 33: // page up
2363                    deltaY = i.containerHeight;
2364                    break;
2365                case 34: // page down
2366                    deltaY = -i.containerHeight;
2367                    break;
2368                case 36: // home
2369                    deltaY = i.contentHeight;
2370                    break;
2371                case 35: // end
2372                    deltaY = -i.contentHeight;
2373                    break;
2374                default:
2375                    return;
2376            }
2377
2378            if (i.settings.suppressScrollX && deltaX !== 0) {
2379                return;
2380            }
2381            if (i.settings.suppressScrollY && deltaY !== 0) {
2382                return;
2383            }
2384
2385            element.scrollTop -= deltaY;
2386            element.scrollLeft += deltaX;
2387            updateGeometry(i);
2388
2389            if (shouldPreventDefault(deltaX, deltaY)) {
2390                e.preventDefault();
2391            }
2392        });
2393    };
2394
2395    var wheel = function(i) {
2396        var element = i.element;
2397
2398        function shouldPreventDefault(deltaX, deltaY) {
2399            var isTop = element.scrollTop === 0;
2400            var isBottom =
2401                element.scrollTop + element.offsetHeight === element.scrollHeight;
2402            var isLeft = element.scrollLeft === 0;
2403            var isRight =
2404                element.scrollLeft + element.offsetWidth === element.offsetWidth;
2405
2406            var hitsBound;
2407
2408            // pick axis with primary direction
2409            if (Math.abs(deltaY) > Math.abs(deltaX)) {
2410                hitsBound = isTop || isBottom;
2411            } else {
2412                hitsBound = isLeft || isRight;
2413            }
2414
2415            return hitsBound ? !i.settings.wheelPropagation : true;
2416        }
2417
2418        function getDeltaFromEvent(e) {
2419            var deltaX = e.deltaX;
2420            var deltaY = -1 * e.deltaY;
2421
2422            if (typeof deltaX === 'undefined' || typeof deltaY === 'undefined') {
2423                // OS X Safari
2424                deltaX = -1 * e.wheelDeltaX / 6;
2425                deltaY = e.wheelDeltaY / 6;
2426            }
2427
2428            if (e.deltaMode && e.deltaMode === 1) {
2429                // Firefox in deltaMode 1: Line scrolling
2430                deltaX *= 10;
2431                deltaY *= 10;
2432            }
2433
2434            if (deltaX !== deltaX && deltaY !== deltaY /* NaN checks */) {
2435                // IE in some mouse drivers
2436                deltaX = 0;
2437                deltaY = e.wheelDelta;
2438            }
2439
2440            if (e.shiftKey) {
2441                // reverse axis with shift key
2442                return [-deltaY, -deltaX];
2443            }
2444            return [deltaX, deltaY];
2445        }
2446
2447        function shouldBeConsumedByChild(target, deltaX, deltaY) {
2448            // FIXME: this is a workaround for <select> issue in FF and IE #571
2449            if (!env.isWebKit && element.querySelector('select:focus')) {
2450                return true;
2451            }
2452
2453            if (!element.contains(target)) {
2454                return false;
2455            }
2456
2457            var cursor = target;
2458
2459            while (cursor && cursor !== element) {
2460                if (cursor.classList.contains(cls.element.consuming)) {
2461                    return true;
2462                }
2463
2464                var style = get(cursor);
2465                var overflow = [style.overflow, style.overflowX, style.overflowY].join(
2466                    ''
2467                );
2468
2469                // if scrollable
2470                if (overflow.match(/(scroll|auto)/)) {
2471                    var maxScrollTop = cursor.scrollHeight - cursor.clientHeight;
2472                    if (maxScrollTop > 0) {
2473                        if (
2474                            !(cursor.scrollTop === 0 && deltaY > 0) &&
2475                            !(cursor.scrollTop === maxScrollTop && deltaY < 0)
2476                        ) {
2477                            return true;
2478                        }
2479                    }
2480                    var maxScrollLeft = cursor.scrollLeft - cursor.clientWidth;
2481                    if (maxScrollLeft > 0) {
2482                        if (
2483                            !(cursor.scrollLeft === 0 && deltaX < 0) &&
2484                            !(cursor.scrollLeft === maxScrollLeft && deltaX > 0)
2485                        ) {
2486                            return true;
2487                        }
2488                    }
2489                }
2490
2491                cursor = cursor.parentNode;
2492            }
2493
2494            return false;
2495        }
2496
2497        function mousewheelHandler(e) {
2498            var ref = getDeltaFromEvent(e);
2499            var deltaX = ref[0];
2500            var deltaY = ref[1];
2501
2502            if (shouldBeConsumedByChild(e.target, deltaX, deltaY)) {
2503                return;
2504            }
2505
2506            var shouldPrevent = false;
2507            if (!i.settings.useBothWheelAxes) {
2508                // deltaX will only be used for horizontal scrolling and deltaY will
2509                // only be used for vertical scrolling - this is the default
2510                element.scrollTop -= deltaY * i.settings.wheelSpeed;
2511                element.scrollLeft += deltaX * i.settings.wheelSpeed;
2512            } else if (i.scrollbarYActive && !i.scrollbarXActive) {
2513                // only vertical scrollbar is active and useBothWheelAxes option is
2514                // active, so let's scroll vertical bar using both mouse wheel axes
2515                if (deltaY) {
2516                    element.scrollTop -= deltaY * i.settings.wheelSpeed;
2517                } else {
2518                    element.scrollTop += deltaX * i.settings.wheelSpeed;
2519                }
2520                shouldPrevent = true;
2521            } else if (i.scrollbarXActive && !i.scrollbarYActive) {
2522                // useBothWheelAxes and only horizontal bar is active, so use both
2523                // wheel axes for horizontal bar
2524                if (deltaX) {
2525                    element.scrollLeft += deltaX * i.settings.wheelSpeed;
2526                } else {
2527                    element.scrollLeft -= deltaY * i.settings.wheelSpeed;
2528                }
2529                shouldPrevent = true;
2530            }
2531
2532            updateGeometry(i);
2533
2534            shouldPrevent = shouldPrevent || shouldPreventDefault(deltaX, deltaY);
2535            if (shouldPrevent && !e.ctrlKey) {
2536                e.stopPropagation();
2537                e.preventDefault();
2538            }
2539        }
2540
2541        if (typeof window.onwheel !== 'undefined') {
2542            i.event.bind(element, 'wheel', mousewheelHandler);
2543        } else if (typeof window.onmousewheel !== 'undefined') {
2544            i.event.bind(element, 'mousewheel', mousewheelHandler);
2545        }
2546    };
2547
2548    var touch = function(i) {
2549        if (!env.supportsTouch && !env.supportsIePointer) {
2550            return;
2551        }
2552
2553        var element = i.element;
2554
2555        function shouldPrevent(deltaX, deltaY) {
2556            var scrollTop = element.scrollTop;
2557            var scrollLeft = element.scrollLeft;
2558            var magnitudeX = Math.abs(deltaX);
2559            var magnitudeY = Math.abs(deltaY);
2560
2561            if (magnitudeY > magnitudeX) {
2562                // user is perhaps trying to swipe up/down the page
2563
2564                if (
2565                    (deltaY < 0 && scrollTop === i.contentHeight - i.containerHeight) ||
2566                    (deltaY > 0 && scrollTop === 0)
2567                ) {
2568                    // set prevent for mobile Chrome refresh
2569                    return window.scrollY === 0 && deltaY > 0 && env.isChrome;
2570                }
2571            } else if (magnitudeX > magnitudeY) {
2572                // user is perhaps trying to swipe left/right across the page
2573
2574                if (
2575                    (deltaX < 0 && scrollLeft === i.contentWidth - i.containerWidth) ||
2576                    (deltaX > 0 && scrollLeft === 0)
2577                ) {
2578                    return true;
2579                }
2580            }
2581
2582            return true;
2583        }
2584
2585        function applyTouchMove(differenceX, differenceY) {
2586            element.scrollTop -= differenceY;
2587            element.scrollLeft -= differenceX;
2588
2589            updateGeometry(i);
2590        }
2591
2592        var startOffset = {};
2593        var startTime = 0;
2594        var speed = {};
2595        var easingLoop = null;
2596
2597        function getTouch(e) {
2598            if (e.targetTouches) {
2599                return e.targetTouches[0];
2600            } else {
2601                // Maybe IE pointer
2602                return e;
2603            }
2604        }
2605
2606        function shouldHandle(e) {
2607            if (e.pointerType && e.pointerType === 'pen' && e.buttons === 0) {
2608                return false;
2609            }
2610            if (e.targetTouches && e.targetTouches.length === 1) {
2611                return true;
2612            }
2613            if (
2614                e.pointerType &&
2615                e.pointerType !== 'mouse' &&
2616                e.pointerType !== e.MSPOINTER_TYPE_MOUSE
2617            ) {
2618                return true;
2619            }
2620            return false;
2621        }
2622
2623        function touchStart(e) {
2624            if (!shouldHandle(e)) {
2625                return;
2626            }
2627
2628            var touch = getTouch(e);
2629
2630            startOffset.pageX = touch.pageX;
2631            startOffset.pageY = touch.pageY;
2632
2633            startTime = new Date().getTime();
2634
2635            if (easingLoop !== null) {
2636                clearInterval(easingLoop);
2637            }
2638        }
2639
2640        function shouldBeConsumedByChild(target, deltaX, deltaY) {
2641            if (!element.contains(target)) {
2642                return false;
2643            }
2644
2645            var cursor = target;
2646
2647            while (cursor && cursor !== element) {
2648                if (cursor.classList.contains(cls.element.consuming)) {
2649                    return true;
2650                }
2651
2652                var style = get(cursor);
2653                var overflow = [style.overflow, style.overflowX, style.overflowY].join(
2654                    ''
2655                );
2656
2657                // if scrollable
2658                if (overflow.match(/(scroll|auto)/)) {
2659                    var maxScrollTop = cursor.scrollHeight - cursor.clientHeight;
2660                    if (maxScrollTop > 0) {
2661                        if (
2662                            !(cursor.scrollTop === 0 && deltaY > 0) &&
2663                            !(cursor.scrollTop === maxScrollTop && deltaY < 0)
2664                        ) {
2665                            return true;
2666                        }
2667                    }
2668                    var maxScrollLeft = cursor.scrollLeft - cursor.clientWidth;
2669                    if (maxScrollLeft > 0) {
2670                        if (
2671                            !(cursor.scrollLeft === 0 && deltaX < 0) &&
2672                            !(cursor.scrollLeft === maxScrollLeft && deltaX > 0)
2673                        ) {
2674                            return true;
2675                        }
2676                    }
2677                }
2678
2679                cursor = cursor.parentNode;
2680            }
2681
2682            return false;
2683        }
2684
2685        function touchMove(e) {
2686            if (shouldHandle(e)) {
2687                var touch = getTouch(e);
2688
2689                var currentOffset = { pageX: touch.pageX, pageY: touch.pageY };
2690
2691                var differenceX = currentOffset.pageX - startOffset.pageX;
2692                var differenceY = currentOffset.pageY - startOffset.pageY;
2693
2694                if (shouldBeConsumedByChild(e.target, differenceX, differenceY)) {
2695                    return;
2696                }
2697
2698                applyTouchMove(differenceX, differenceY);
2699                startOffset = currentOffset;
2700
2701                var currentTime = new Date().getTime();
2702
2703                var timeGap = currentTime - startTime;
2704                if (timeGap > 0) {
2705                    speed.x = differenceX / timeGap;
2706                    speed.y = differenceY / timeGap;
2707                    startTime = currentTime;
2708                }
2709
2710                if (shouldPrevent(differenceX, differenceY)) {
2711                    e.preventDefault();
2712                }
2713            }
2714        }
2715        function touchEnd() {
2716            if (i.settings.swipeEasing) {
2717                clearInterval(easingLoop);
2718                easingLoop = setInterval(function() {
2719                    if (i.isInitialized) {
2720                        clearInterval(easingLoop);
2721                        return;
2722                    }
2723
2724                    if (!speed.x && !speed.y) {
2725                        clearInterval(easingLoop);
2726                        return;
2727                    }
2728
2729                    if (Math.abs(speed.x) < 0.01 && Math.abs(speed.y) < 0.01) {
2730                        clearInterval(easingLoop);
2731                        return;
2732                    }
2733
2734                    applyTouchMove(speed.x * 30, speed.y * 30);
2735
2736                    speed.x *= 0.8;
2737                    speed.y *= 0.8;
2738                }, 10);
2739            }
2740        }
2741
2742        if (env.supportsTouch) {
2743            i.event.bind(element, 'touchstart', touchStart);
2744            i.event.bind(element, 'touchmove', touchMove);
2745            i.event.bind(element, 'touchend', touchEnd);
2746        } else if (env.supportsIePointer) {
2747            if (window.PointerEvent) {
2748                i.event.bind(element, 'pointerdown', touchStart);
2749                i.event.bind(element, 'pointermove', touchMove);
2750                i.event.bind(element, 'pointerup', touchEnd);
2751            } else if (window.MSPointerEvent) {
2752                i.event.bind(element, 'MSPointerDown', touchStart);
2753                i.event.bind(element, 'MSPointerMove', touchMove);
2754                i.event.bind(element, 'MSPointerUp', touchEnd);
2755            }
2756        }
2757    };
2758
2759    var defaultSettings = function () { return ({
2760        handlers: ['click-rail', 'drag-thumb', 'keyboard', 'wheel', 'touch'],
2761        maxScrollbarLength: null,
2762        minScrollbarLength: null,
2763        scrollingThreshold: 1000,
2764        scrollXMarginOffset: 0,
2765        scrollYMarginOffset: 0,
2766        suppressScrollX: false,
2767        suppressScrollY: false,
2768        swipeEasing: true,
2769        useBothWheelAxes: false,
2770        wheelPropagation: false,
2771        wheelSpeed: 1,
2772    }); };
2773
2774    var handlers = {
2775        'click-rail': clickRail,
2776        'drag-thumb': dragThumb,
2777        keyboard: keyboard,
2778        wheel: wheel,
2779        touch: touch,
2780    };
2781
2782    var PerfectScrollbar = function PerfectScrollbar(element, userSettings) {
2783        var this$1 = this;
2784        if ( userSettings === void 0 ) userSettings = {};
2785
2786        if (typeof element === 'string') {
2787            element = document.querySelector(element);
2788        }
2789
2790        if (!element || !element.nodeName) {
2791            throw new Error('no element is specified to initialize PerfectScrollbar');
2792        }
2793
2794        this.element = element;
2795
2796        element.classList.add(cls.main);
2797
2798        this.settings = defaultSettings();
2799        for (var key in userSettings) {
2800            this$1.settings[key] = userSettings[key];
2801        }
2802
2803        this.containerWidth = null;
2804        this.containerHeight = null;
2805        this.contentWidth = null;
2806        this.contentHeight = null;
2807
2808        var focus = function () { return element.classList.add(cls.state.focus); };
2809        var blur = function () { return element.classList.remove(cls.state.focus); };
2810
2811        this.isRtl = get(element).direction === 'rtl';
2812        this.isNegativeScroll = (function () {
2813            var originalScrollLeft = element.scrollLeft;
2814            var result = null;
2815            element.scrollLeft = -1;
2816            result = element.scrollLeft < 0;
2817            element.scrollLeft = originalScrollLeft;
2818            return result;
2819        })();
2820        this.negativeScrollAdjustment = this.isNegativeScroll
2821            ? element.scrollWidth - element.clientWidth
2822            : 0;
2823        this.event = new EventManager();
2824        this.ownerDocument = element.ownerDocument || document;
2825
2826        this.scrollbarXRail = div(cls.element.rail('x'));
2827        element.appendChild(this.scrollbarXRail);
2828        this.scrollbarX = div(cls.element.thumb('x'));
2829        this.scrollbarXRail.appendChild(this.scrollbarX);
2830        this.scrollbarX.setAttribute('tabindex', 0);
vendor: 5,472 bytes, lines 2831-2976
2831        this.event.bind(this.scrollbarX, 'focus', focus);
2832        this.event.bind(this.scrollbarX, 'blur', blur);
2833        this.scrollbarXActive = null;
2834        this.scrollbarXWidth = null;
2835        this.scrollbarXLeft = null;
2836        var railXStyle = get(this.scrollbarXRail);
2837        this.scrollbarXBottom = parseInt(railXStyle.bottom, 10);
2838        if (isNaN(this.scrollbarXBottom)) {
2839            this.isScrollbarXUsingBottom = false;
2840            this.scrollbarXTop = toInt(railXStyle.top);
2841        } else {
2842            this.isScrollbarXUsingBottom = true;
2843        }
2844        this.railBorderXWidth =
2845            toInt(railXStyle.borderLeftWidth) + toInt(railXStyle.borderRightWidth);
2846        // Set rail to display:block to calculate margins
2847        set(this.scrollbarXRail, { display: 'block' });
2848        this.railXMarginWidth =
2849            toInt(railXStyle.marginLeft) + toInt(railXStyle.marginRight);
2850        set(this.scrollbarXRail, { display: '' });
2851        this.railXWidth = null;
2852        this.railXRatio = null;
2853
2854        this.scrollbarYRail = div(cls.element.rail('y'));
2855        element.appendChild(this.scrollbarYRail);
2856        this.scrollbarY = div(cls.element.thumb('y'));
2857        this.scrollbarYRail.appendChild(this.scrollbarY);
2858        this.scrollbarY.setAttribute('tabindex', 0);
2859        this.event.bind(this.scrollbarY, 'focus', focus);
2860        this.event.bind(this.scrollbarY, 'blur', blur);
2861        this.scrollbarYActive = null;
2862        this.scrollbarYHeight = null;
2863        this.scrollbarYTop = null;
2864        var railYStyle = get(this.scrollbarYRail);
2865        this.scrollbarYRight = parseInt(railYStyle.right, 10);
2866        if (isNaN(this.scrollbarYRight)) {
2867            this.isScrollbarYUsingRight = false;
2868            this.scrollbarYLeft = toInt(railYStyle.left);
2869        } else {
2870            this.isScrollbarYUsingRight = true;
2871        }
2872        this.scrollbarYOuterWidth = this.isRtl ? outerWidth(this.scrollbarY) : null;
2873        this.railBorderYWidth =
2874            toInt(railYStyle.borderTopWidth) + toInt(railYStyle.borderBottomWidth);
2875        set(this.scrollbarYRail, { display: 'block' });
2876        this.railYMarginHeight =
2877            toInt(railYStyle.marginTop) + toInt(railYStyle.marginBottom);
2878        set(this.scrollbarYRail, { display: '' });
2879        this.railYHeight = null;
2880        this.railYRatio = null;
2881
2882        this.reach = {
2883            x:
2884                element.scrollLeft <= 0
2885                    ? 'start'
2886                    : element.scrollLeft >= this.contentWidth - this.containerWidth
2887                    ? 'end'
2888                    : null,
2889            y:
2890                element.scrollTop <= 0
2891                    ? 'start'
2892                    : element.scrollTop >= this.contentHeight - this.containerHeight
2893                    ? 'end'
2894                    : null,
2895        };
2896
2897        this.isAlive = true;
2898
2899        this.settings.handlers.forEach(function (handlerName) { return handlers[handlerName](this$1); });
2900
2901        this.lastScrollTop = element.scrollTop; // for onScroll only
2902        this.lastScrollLeft = element.scrollLeft; // for onScroll only
2903        this.event.bind(this.element, 'scroll', function (e) { return this$1.onScroll(e); });
2904        updateGeometry(this);
2905    };
2906
2907    PerfectScrollbar.prototype.update = function update () {
2908        if (!this.isAlive) {
2909            return;
2910        }
2911
2912        // Recalcuate negative scrollLeft adjustment
2913        this.negativeScrollAdjustment = this.isNegativeScroll
2914            ? this.element.scrollWidth - this.element.clientWidth
2915            : 0;
2916
2917        // Recalculate rail margins
2918        set(this.scrollbarXRail, { display: 'block' });
2919        set(this.scrollbarYRail, { display: 'block' });
2920        this.railXMarginWidth =
2921            toInt(get(this.scrollbarXRail).marginLeft) +
2922            toInt(get(this.scrollbarXRail).marginRight);
2923        this.railYMarginHeight =
2924            toInt(get(this.scrollbarYRail).marginTop) +
2925            toInt(get(this.scrollbarYRail).marginBottom);
2926
2927        // Hide scrollbars not to affect scrollWidth and scrollHeight
2928        set(this.scrollbarXRail, { display: 'none' });
2929        set(this.scrollbarYRail, { display: 'none' });
2930
2931        updateGeometry(this);
2932
2933        processScrollDiff(this, 'top', 0, false, true);
2934        processScrollDiff(this, 'left', 0, false, true);
2935
2936        set(this.scrollbarXRail, { display: '' });
2937        set(this.scrollbarYRail, { display: '' });
2938    };
2939
2940    PerfectScrollbar.prototype.onScroll = function onScroll (e) {
2941        if (!this.isAlive) {
2942            return;
2943        }
2944
2945        updateGeometry(this);
2946        processScrollDiff(this, 'top', this.element.scrollTop - this.lastScrollTop);
2947        processScrollDiff(
2948            this,
2949            'left',
2950            this.element.scrollLeft - this.lastScrollLeft
2951        );
2952
2953        this.lastScrollTop = this.element.scrollTop;
2954        this.lastScrollLeft = this.element.scrollLeft;
2955    };
2956
2957    PerfectScrollbar.prototype.destroy = function destroy () {
2958        if (!this.isAlive) {
2959            return;
2960        }
2961
2962        this.event.unbindAll();
2963        remove(this.scrollbarX);
2964        remove(this.scrollbarY);
2965        remove(this.scrollbarXRail);
2966        remove(this.scrollbarYRail);
2967        this.removePsClasses();
2968
2969        // unset elements
2970        this.element = null;
2971        this.scrollbarX = null;
2972        this.scrollbarY = null;
2973        this.scrollbarXRail = null;
2974        this.scrollbarYRail = null;
2975
2976        this.isAlive = false;
2977    };
2978
2979    PerfectScrollbar.prototype.removePsClasses = function removePsClasses () {
2980        this.element.className = this.element.className
2981            .split(' ')
2982            .filter(function (name) { return !name.match(/^ps([-_].+|)$/); })
2983            .join(' ');
2984    };
2985
2986    return PerfectScrollbar;
2987
2988})));
2989/**
2990 * END: perfect-scrollbar.js
2991 */
2992},{}]},{},[1]);

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.