PageSourceSearch

https://artwoodbase.ru/g/libs/remodal/js/remodal.js

js artwoodbase.ru collected 2026-09-30 06:21:02 UTC 19,135 bytes, 776 lines download raw bytes

vendor: 19,135 bytes, lines 1-776
1!(function(root, factory) {
2  if (typeof define === 'function' && define.amd) {
3    define(['jquery'], function($) {
4      return factory(root, $);
5    });
6  } else if (typeof exports === 'object') {
7    factory(root, require('jquery'));
8  } else {
9    factory(root, root.jQuery || root.Zepto);
10  }
11})(this, function(global, $) {
12
13  'use strict';
14
15  /**
16   * Name of the plugin
17   * @private
18   * @const
19   * @type {String}
20   */
21  var PLUGIN_NAME = 'remodal';
22
23  /**
24   * Namespace for CSS and events
25   * @private
26   * @const
27   * @type {String}
28   */
29  var NAMESPACE = global.REMODAL_GLOBALS && global.REMODAL_GLOBALS.NAMESPACE || PLUGIN_NAME;
30
31  /**
32   * Animationstart event with vendor prefixes
33   * @private
34   * @const
35   * @type {String}
36   */
37  var ANIMATIONSTART_EVENTS = $.map(
38    ['animationstart', 'webkitAnimationStart', 'MSAnimationStart', 'oAnimationStart'],
39
40    function(eventName) {
41      return eventName + '.' + NAMESPACE;
42    }
43
44  ).join(' ');
45
46  /**
47   * Animationend event with vendor prefixes
48   * @private
49   * @const
50   * @type {String}
51   */
52  var ANIMATIONEND_EVENTS = $.map(
53    ['animationend', 'webkitAnimationEnd', 'MSAnimationEnd', 'oAnimationEnd'],
54
55    function(eventName) {
56      return eventName + '.' + NAMESPACE;
57    }
58
59  ).join(' ');
60
61  /**
62   * Default settings
63   * @private
64   * @const
65   * @type {Object}
66   */
67  var DEFAULTS = $.extend({
68    hashTracking: true,
69    closeOnConfirm: true,
70    closeOnCancel: true,
71    closeOnEscape: true,
72    closeOnOutsideClick: true,
73    modifier: '',
74    appendTo: null
75  }, global.REMODAL_GLOBALS && global.REMODAL_GLOBALS.DEFAULTS);
76
77  /**
78   * States of the Remodal
79   * @private
80   * @const
81   * @enum {String}
82   */
83  var STATES = {
84    CLOSING: 'closing',
85    CLOSED: 'closed',
86    OPENING: 'opening',
87    OPENED: 'opened'
88  };
89
90  /**
91   * Reasons of the state change.
92   * @private
93   * @const
94   * @enum {String}
95   */
96  var STATE_CHANGE_REASONS = {
97    CONFIRMATION: 'confirmation',
98    CANCELLATION: 'cancellation'
99  };
100
101  /**
102   * Is animation supported?
103   * @private
104   * @const
105   * @type {Boolean}
106   */
107  var IS_ANIMATION = (function() {
108    var style = document.createElement('div').style;
109
110    return style.animationName !== undefined ||
111      style.WebkitAnimationName !== undefined ||
112      style.MozAnimationName !== undefined ||
113      style.msAnimationName !== undefined ||
114      style.OAnimationName !== undefined;
115  })();
116
117  /**
118   * Is iOS?
119   * @private
120   * @const
121   * @type {Boolean}
122   */
123  var IS_IOS = /iPad|iPhone|iPod/.test(navigator.platform);
124
125  /**
126   * Current modal
127   * @private
128   * @type {Remodal}
129   */
130  var current;
131
132  /**
133   * Scrollbar position
134   * @private
135   * @type {Number}
136   */
137  var scrollTop;
138
139  /**
140   * Returns an animation duration
141   * @private
142   * @param {jQuery} $elem
143   * @returns {Number}
144   */
145  function getAnimationDuration($elem) {
146    if (
147      IS_ANIMATION &&
148      $elem.css('animation-name') === 'none' &&
149      $elem.css('-webkit-animation-name') === 'none' &&
150      $elem.css('-moz-animation-name') === 'none' &&
151      $elem.css('-o-animation-name') === 'none' &&
152      $elem.css('-ms-animation-name') === 'none'
153    ) {
154      return 0;
155    }
156
157    var duration = $elem.css('animation-duration') ||
158      $elem.css('-webkit-animation-duration') ||
159      $elem.css('-moz-animation-duration') ||
160      $elem.css('-o-animation-duration') ||
161      $elem.css('-ms-animation-duration') ||
162      '0s';
163
164    var delay = $elem.css('animation-delay') ||
165      $elem.css('-webkit-animation-delay') ||
166      $elem.css('-moz-animation-delay') ||
167      $elem.css('-o-animation-delay') ||
168      $elem.css('-ms-animation-delay') ||
169      '0s';
170
171    var iterationCount = $elem.css('animation-iteration-count') ||
172      $elem.css('-webkit-animation-iteration-count') ||
173      $elem.css('-moz-animation-iteration-count') ||
174      $elem.css('-o-animation-iteration-count') ||
175      $elem.css('-ms-animation-iteration-count') ||
176      '1';
177
178    var max;
179    var len;
180    var num;
181    var i;
182
183    duration = duration.split(', ');
184    delay = delay.split(', ');
185    iterationCount = iterationCount.split(', ');
186
187    // The 'duration' size is the same as the 'delay' size
188    for (i = 0, len = duration.length, max = Number.NEGATIVE_INFINITY; i < len; i++) {
189      num = parseFloat(duration[i]) * parseInt(iterationCount[i], 10) + parseFloat(delay[i]);
190
191      if (num > max) {
192        max = num;
193      }
194    }
195
196    return max;
197  }
198
199  /**
200   * Returns a scrollbar width
201   * @private
202   * @returns {Number}
203   */
204  function getScrollbarWidth() {
205    if ($(document).height() <= $(window).height()) {
206      return 0;
207    }
208
209    var outer = document.createElement('div');
210    var inner = document.createElement('div');
211    var widthNoScroll;
212    var widthWithScroll;
213
214    outer.style.visibility = 'hidden';
215    outer.style.width = '100px';
216    document.body.appendChild(outer);
217
218    widthNoScroll = outer.offsetWidth;
219
220    // Force scrollbars
221    outer.style.overflow = 'scroll';
222
223    // Add inner div
224    inner.style.width = '100%';
225    outer.appendChild(inner);
226
227    widthWithScroll = inner.offsetWidth;
228
229    // Remove divs
230    outer.parentNode.removeChild(outer);
231
232    return widthNoScroll - widthWithScroll;
233  }
234
235  /**
236   * Locks the screen
237   * @private
238   */
239  function lockScreen() {
240    if (IS_IOS) {
241      return;
242    }
243
244    var $html = $('html');
245    var lockedClass = namespacify('is-locked');
246    var paddingRight;
247    var $body;
248
249    if (!$html.hasClass(lockedClass)) {
250      $body = $(document.body);
251
252      // Zepto does not support '-=', '+=' in the `css` method
253      paddingRight = parseInt($body.css('padding-right'), 10) + getScrollbarWidth();
254
255      $body.css('padding-right', paddingRight + 'px');
256      $html.addClass(lockedClass);
257    }
258  }
259
260  /**
261   * Unlocks the screen
262   * @private
263   */
264  function unlockScreen() {
265    if (IS_IOS) {
266      return;
267    }
268
269    var $html = $('html');
270    var lockedClass = namespacify('is-locked');
271    var paddingRight;
272    var $body;
273
274    if ($html.hasClass(lockedClass)) {
275      $body = $(document.body);
276
277      // Zepto does not support '-=', '+=' in the `css` method
278      paddingRight = parseInt($body.css('padding-right'), 10) - getScrollbarWidth();
279
280      $body.css('padding-right', paddingRight + 'px');
281      $html.removeClass(lockedClass);
282    }
283  }
284
285  /**
286   * Sets a state for an instance
287   * @private
288   * @param {Remodal} instance
289   * @param {STATES} state
290   * @param {Boolean} isSilent If true, Remodal does not trigger events
291   * @param {String} Reason of a state change.
292   */
293  function setState(instance, state, isSilent, reason) {
294
295    var newState = namespacify('is', state);
296    var allStates = [namespacify('is', STATES.CLOSING),
297                     namespacify('is', STATES.OPENING),
298                     namespacify('is', STATES.CLOSED),
299                     namespacify('is', STATES.OPENED)].join(' ');
300
301    instance.$bg
302      .removeClass(allStates)
303      .addClass(newState);
304
305    instance.$overlay
306      .removeClass(allStates)
307      .addClass(newState);
308
309    instance.$wrapper
310      .removeClass(allStates)
311      .addClass(newState);
312
313    instance.$modal
314      .removeClass(allStates)
315      .addClass(newState);
316
317    instance.state = state;
318    !isSilent && instance.$modal.trigger({
319      type: state,
320      reason: reason
321    }, [{ reason: reason }]);
322  }
323
324  /**
325   * Synchronizes with the animation
326   * @param {Function} doBeforeAnimation
327   * @param {Function} doAfterAnimation
328   * @param {Remodal} instance
329   */
330  function syncWithAnimation(doBeforeAnimation, doAfterAnimation, instance) {
331    var runningAnimationsCount = 0;
332
333    var handleAnimationStart = function(e) {
334      if (e.target !== this) {
335        return;
336      }
337
338      runningAnimationsCount++;
339    };
340
341    var handleAnimationEnd = function(e) {
342      if (e.target !== this) {
343        return;
344      }
345
346      if (--runningAnimationsCount === 0) {
347
348        // Remove event listeners
349        $.each(['$bg', '$overlay', '$wrapper', '$modal'], function(index, elemName) {
350          instance[elemName].off(ANIMATIONSTART_EVENTS + ' ' + ANIMATIONEND_EVENTS);
351        });
352
353        doAfterAnimation();
354      }
355    };
356
357    $.each(['$bg', '$overlay', '$wrapper', '$modal'], function(index, elemName) {
358      instance[elemName]
359        .on(ANIMATIONSTART_EVENTS, handleAnimationStart)
360        .on(ANIMATIONEND_EVENTS, handleAnimationEnd);
361    });
362
363    doBeforeAnimation();
364
365    // If the animation is not supported by a browser or its duration is 0
366    if (
367      getAnimationDuration(instance.$bg) === 0 &&
368      getAnimationDuration(instance.$overlay) === 0 &&
369      getAnimationDuration(instance.$wrapper) === 0 &&
370      getAnimationDuration(instance.$modal) === 0
371    ) {
372
373      // Remove event listeners
374      $.each(['$bg', '$overlay', '$wrapper', '$modal'], function(index, elemName) {
375        instance[elemName].off(ANIMATIONSTART_EVENTS + ' ' + ANIMATIONEND_EVENTS);
376      });
377
378      doAfterAnimation();
379    }
380  }
381
382  /**
383   * Closes immediately
384   * @private
385   * @param {Remodal} instance
386   */
387  function halt(instance) {
388    if (instance.state === STATES.CLOSED) {
389      return;
390    }
391
392    $.each(['$bg', '$overlay', '$wrapper', '$modal'], function(index, elemName) {
393      instance[elemName].off(ANIMATIONSTART_EVENTS + ' ' + ANIMATIONEND_EVENTS);
394    });
395
396    instance.$bg.removeClass(instance.settings.modifier);
397    instance.$overlay.removeClass(instance.settings.modifier).hide();
398    instance.$wrapper.hide();
399    unlockScreen();
400    setState(instance, STATES.CLOSED, true);
401  }
402
403  /**
404   * Parses a string with options
405   * @private
406   * @param str
407   * @returns {Object}
408   */
409  function parseOptions(str) {
410    var obj = {};
411    var arr;
412    var len;
413    var val;
414    var i;
415
416    // Remove spaces before and after delimiters
417    str = str.replace(/\s*:\s*/g, ':').replace(/\s*,\s*/g, ',');
418
419    // Parse a string
420    arr = str.split(',');
421    for (i = 0, len = arr.length; i < len; i++) {
422      arr[i] = arr[i].split(':');
423      val = arr[i][1];
424
425      // Convert a string value if it is like a boolean
426      if (typeof val === 'string' || val instanceof String) {
427        val = val === 'true' || (val === 'false' ? false : val);
428      }
429
430      // Convert a string value if it is like a number
431      if (typeof val === 'string' || val instanceof String) {
432        val = !isNaN(val) ? +val : val;
433      }
434
435      obj[arr[i][0]] = val;
436    }
437
438    return obj;
439  }
440
441  /**
442   * Generates a string separated by dashes and prefixed with NAMESPACE
443   * @private
444   * @param {...String}
445   * @returns {String}
446   */
447  function namespacify() {
448    var result = NAMESPACE;
449
450    for (var i = 0; i < arguments.length; ++i) {
451      result += '-' + arguments[i];
452    }
453
454    return result;
455  }
456
457  /**
458   * Handles the hashchange event
459   * @private
460   * @listens hashchange
461   */
462  function handleHashChangeEvent() {
463    var id = location.hash.replace('#', '');
464    var instance;
465    var $elem;
466
467    if (!id) {
468
469      // Check if we have currently opened modal and animation was completed
470      if (current && current.state === STATES.OPENED && current.settings.hashTracking) {
471        current.close();
472      }
473    } else {
474
475      // Catch syntax error if your hash is bad
476      try {
477        $elem = $(
478          '[data-' + PLUGIN_NAME + '-id="' + id + '"]'
479        );
480      } catch (err) {}
481
482      if ($elem && $elem.length) {
483        instance = $[PLUGIN_NAME].lookup[$elem.data(PLUGIN_NAME)];
484
485        if (instance && instance.settings.hashTracking) {
486          instance.open();
487        }
488      }
489
490    }
491  }
492
493  /**
494   * Remodal constructor
495   * @constructor
496   * @param {jQuery} $modal
497   * @param {Object} options
498   */
499  function Remodal($modal, options) {
500    var $body = $(document.body);
501    var $appendTo = $body;
502    var remodal = this;
503
504    remodal.settings = $.extend({}, DEFAULTS, options);
505    remodal.index = $[PLUGIN_NAME].lookup.push(remodal) - 1;
506    remodal.state = STATES.CLOSED;
507
508    remodal.$overlay = $('.' + namespacify('overlay'));
509
510    if (remodal.settings.appendTo !== null && remodal.settings.appendTo.length) {
511      $appendTo = $(remodal.settings.appendTo);
512    }
513
514    if (!remodal.$overlay.length) {
515      remodal.$overlay = $('<div>').addClass(namespacify('overlay') + ' ' + namespacify('is', STATES.CLOSED)).hide();
516      $appendTo.append(remodal.$overlay);
517    }
518
519    remodal.$bg = $('.' + namespacify('bg')).addClass(namespacify('is', STATES.CLOSED));
520
521    remodal.$modal = $modal
522      .addClass(
523        NAMESPACE + ' ' +
524        namespacify('is-initialized') + ' ' +
525        remodal.settings.modifier + ' ' +
526        namespacify('is', STATES.CLOSED))
527      .attr('tabindex', '-1');
528
529    remodal.$wrapper = $('<div>')
530      .addClass(
531        namespacify('wrapper') + ' ' +
532        remodal.settings.modifier + ' ' +
533        namespacify('is', STATES.CLOSED))
534      .hide()
535      .append(remodal.$modal);
536    $appendTo.append(remodal.$wrapper);
537
538    // Add the event listener for the close button
539    remodal.$wrapper.on('click.' + NAMESPACE, '[data-' + PLUGIN_NAME + '-action="close"]', function(e) {
540      e.preventDefault();
541
542      remodal.close();
543    });
544
545    // Add the event listener for the cancel button
546    remodal.$wrapper.on('click.' + NAMESPACE, '[data-' + PLUGIN_NAME + '-action="cancel"]', function(e) {
547      e.preventDefault();
548
549      remodal.$modal.trigger(STATE_CHANGE_REASONS.CANCELLATION);
550
551      if (remodal.settings.closeOnCancel) {
552        remodal.close(STATE_CHANGE_REASONS.CANCELLATION);
553      }
554    });
555
556    // Add the event listener for the confirm button
557    remodal.$wrapper.on('click.' + NAMESPACE, '[data-' + PLUGIN_NAME + '-action="confirm"]', function(e) {
558      e.preventDefault();
559
560      remodal.$modal.trigger(STATE_CHANGE_REASONS.CONFIRMATION);
561
562      if (remodal.settings.closeOnConfirm) {
563        remodal.close(STATE_CHANGE_REASONS.CONFIRMATION);
564      }
565    });
566
567    // Add the event listener for the overlay
568    remodal.$wrapper.on('click.' + NAMESPACE, function(e) {
569      var $target = $(e.target);
570
571      if (!$target.hasClass(namespacify('wrapper'))) {
572        return;
573      }
574
575      if (remodal.settings.closeOnOutsideClick) {
576        remodal.close();
577      }
578    });
579  }
580
581  /**
582   * Opens a modal window
583   * @public
584   */
585  Remodal.prototype.open = function() {
586    var remodal = this;
587    var id;
588
589    // Check if the animation was completed
590    if (remodal.state === STATES.OPENING || remodal.state === STATES.CLOSING) {
591      return;
592    }
593
594    id = remodal.$modal.attr('data-' + PLUGIN_NAME + '-id');
595
596    if (id && remodal.settings.hashTracking) {
597      scrollTop = $(window).scrollTop();
598      location.hash = id;
599    }
600
601    if (current && current !== remodal) {
602      halt(current);
603    }
604
605    current = remodal;
606    lockScreen();
607    remodal.$bg.addClass(remodal.settings.modifier);
608    remodal.$overlay.addClass(remodal.settings.modifier).show();
609    remodal.$wrapper.show().scrollTop(0);
610    remodal.$modal.focus();
611
612    syncWithAnimation(
613      function() {
614        setState(remodal, STATES.OPENING);
615      },
616
617      function() {
618        setState(remodal, STATES.OPENED);
619      },
620
621      remodal);
622  };
623
624  /**
625   * Closes a modal window
626   * @public
627   * @param {String} reason
628   */
629  Remodal.prototype.close = function(reason) {
630    var remodal = this;
631
632    // Check if the animation was completed
633    if (remodal.state === STATES.OPENING || remodal.state === STATES.CLOSING || remodal.state === STATES.CLOSED) {
634      return;
635    }
636
637    if (
638      remodal.settings.hashTracking &&
639      remodal.$modal.attr('data-' + PLUGIN_NAME + '-id') === location.hash.substr(1)
640    ) {
641      location.hash = '';
642      $(window).scrollTop(scrollTop);
643    }
644
645    syncWithAnimation(
646      function() {
647        setState(remodal, STATES.CLOSING, false, reason);
648      },
649
650      function() {
651        remodal.$bg.removeClass(remodal.settings.modifier);
652        remodal.$overlay.removeClass(remodal.settings.modifier).hide();
653        remodal.$wrapper.hide();
654        unlockScreen();
655
656        setState(remodal, STATES.CLOSED, false, reason);
657      },
658
659      remodal);
660  };
661
662  /**
663   * Returns a current state of a modal
664   * @public
665   * @returns {STATES}
666   */
667  Remodal.prototype.getState = function() {
668    return this.state;
669  };
670
671  /**
672   * Destroys a modal
673   * @public
674   */
675  Remodal.prototype.destroy = function() {
676    var lookup = $[PLUGIN_NAME].lookup;
677    var instanceCount;
678
679    halt(this);
680    this.$wrapper.remove();
681
682    delete lookup[this.index];
683    instanceCount = $.grep(lookup, function(instance) {
684      return !!instance;
685    }).length;
686
687    if (instanceCount === 0) {
688      this.$overlay.remove();
689      this.$bg.removeClass(
690        namespacify('is', STATES.CLOSING) + ' ' +
691        namespacify('is', STATES.OPENING) + ' ' +
692        namespacify('is', STATES.CLOSED) + ' ' +
693        namespacify('is', STATES.OPENED));
694    }
695  };
696
697  /**
698   * Special plugin object for instances
699   * @public
700   * @type {Object}
701   */
702  $[PLUGIN_NAME] = {
703    lookup: []
704  };
705
706  /**
707   * Plugin constructor
708   * @constructor
709   * @param {Object} options
710   * @returns {JQuery}
711   */
712  $.fn[PLUGIN_NAME] = function(opts) {
713    var instance;
714    var $elem;
715
716    this.each(function(index, elem) {
717      $elem = $(elem);
718
719      if ($elem.data(PLUGIN_NAME) == null) {
720        instance = new Remodal($elem, opts);
721        $elem.data(PLUGIN_NAME, instance.index);
722
723        if (
724          instance.settings.hashTracking &&
725          $elem.attr('data-' + PLUGIN_NAME + '-id') === location.hash.substr(1)
726        ) {
727          instance.open();
728        }
729      } else {
730        instance = $[PLUGIN_NAME].lookup[$elem.data(PLUGIN_NAME)];
731      }
732    });
733
734    return instance;
735  };
736
737  $(document).ready(function() {
738
739    // data-remodal-target opens a modal window with the special Id
740    $(document).on('click', '[data-' + PLUGIN_NAME + '-target]', function(e) {
741      e.preventDefault();
742
743      var elem = e.currentTarget;
744      var id = elem.getAttribute('data-' + PLUGIN_NAME + '-target');
745      var $target = $('[data-' + PLUGIN_NAME + '-id="' + id + '"]');
746
747      $[PLUGIN_NAME].lookup[$target.data(PLUGIN_NAME)].open();
748    });
749
750    // Auto initialization of modal windows
751    // They should have the 'remodal' class attribute
752    // Also you can write the `data-remodal-options` attribute to pass params into the modal
753    $(document).find('.' + NAMESPACE).each(function(i, container) {
754      var $container = $(container);
755      var options = $container.data(PLUGIN_NAME + '-options');
756
757      if (!options) {
758        options = {};
759      } else if (typeof options === 'string' || options instanceof String) {
760        options = parseOptions(options);
761      }
762
763      $container[PLUGIN_NAME](options);
764    });
765
766    // Handles the keydown event
767    $(document).on('keydown.' + NAMESPACE, function(e) {
768      if (current && current.settings.closeOnEscape && current.state === STATES.OPENED && e.keyCode === 27) {
769        current.close();
770      }
771    });
772
773    // Handles the hashchange event
774    $(window).on('hashchange.' + NAMESPACE, handleHashChangeEvent);
775  });
776});

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.