PluginProbe
Depicter — Popup & Slider Builder / 1.3.8
Depicter — Popup & Slider Builder v1.3.8
4.8.1 trunk 1.0.0 1.1.0 1.1.2 1.1.4 1.1.6 1.1.7 1.1.8 1.1.9 1.2.0 1.3.0 1.3.1 1.3.2 1.3.3 1.3.5 1.3.8 1.5.0 1.5.1 1.5.2 1.5.5 1.6.0 1.6.1 1.6.2 1.7.0 All 76 releases
depicter / resources / scripts / player / masterslider.js

masterslider.js in Depicter — Popup & Slider Builder 1.3.8, at resources/scripts/player/masterslider.js

11,701 lines 319.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1
2 (function(l, r) { if (!l || l.getElementById('livereloadscript')) return; r = l.createElement('script'); r.async = 1; r.src = '//' + (self.location.host || 'localhost').split(':')[0] + ':35729/livereload.js?snipver=1'; r.id = 'livereloadscript'; l.getElementsByTagName('head')[0].appendChild(r) })(self.document);
3 (function (global, factory) {
4 typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('@master-slider/animator')) :
5 typeof define === 'function' && define.amd ? define(['@master-slider/animator'], factory) :
6 (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.MasterSlider = factory(global.animator));
7 })(this, (function (animator) { 'use strict';
8
9 function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
10
11 var animator__default = /*#__PURE__*/_interopDefaultLegacy(animator);
12
13 function ownKeys(object, enumerableOnly) {
14 var keys = Object.keys(object);
15
16 if (Object.getOwnPropertySymbols) {
17 var symbols = Object.getOwnPropertySymbols(object);
18
19 if (enumerableOnly) {
20 symbols = symbols.filter(function (sym) {
21 return Object.getOwnPropertyDescriptor(object, sym).enumerable;
22 });
23 }
24
25 keys.push.apply(keys, symbols);
26 }
27
28 return keys;
29 }
30
31 function _objectSpread2(target) {
32 for (var i = 1; i < arguments.length; i++) {
33 var source = arguments[i] != null ? arguments[i] : {};
34
35 if (i % 2) {
36 ownKeys(Object(source), true).forEach(function (key) {
37 _defineProperty(target, key, source[key]);
38 });
39 } else if (Object.getOwnPropertyDescriptors) {
40 Object.defineProperties(target, Object.getOwnPropertyDescriptors(source));
41 } else {
42 ownKeys(Object(source)).forEach(function (key) {
43 Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key));
44 });
45 }
46 }
47
48 return target;
49 }
50
51 function _defineProperty(obj, key, value) {
52 if (key in obj) {
53 Object.defineProperty(obj, key, {
54 value: value,
55 enumerable: true,
56 configurable: true,
57 writable: true
58 });
59 } else {
60 obj[key] = value;
61 }
62
63 return obj;
64 }
65
66 function _objectWithoutPropertiesLoose(source, excluded) {
67 if (source == null) return {};
68 var target = {};
69 var sourceKeys = Object.keys(source);
70 var key, i;
71
72 for (i = 0; i < sourceKeys.length; i++) {
73 key = sourceKeys[i];
74 if (excluded.indexOf(key) >= 0) continue;
75 target[key] = source[key];
76 }
77
78 return target;
79 }
80
81 function _objectWithoutProperties(source, excluded) {
82 if (source == null) return {};
83
84 var target = _objectWithoutPropertiesLoose(source, excluded);
85
86 var key, i;
87
88 if (Object.getOwnPropertySymbols) {
89 var sourceSymbolKeys = Object.getOwnPropertySymbols(source);
90
91 for (i = 0; i < sourceSymbolKeys.length; i++) {
92 key = sourceSymbolKeys[i];
93 if (excluded.indexOf(key) >= 0) continue;
94 if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue;
95 target[key] = source[key];
96 }
97 }
98
99 return target;
100 }
101
102 /* eslint-disable func-names */
103
104 /* eslint-disable no-prototype-builtins */
105 (function (arr) {
106 arr.forEach(item => {
107 if (item.hasOwnProperty('remove')) {
108 return;
109 }
110
111 Object.defineProperty(item, 'remove', {
112 configurable: true,
113 enumerable: true,
114 writable: true,
115 value: function remove() {
116 if (this.parentNode) {
117 this.parentNode.removeChild(this);
118 }
119 }
120 });
121 });
122 })([Element.prototype, CharacterData.prototype, DocumentType.prototype]);
123
124 // eslint-disable-next-line func-names
125 (function (doc, proto) {
126 try {
127 // check if browser supports :scope natively
128 doc.querySelector(':scope body');
129 } catch (err) {
130 // polyfill native methods if it doesn't
131 ['querySelector', 'querySelectorAll'].forEach(method => {
132 const nativ = proto[method]; // eslint-disable-next-line func-names
133
134 proto[method] = function (selectors) {
135 if (/(^|,)\s*:scope/.test(selectors)) {
136 // only if selectors contains :scope
137 const {
138 id
139 } = this; // remember current element id
140
141 this.id = 'ID_' + Date.now(); // assign new unique id
142
143 selectors = selectors.replace(/((^|,)\s*):scope/g, '$1#' + this.id); // replace :scope with #ID
144
145 const result = doc[method](selectors);
146 this.id = id; // restore previous id
147
148 return result;
149 }
150
151 return nativ.call(this, selectors); // use native code for other selectors
152 };
153 });
154 }
155 })(window.document, Element.prototype);
156
157 /**
158 * Custom object event emitter class
159 * @author Averta
160 * @version 1.0.0
161 */
162 class Emitter {
163 constructor() {
164 this.listeners = {};
165 this._onceList = []; // define alias method names
166
167 this.addEventListener = this.on;
168 this.removeEventListener = this.off; // debug flag it traces all triggers
169
170 this.debugEvents = false;
171 }
172 /**
173 * Triggers new event
174 * @param {String} name event name
175 * @param {*} args handler custom arguments
176 * @param {Boolean} usePrefix whether add prefix before event name on parent Emitter
177 */
178
179
180 trigger(name, args, usePrefix = false) {
181 if (this.debugEvents) {
182 /* eslint-disable */
183 console.log(name, args);
184 /* eslint-enable */
185 }
186
187 if (this.parentEmitter) {
188 this.parentEmitter.trigger(!usePrefix ? name : this._transformName(name), args);
189 }
190
191 if (!this.listeners) {
192 return;
193 }
194
195 if (this.listeners[name]) {
196 if (args) {
197 args.unshift(name);
198 } else {
199 args = [name];
200 }
201
202 this.listeners[name].forEach(action => {
203 action.callback.apply(action.context, args);
204 });
205 }
206
207 if (this._onceList.length) {
208 this._onceList = this._onceList.filter(value => {
209 if (value.name === name) {
210 this.off(value.name, value.callback, value.context);
211 return false;
212 }
213
214 return true;
215 });
216 }
217 }
218 /**
219 * Adds new event listener
220 * @param {String} name Event name
221 * @param {Function} callback Event listener
222 * @param {*} context Event listener this argument
223 * @param {Number} priority Listener priority
224 */
225
226
227 on(name, callback, context, priority = 0) {
228 if (name.indexOf(',') !== -1) {
229 name.replace(/\s*/g, '').split(',').forEach(namePart => {
230 this.on(namePart, callback, context, priority);
231 });
232 return;
233 }
234
235 if (!this.listeners[name]) {
236 this.listeners[name] = [];
237 }
238
239 let listeners = this.listeners[name];
240
241 if (listeners.find(l => l.callback === callback && l.context === context && l.priority === priority)) {
242 return;
243 }
244
245 listeners.push({
246 callback,
247 priority,
248 context
249 });
250 listeners = listeners.sort((a, b) => {
251 if (a.priority > b.priority) {
252 return 1;
253 }
254
255 if (a.priority < b.priority) {
256 return -1;
257 }
258
259 return 0;
260 });
261 }
262 /**
263 * Adds new event listener which only calls once
264 * @param {String} name Event name
265 * @param {Function} callback Event listener
266 * @param {*} context Event listener this argument
267 * @param {Number} priority Listener priority
268 */
269
270
271 once(name, callback, context, priority) {
272 this.on(name, callback, context, priority);
273
274 this._onceList.push({
275 name,
276 callback,
277 context
278 });
279 }
280 /**
281 * Removes the added event
282 * @param {String} name Event name
283 * @param {Function} callback Event callback
284 * @param {*} context Callback this argument
285 */
286
287
288 off(name, callback, context) {
289 if (name.indexOf(',') !== -1) {
290 name.replace(/\s*/g, '').split(',').forEach(namePart => {
291 this.off(namePart, callback, context);
292 });
293 return;
294 }
295
296 const listeners = this.listeners[name];
297
298 if (listeners && listeners.length) {
299 this.listeners[name] = listeners.filter(value => value.callback !== callback || value.context !== context);
300 }
301 }
302 /**
303 * Removes all registered listeners that has the callback with the given this argument value
304 * @param {*} context This argument value of registered callbacks
305 */
306
307
308 offOnContext(context) {
309 Object.keys(this.listeners).forEach(key => {
310 this.listeners[key] = this.listeners[key].filter(value => value.context !== context);
311 });
312 }
313 /**
314 * Removes all registered listeners of the given event name
315 * @param {String} name Event name
316 */
317
318
319 offByName(name) {
320 if (this.listeners[name]) {
321 this.listeners[name] = undefined;
322 }
323 }
324 /**
325 * Prepends event prefix to event name
326 * @param {String} name event name
327 */
328
329
330 _transformName(name) {
331 if (this.eventPrefix && this.eventPrefix.length) {
332 return this.eventPrefix + name.slice(0, 1).toUpperCase() + name.slice(1);
333 }
334
335 return name;
336 }
337
338 }
339
340 // This module contains all global variables
341 const prefix = 'ms';
342 const isTouch = ('ontouchstart' in document);
343 const has$1 = Object.prototype.hasOwnProperty;
344
345 const breakpoints = {
346 phone: 480,
347 tablet: 768
348 };
349 const breakpointNames = Object.keys(breakpoints).sort((a, b) => breakpoints[b] - breakpoints[a]);
350 /**
351 *
352 * @returns {name: string, index:number, size:number} current breakpoint info
353 */
354
355 const findBreakpoint = () => {
356 const refWidth = window.innerWidth;
357 let currentBreakpoint = null;
358 let currentBreakpointIndex = -1;
359 [...breakpointNames].reverse().some((breakpoint, index) => {
360 if (refWidth <= breakpoints[breakpoint]) {
361 currentBreakpoint = breakpoint;
362 currentBreakpointIndex = breakpointNames.length - index - 1;
363 return true;
364 }
365
366 return false;
367 });
368 return {
369 name: currentBreakpoint,
370 index: currentBreakpointIndex,
371 size: breakpoints[currentBreakpoint] || refWidth
372 };
373 };
374
375 class ResponsiveHelperClass extends Emitter {
376 constructor() {
377 super();
378 this.update = this.update.bind(this);
379 window.addEventListener('resize', this.update);
380 this.activeBreakpoint = null;
381 this.activeBreakpointIndex = null;
382 this.activeBreakpointSize = null;
383 this.update();
384 }
385
386 update(event) {
387 const delayBeforeResize = 20;
388
389 if (event && delayBeforeResize > 0) {
390 clearTimeout(this._resizeTimeout);
391 this._resizeTimeout = setTimeout(this.update, delayBeforeResize);
392 return;
393 }
394
395 const {
396 name,
397 index,
398 size
399 } = findBreakpoint();
400
401 if (name !== this.activeBreakpoint) {
402 this.activeBreakpoint = name;
403 this.activeBreakpointIndex = index;
404 this.activeBreakpointSize = size;
405 this.trigger('breakpointChange', [name, index, size]);
406 }
407 }
408
409 }
410
411 const responsiveHelper = new ResponsiveHelperClass();
412 /**
413 * Returns the value that is related to the current breakpoint, the given value can be an object which each breakpoint
414 * value defines by a breakpoint name or an array sorted based based on breakpoint sizes order.
415 *
416 * @param {Object|Array|Observable} values Any value to check with active breakpoint, it can be an array or an object
417 * @param {String} breakpoint Overrides active breakpoint name
418 */
419
420 const getResponsiveValue = (values, breakpoint) => {
421 if (!breakpoint) {
422 breakpoint = findBreakpoint().name;
423 }
424
425 const breakpointIndex = breakpointNames.indexOf(breakpoint);
426
427 if (Array.isArray(values)) {
428 if (values.length === 0) {
429 return undefined;
430 }
431
432 const value = values[breakpointIndex + 1];
433
434 if (!value || typeof value === 'string' && !value.length) {
435 if (breakpoint === 'none') {
436 return undefined;
437 }
438
439 return getResponsiveValue(values, breakpointIndex >= 1 ? breakpointNames[breakpointIndex - 1] : 'none');
440 }
441
442 return value;
443 } // observable object
444
445
446 if (has$1.call(values, 'toObject')) {
447 values = values.toObject();
448 }
449
450 if (typeof values === 'object') {
451 if (has$1.call(values, breakpoint)) {
452 return values[breakpoint];
453 }
454
455 if (breakpoint === 'none') {
456 return undefined;
457 }
458
459 return getResponsiveValue(values, breakpointIndex >= 1 ? breakpointNames[breakpointIndex - 1] : 'none');
460 }
461
462 return values;
463 };
464 /**
465 * Reads all breakpoint related attribute values and creates an object from them
466 * @param {Element} element Target element
467 * @param {String} attribute Element data attribute name
468 */
469
470 const getAttrValues = (element, attribute) => {
471 const bps = {};
472
473 if (element.hasAttribute(`data-${attribute}`)) {
474 bps.none = element.getAttribute(`data-${attribute}`);
475 }
476
477 breakpointNames.forEach(name => {
478 if (element.hasAttribute(`data-${name}-${attribute}`)) {
479 bps[name] = element.getAttribute(`data-${name}-${attribute}`);
480 }
481 });
482 return bps;
483 };
484 const addHideOn = (element, bps, callback, className = 'ms-hidden') => {
485 const update = (action, bp) => {
486 if (bp === null) {
487 bp = 'desktop';
488 }
489
490 if (bps.includes(bp)) {
491 if (callback) callback(true);
492 element.classList.add(className);
493 } else {
494 if (callback) callback(false);
495 element.classList.remove(className);
496 }
497 };
498
499 update('', findBreakpoint().name);
500 responsiveHelper.on('breakpointChange', update);
501 };
502 /**
503 * Watches a set of responsive values and calls the callback function with the new active value upon breakpoint changes.
504 * @param {Array | string} from An array or a comma separated string of responsive values
505 * @param {Function} callback Watch the active breakpoint
506 */
507
508 const watchResponsiveValue = (from, callback) => {
509 let values = from;
510
511 if (Array.isArray(from)) {
512 if (from.length === 1) {
513 callback(from[0]);
514 return;
515 }
516
517 values = from.slice();
518 } else if (typeof from === 'string' && from.includes(',')) {
519 values = from.split(',').map(v => v.trim());
520 } else {
521 callback(values);
522 return;
523 }
524
525 let lastValue = undefined;
526
527 const check = (action, breakpoint) => {
528 const value = getResponsiveValue(values, breakpoint);
529
530 if (value !== lastValue) {
531 lastValue = value;
532 callback(value);
533 }
534 };
535
536 responsiveHelper.on('breakpointChange', check);
537 check('', responsiveHelper.activeBreakpoint);
538 };
539 /**
540 * Watches multiple responsive values and calls the callback function upon the breakpoint changes
541 * @param {Array} from An array of multiple responsive values
542 * @param {Function} callback The callback function that calls whenever on of the responsive values changes
543 */
544
545 const watchMultipleResponsiveValues = (from, callback) => {
546 const result = [];
547 let timeOut;
548
549 const callTheCB = () => {
550 clearTimeout(timeOut);
551 timeOut = setTimeout(() => {
552 callback(result);
553 }, 1);
554 };
555
556 from.forEach((value, index) => watchResponsiveValue(value, val => {
557 result[index] = val;
558 callTheCB();
559 }));
560 };
561
562 /**
563 * This class is responsible to control the content layout. It defines all needed layout options,
564 * adds required containers for UI controls, resizes the view and finds active breakpoint.
565 */
566
567 class LayoutController {
568 /**
569 * Creates new layout controller instance
570 * @param {Composer} composer Master Composer instance
571 * @param {DomView} view
572 * @param {Observable} options
573 */
574 constructor(composer, view, options) {
575 this.composer = composer;
576 this.options = options;
577 this.view = view;
578 this.innerContainers = {};
579 this.outerContainers = {};
580 this._matchHeightList = [];
581 this.options.register({
582 layout: 'boxed',
583 // fullscreen, auto, fullwidth
584 stretchWidth: false,
585 width: 900,
586 height: 500,
587 columns: 1,
588 rtl: false,
589 keepAspectRatio: true,
590 delayBeforeResize: 0,
591 // sizingReference: 'box',
592 fullscreenMargin: 0,
593 narrowLayoutOn: 'phone',
594 autoHeight: false,
595 overflowFix: true
596 });
597 this.primaryContainer = document.createElement('div');
598 this.primaryContainer.classList.add(`${prefix}-primary-container`);
599 this.composer.element.appendChild(this.primaryContainer); // wrap view element
600
601 this.viewContainer = document.createElement('div');
602 this.viewContainer.classList.add(`${prefix}-view-container`);
603 this.view.appendTo(this.viewContainer);
604 this.primaryContainer.appendChild(this.viewContainer); // update view reverse option and container class name
605
606 if (this.view.options.has('reverse')) {
607 const isRTL = this.options.get('rtl');
608 this.view.options.set('reverse', isRTL);
609
610 if (isRTL) {
611 this.composer.element.classList.add(`${prefix}-rtl`);
612 }
613
614 this.options.observe('rtl', (name, value) => {
615 this.view.options.set('reverse', value);
616 this.composer.element.classList[value ? 'add' : 'remove'](`${prefix}-rtl`);
617 });
618 }
619
620 this.update = this.update.bind(this);
621 window.addEventListener('resize', this.update, false);
622 this.update();
623 }
624 /**
625 * Updates the content layout
626 * @param {Event} event Resize event object [optional]
627 */
628
629
630 update(event) {
631 const delayBeforeResize = this.options.get('delayBeforeResize');
632
633 if (event && delayBeforeResize > 0) {
634 clearTimeout(this._resizeTimeout);
635 this._resizeTimeout = setTimeout(this.update, delayBeforeResize);
636 return;
637 }
638
639 const options = this.options.get(['layout', 'width', 'height', 'maxHeight', 'minHeight', 'keepAspectRatio', 'autoHeight', 'fullscreenMargin', 'overflowFix', 'narrowLayoutOn']);
640 const composerContainer = this.composer.element;
641 composerContainer.classList.add(`${prefix}-layout-${options.layout}`);
642 const {
643 name: breakpoint,
644 size: breakpointSize
645 } = findBreakpoint();
646
647 if (breakpoint !== this.activeBreakpoint) {
648 if (this.activeBreakpoint) {
649 this.composer.element.classList.remove(`${prefix}-bp-${this.activeBreakpoint}`);
650 }
651
652 this.activeBreakpoint = breakpoint;
653
654 if (breakpoint !== null) {
655 this.composer.element.classList.add(`${prefix}-bp-${breakpoint}`);
656 }
657
658 this.activeBreakpointSize = breakpoint ? breakpointSize : getResponsiveValue(options.width, breakpoint);
659 } // is it narrow?
660
661
662 this.isNarrow = breakpoint === options.narrowLayoutOn;
663
664 if (this._lastNarrowStatus !== this.isNarrow) {
665 if (this.isNarrow) {
666 composerContainer.classList.add(`${prefix}-narrow-layout`);
667 } else {
668 composerContainer.classList.remove(`${prefix}-narrow-layout`);
669 }
670
671 this._lastNarrowStatus = this.isNarrow;
672 } // set width
673
674
675 switch (options.layout) {
676 case 'fullscreen':
677 if (options.overflowFix) {
678 document.body.classList.add(`${prefix}-overflow-fix`);
679 }
680
681 case 'fullwidth':
682 composerContainer.style.width = document.body.clientWidth + 'px';
683 setTimeout(() => {
684 composerContainer.style.width = document.body.clientWidth + 'px';
685 }); // fix unexpected horizontal scroll
686
687 composerContainer.style.marginLeft = '';
688 composerContainer.style.marginLeft = -composerContainer.offsetLeft + 'px';
689 break;
690
691 case 'boxed':
692 composerContainer.style.maxWidth = getResponsiveValue(options.width, breakpoint) + 'px';
693
694 }
695
696 const width = composerContainer.offsetWidth; // auto height
697
698 if (options.autoHeight) {
699 options.height = 'auto';
700 }
701
702 this.autoHeight = options.height === 'auto';
703
704 if (!this.autoHeight || options.layout === 'fullscreen') {
705 let height = getResponsiveValue(options.height, breakpoint);
706
707 if (options.keepAspectRatio) {
708 height *= width / this.activeBreakpointSize;
709 } // set height
710
711
712 switch (options.layout) {
713 case 'fullscreen':
714 if (options.fullscreenMargin) {
715 composerContainer.style.height = `calc( 100vh - ${options.fullscreenMargin}px )`;
716 } else {
717 composerContainer.style.height = '100vh';
718 }
719
720 break;
721
722 case 'fullwidth':
723 case 'boxed':
724 composerContainer.style.height = height + 'px';
725
726 }
727 }
728
729 this.composer.trigger('beforeViewResize');
730 this.view.resize();
731
732 this._updateMatchHeights();
733
734 if (width !== this.width || this.height !== composerContainer.offMatchHeight) {
735 this.width = width;
736 this.height = composerContainer.offsetHeight;
737 this.composer.trigger('resize');
738 }
739
740 this.composer.trigger('layoutUpdate');
741 }
742 /**
743 * Returns the related container based on given area
744 * @param {String} area The target area
745 */
746
747
748 getContainer(area) {
749 if (typeof area !== 'string') {
750 return false;
751 }
752
753 area = area.toLowerCase();
754 const isInner = area.indexOf('inner') !== -1;
755 const alignment = area.replace('inner', '');
756 const containers = isInner ? this.innerContainers : this.outerContainers;
757
758 if (!has$1.call(containers, alignment)) {
759 this._createContainer(alignment, isInner);
760 }
761
762 return containers[alignment];
763 }
764 /**
765 * Matches element height with the view height
766 * @param {Element} element
767 */
768
769
770 onMatchHeight(element) {
771 this._matchHeightList.push(element);
772
773 this._updateMatchHeights();
774 }
775 /**
776 * Disables the match height feature from element
777 * @param {Element} element
778 */
779
780
781 offMatchHeight(element) {
782 element.style.height = '';
783
784 this._matchHeightList.splice(this._matchHeightList.indexOf(element), 1);
785 }
786 /**
787 * Updates all registered element in match height list
788 * @private
789 */
790
791
792 _updateMatchHeights() {
793 this._matchHeightList.forEach(element => {
794 element.style.height = this.slider.view.height + 'px';
795 });
796 }
797 /**
798 * Creates new container
799 * @private
800 *
801 * @param {String} alignment
802 * @param {Boolean} isInner
803 */
804
805
806 _createContainer(alignment, isInner) {
807 const container = document.createElement('div');
808 container.classList.add(`${prefix}-${alignment}-container`);
809
810 if (isInner) {
811 if (!this.hasInnerBox) {
812 this.hasInnerBox = true;
813 this.innerBox = document.createElement('div');
814 this.innerBox.classList.add(`${prefix}-inner-container`);
815 this.innerBox.appendChild(this.viewContainer);
816 (this.hasMidRow ? this.midRow : this.primaryContainer).appendChild(this.innerBox);
817 }
818
819 this.innerContainers[alignment] = container;
820
821 if (alignment === 'right' || alignment === 'left') {
822 if (!this.hasInnerMidRow) {
823 this.hasInnerMidRow = true;
824 this.innerMidRow = document.createElement('div');
825 this.innerMidRow.classList.add(`${prefix}-mid-row`);
826 this.innerMidRow.appendChild(this.viewContainer);
827 this.innerBox.appendChild(this.innerMidRow);
828 }
829
830 this.innerMidRow.appendChild(container);
831 } else {
832 this.innerBox.appendChild(container);
833 }
834 } else {
835 this.outerContainers[alignment] = container;
836
837 if (alignment === 'right' || alignment === 'left') {
838 if (!this.hasMidRow) {
839 this.hasMidRow = true;
840 this.midRow = document.createElement('div');
841 this.midRow.classList.add(`${prefix}-mid-row`);
842 this.midRow.appendChild(this.hasInnerBox ? this.innerBox : this.viewContainer);
843 this.primaryContainer.appendChild(this.midRow);
844 }
845
846 this.midRow.appendChild(container);
847 } else {
848 this.primaryContainer.appendChild(container);
849 }
850 }
851
852 this.update();
853 }
854
855 }
856
857 /**
858 * This class triggers the given action callback after all dependencies get done.
859 * To add a new dependency call `hold` method and after the dependency resolved call `exec`
860 */
861 class ActionTrigger {
862 /**
863 * Creates new action trigger instance
864 * @param {Function} action
865 * @param {boolean} noMoreExec
866 */
867 constructor(action, noMoreExec = true) {
868 this._dependencies = 1;
869 this.action = action;
870 this.noMoreExec = noMoreExec;
871 }
872 /**
873 * Adds new dependency
874 */
875
876
877 hold() {
878 this._dependencies += 1;
879 }
880 /**
881 * Charges trigger for given value
882 * @param {Number} times
883 */
884
885
886 charge(times) {
887 this._dependencies += times;
888 }
889 /**
890 * One dependency resolved, it automatically calls the action after all dependencies
891 */
892
893
894 exec() {
895 if (this._executed) {
896 if (this.noMoreExec) {
897 throw new Error('The action is triggered before.');
898 } else {
899 return true;
900 }
901 }
902
903 this._dependencies -= 1;
904
905 if (this._dependencies <= 0) {
906 this._executed = true;
907 this.action();
908 return true;
909 }
910
911 return false;
912 }
913
914 }
915
916 /**
917 * Converts string value to an array
918 * @param {String} value
919 * @param {Boolean} numbers Whether parse values to number or not
920 */
921 function toArray$1(value, numbers = true) {
922 if (typeof value !== 'string') {
923 return value;
924 }
925
926 value = value.replace(/\s+/g, '').split(',');
927
928 if (numbers) {
929 value = value.map(val => Number.parseInt(val, 10));
930 }
931
932 return value;
933 }
934
935 !function () {
936
937 if ("undefined" != typeof window) {
938 var t = window.navigator.userAgent.match(/Edge\/(\d{2})\./),
939 e = t ? parseInt(t[1], 10) : null,
940 n = !!e && 16 <= e && e <= 18;
941
942 if (!("objectFit" in document.documentElement.style != !1) || n) {
943 var o = function (t, e, i) {
944 var n, o, l, a, d;
945 if ((i = i.split(" ")).length < 2 && (i[1] = i[0]), "x" === t) n = i[0], o = i[1], l = "left", a = "right", d = e.clientWidth;else {
946 if ("y" !== t) return;
947 n = i[1], o = i[0], l = "top", a = "bottom", d = e.clientHeight;
948 }
949
950 if (n !== l && o !== l) {
951 if (n !== a && o !== a) return "center" === n || "50%" === n ? (e.style[l] = "50%", void (e.style["margin-" + l] = d / -2 + "px")) : void (0 <= n.indexOf("%") ? (n = parseInt(n, 10)) < 50 ? (e.style[l] = n + "%", e.style["margin-" + l] = d * (n / -100) + "px") : (n = 100 - n, e.style[a] = n + "%", e.style["margin-" + a] = d * (n / -100) + "px") : e.style[l] = n);
952 e.style[a] = "0";
953 } else e.style[l] = "0";
954 },
955 l = function (t) {
956 var e = t.dataset ? t.dataset.objectFit : t.getAttribute("data-object-fit"),
957 i = t.dataset ? t.dataset.objectPosition : t.getAttribute("data-object-position");
958 e = e || "cover", i = i || "50% 50%";
959 var n = t.parentNode;
960 return function (t) {
961 var e = window.getComputedStyle(t, null),
962 i = e.getPropertyValue("position"),
963 n = e.getPropertyValue("overflow"),
964 o = e.getPropertyValue("display");
965 i && "static" !== i || (t.style.position = "relative"), "hidden" !== n && (t.style.overflow = "hidden"), o && "inline" !== o || (t.style.display = "block"), 0 === t.clientHeight && (t.style.height = "100%"), -1 === t.className.indexOf("object-fit-polyfill") && (t.className = t.className + " object-fit-polyfill");
966 }(n), function (t) {
967 var e = window.getComputedStyle(t, null),
968 i = {
969 "max-width": "none",
970 "max-height": "none",
971 "min-width": "0px",
972 "min-height": "0px",
973 top: "auto",
974 right: "auto",
975 bottom: "auto",
976 left: "auto",
977 "margin-top": "0px",
978 "margin-right": "0px",
979 "margin-bottom": "0px",
980 "margin-left": "0px"
981 };
982
983 for (var n in i) e.getPropertyValue(n) !== i[n] && (t.style[n] = i[n]);
984 }(t), t.style.position = "absolute", t.style.width = "auto", t.style.height = "auto", "scale-down" === e && (e = t.clientWidth < n.clientWidth && t.clientHeight < n.clientHeight ? "none" : "contain"), "none" === e ? (o("x", t, i), void o("y", t, i)) : "fill" === e ? (t.style.width = "100%", t.style.height = "100%", o("x", t, i), void o("y", t, i)) : (t.style.height = "100%", void ("cover" === e && t.clientWidth > n.clientWidth || "contain" === e && t.clientWidth < n.clientWidth ? (t.style.top = "0", t.style.marginTop = "0", o("x", t, i)) : (t.style.width = "100%", t.style.height = "auto", t.style.left = "0", t.style.marginLeft = "0", o("y", t, i))));
985 },
986 i = function (t) {
987 if (void 0 === t || t instanceof Event) t = document.querySelectorAll("[data-object-fit]");else if (t && t.nodeName) t = [t];else {
988 if ("object" != typeof t || !t.length || !t[0].nodeName) return !1;
989 t = t;
990 }
991
992 for (var e = 0; e < t.length; e++) if (t[e].nodeName) {
993 var i = t[e].nodeName.toLowerCase();
994
995 if ("img" === i) {
996 if (n) continue;
997 t[e].complete ? l(t[e]) : t[e].addEventListener("load", function () {
998 l(this);
999 });
1000 } else "video" === i ? 0 < t[e].readyState ? l(t[e]) : t[e].addEventListener("loadedmetadata", function () {
1001 l(this);
1002 }) : l(t[e]);
1003 }
1004
1005 return !0;
1006 };
1007
1008 "loading" === document.readyState ? document.addEventListener("DOMContentLoaded", i) : i(), window.addEventListener("resize", i), window.objectFitPolyfill = i;
1009 } else window.objectFitPolyfill = function () {
1010 return !1;
1011 };
1012 }
1013 }();
1014
1015 /**
1016 * Adds object fit and position values as style and data attribute
1017 * @param {Element} element Target element
1018 * @param {String} fit Object fit value
1019 * @param {String} position Object position value
1020 * @param {Boolean} readAttribute Whether check object fit and position of element or not.
1021 * @param {Boolean} overrideAttributes Whether to override attributes with the given position and fit values
1022 */
1023
1024 function objectFit(element, fit, position, readAttribute = true, overrideAttributes = false) {
1025 if (readAttribute && element.hasAttribute('data-object-fit')) {
1026 fit = element.getAttribute('data-object-fit');
1027 }
1028
1029 if (fit === 'tile' && element.nodeName === 'IMG') {
1030 element.style.visibility = 'hidden';
1031 element.parentElement.style.backgroundImage = `url( ${element.getAttribute('data-src') || element.src})`;
1032 return;
1033 }
1034
1035 if (!element.hasAttribute('data-object-fit') || overrideAttributes) {
1036 element.setAttribute('data-object-fit', fit);
1037 }
1038
1039 element.style.objectFit = fit;
1040
1041 if (readAttribute && element.hasAttribute('data-object-position')) {
1042 position = element.getAttribute('data-object-position');
1043 }
1044
1045 if (position) {
1046 if (!element.hasAttribute('data-object-position') || overrideAttributes) {
1047 element.setAttribute('data-object-position', position);
1048 }
1049
1050 element.style.objectPosition = position;
1051 }
1052
1053 if (window.objectFitPolyfill) {
1054 window.objectFitPolyfill(element);
1055 }
1056 }
1057 const responsiveObjectFit = (element, defaultFit, defaultPosition = '50% 50%') => {
1058 const {
1059 objectFit: objectFitAttr = defaultFit,
1060 objectPosition = defaultPosition
1061 } = element.dataset;
1062 const objectFitArr = objectFitAttr.split(',').map(v => v.trim());
1063 objectPosition.split(',').map(v => v.trim());
1064
1065 const update = (action, breakpoint) => {
1066 objectFit(element, getResponsiveValue(objectFitArr, breakpoint), getResponsiveValue(objectPosition, breakpoint), false, true);
1067 };
1068
1069 responsiveHelper.on('breakpointChange', update);
1070 update('', responsiveHelper.activeBreakpoint);
1071 };
1072
1073 /**
1074 * Replaces image element src and srcset attributes values with related data values beside adding load and error event
1075 * @param {Element} element Target image element
1076 * @param {Function} onLoad On load event listener
1077 * @param {Function} onError On error event listener
1078 */
1079 function loadImage(element, onLoad, onError) {
1080 if (element.hasAttribute('data-srcset')) {
1081 element.setAttribute('srcset', element.getAttribute('data-srcset'));
1082 element.removeAttribute('data-srcset');
1083 }
1084
1085 if (element.hasAttribute('data-src')) {
1086 element.setAttribute('src', element.getAttribute('data-src'));
1087 element.removeAttribute('data-src');
1088 }
1089
1090 if (element.complete) {
1091 if (onLoad || onError) {
1092 if (element.naturalWidth && onLoad) {
1093 onLoad();
1094 } else if (onError) {
1095 onError();
1096 }
1097 }
1098
1099 return;
1100 }
1101
1102 if (onLoad) {
1103 element.addEventListener('load', onLoad, false);
1104 }
1105
1106 if (onError) {
1107 element.addEventListener('error', onError, false);
1108 }
1109 }
1110
1111 /**
1112 * Section class, it can contain layers, background image, video etc.
1113 */
1114
1115 class Section extends Emitter {
1116 /**
1117 * Creates new section
1118 * @param {Element} element Section element
1119 * @param {Composer} composer
1120 */
1121 constructor(element, composer) {
1122 super();
1123 this.element = element;
1124 this.composer = composer;
1125 this.view = composer.view;
1126 this.space = 0;
1127 this.merge = 1;
1128 this.id = element.id;
1129 this.targetHeight = element.dataset.wrapperHeight ? element.dataset.wrapperHeight.split(',') : composer.options.get('height'); // add event prefix
1130
1131 this.eventPrefix = 'section';
1132 this.parentEmitter = this.composer; // initial values
1133
1134 this.position = -1;
1135 this.offset = -1;
1136 this.size = 0;
1137
1138 if (this.element.hasAttribute('data-merge')) {
1139 this.merge = toArray$1(this.element.getAttribute('data-merge'));
1140 }
1141
1142 this.trigger('sectionCreate', [this], true);
1143 this.readyTrigger = new ActionTrigger(this.ready.bind(this));
1144 this.loadTrigger = new ActionTrigger(this.loadContent.bind(this), false);
1145 this._active = false; // setup background
1146
1147 this._setupBackground();
1148 }
1149 /**
1150 * Gets section active value
1151 */
1152
1153
1154 get active() {
1155 return this._active;
1156 }
1157 /**
1158 * Sets section active value
1159 */
1160
1161
1162 set active(value) {
1163 if (this._active !== value) {
1164 this._active = value;
1165 this.element.classList[value ? 'add' : 'remove'](`${prefix}-active`);
1166 this.trigger(value ? 'activated' : 'deactivated', [this], true);
1167
1168 if (this.isReady) {
1169 this.trigger(value ? 'readyAndActivated' : 'readyAndDeactivated', [this], true);
1170 }
1171 }
1172 }
1173 /**
1174 * Gets current status
1175 */
1176
1177
1178 get status() {
1179 return this._status;
1180 }
1181 /**
1182 * Sets status
1183 */
1184
1185
1186 set status(value) {
1187 if (value === this._status) {
1188 return;
1189 } // add and remove status class names
1190
1191
1192 this.element.classList.add(`${prefix}-${value}`);
1193
1194 if (this._status) {
1195 this.element.classList.remove(`${prefix}-${this._status}`);
1196 }
1197
1198 const oldValue = this._status;
1199 this._status = value;
1200 this.trigger('statusChange', [this, value, oldValue], true);
1201 }
1202 /**
1203 * Gets pending offset value
1204 */
1205
1206
1207 get pendingOffset() {
1208 return this._pendingOffset;
1209 }
1210 /**
1211 * Sets pending offset
1212 * Appear offset indicates the section portion length that is located out side of view. The value is 0 when section is entirely visible.
1213 */
1214
1215
1216 set pendingOffset(value) {
1217 if (value !== this._pendingOffset) {
1218 this._pendingOffset = value;
1219 this.trigger('pendingOffsetChange', [this, value, value / this.size]);
1220 }
1221 }
1222
1223 triggerPendingOffsetChange() {
1224 this.trigger('pendingOffsetChange', [this, this._pendingOffset, this._pendingOffset / this.size]);
1225 }
1226 /**
1227 * Calculates the sections size based on columns number and direction
1228 */
1229
1230
1231 calculateSize() {
1232 let columns = this.composer.options.get('columns');
1233 let merge = getResponsiveValue(this.merge);
1234 const isHorizontal = this.view.options.is('dir', 'h');
1235 const sizeReference = isHorizontal ? 'offsetWidth' : 'offsetHeight';
1236
1237 if (!columns) {
1238 this.size = this.element[sizeReference] + this.space;
1239 } else {
1240 columns = getResponsiveValue(columns);
1241 let noSpaceSize = this.view.size - this.space * (columns - 1);
1242 this.size = noSpaceSize / columns + this.space;
1243
1244 if (merge > 1) {
1245 merge = Math.min(columns, merge);
1246 this.size = this.size * merge + this.space * (merge - 1);
1247 noSpaceSize += this.space * (merge - 1);
1248 }
1249 }
1250
1251 this.autoHeight = this.composer.options.get('autoHeight');
1252
1253 if (isHorizontal) {
1254 this.element.style.width = this.size - this.space + 'px';
1255
1256 if (!this.autoHeight) {
1257 this.element.style.height = this.view.height + 'px';
1258 } else {
1259 this.element.style.height = getResponsiveValue(this.targetHeight) + 'px';
1260 }
1261 } else {
1262 this.element.style.width = this.view.width + 'px';
1263 this.element.style.height = this.size - this.space + 'px';
1264 }
1265
1266 this.checkResize();
1267 }
1268 /**
1269 * Checks wether the given value is located in side the section or not.
1270 * This method is used by scroll views
1271 * @param {Number} value
1272 */
1273
1274
1275 inRangeTest(value) {
1276 return value >= this.position && value < this.position + this.size;
1277 }
1278 /**
1279 * Tells the section that is has mounted
1280 */
1281
1282
1283 mount() {
1284 if (this.firstMount !== false) {
1285 this.firstMount = true;
1286 } else {
1287 this.firstMount = false;
1288 }
1289
1290 this.trigger('beforeMount', [this], true);
1291 this.mounted = true;
1292
1293 if (!this.isReady && !this.isLoading) {
1294 this.loadTrigger.exec();
1295 }
1296
1297 this.trigger('afterMount', [this], true);
1298 }
1299 /**
1300 * Tells the section that is has unmounted
1301 */
1302
1303
1304 unmount() {
1305 this.mounted = false;
1306 }
1307 /**
1308 * On section gets ready, usually after loading all contents
1309 */
1310
1311
1312 ready() {
1313 this.element.classList.add(`${prefix}-ready`);
1314 this.isReady = true;
1315 this.isLoading = false;
1316 this.trigger('ready');
1317
1318 if (this._active) {
1319 this.trigger('readyAndActivated', [this], true);
1320 }
1321 }
1322 /**
1323 * Starts loading content
1324 */
1325
1326
1327 loadContent() {
1328 this.isLoading = true;
1329 this.trigger('loadingStart', [this], true);
1330
1331 if (this.backgroundImage) {
1332 this._onBgLoad = this._onBgLoad.bind(this);
1333 loadImage(this.backgroundImage, this._onBgLoad, this._onBgLoad);
1334 } else {
1335 this.readyTrigger.exec();
1336 }
1337 }
1338 /**
1339 * Checks section width and height and triggers resize event
1340 * @param {Boolean} force
1341 */
1342
1343
1344 checkResize(force) {
1345 const width = this.element.offsetWidth;
1346 const height = this.element.offsetHeight;
1347
1348 if (force || this.height !== height || this.width !== width) {
1349 this.width = width;
1350 this.height = height;
1351 this.trigger('resize', [this, width, height], true);
1352 }
1353 }
1354 /**
1355 * Setups the section background
1356 * @private
1357 */
1358
1359
1360 _setupBackground() {
1361 this.backgroundImage = this.element.querySelector(`:scope > img.${prefix}-bg`);
1362
1363 if (!this.backgroundImage) {
1364 return;
1365 } // Section background container
1366
1367
1368 this.bgImageCont = document.createElement('div');
1369 this.bgImageCont.classList.add(`${prefix}-bg-container`);
1370 this.bgImageCont.appendChild(this.backgroundImage);
1371 this.element.appendChild(this.bgImageCont); // objectFit(this.backgroundImage, this.composer.options.get('sectionFit'));
1372
1373 responsiveObjectFit(this.backgroundImage, this.composer.options.get('sectionFit'));
1374 this.trigger('bgImageSetup', [this.backgroundImage], true);
1375 }
1376 /**
1377 * After background load
1378 */
1379
1380
1381 _onBgLoad() {
1382 this.trigger('bgImageLoad', [this], true);
1383
1384 if (!this._bgLoaded) {
1385 this.readyTrigger.exec();
1386 }
1387
1388 this._bgLoaded = true;
1389
1390 if (this.autoHeight) {
1391 this.checkResize();
1392 }
1393 }
1394
1395 }
1396
1397 /*
1398 * anime.js v3.2.1
1399 * (c) 2020 Julian Garnier
1400 * Released under the MIT license
1401 * animejs.com
1402 */
1403 // Defaults
1404 var defaultInstanceSettings = {
1405 update: null,
1406 begin: null,
1407 loopBegin: null,
1408 changeBegin: null,
1409 change: null,
1410 changeComplete: null,
1411 loopComplete: null,
1412 complete: null,
1413 loop: 1,
1414 direction: 'normal',
1415 autoplay: true,
1416 timelineOffset: 0
1417 };
1418 var defaultTweenSettings = {
1419 duration: 1000,
1420 delay: 0,
1421 endDelay: 0,
1422 easing: 'easeOutElastic(1, .5)',
1423 round: 0
1424 };
1425 var validTransforms = ['translateX', 'translateY', 'translateZ', 'rotate', 'rotateX', 'rotateY', 'rotateZ', 'scale', 'scaleX', 'scaleY', 'scaleZ', 'skew', 'skewX', 'skewY', 'perspective', 'matrix', 'matrix3d']; // Caching
1426
1427 var cache = {
1428 CSS: {},
1429 springs: {}
1430 }; // Utils
1431
1432 function minMax(val, min, max) {
1433 return Math.min(Math.max(val, min), max);
1434 }
1435
1436 function stringContains(str, text) {
1437 return str.indexOf(text) > -1;
1438 }
1439
1440 function applyArguments(func, args) {
1441 return func.apply(null, args);
1442 }
1443
1444 var is = {
1445 arr: function (a) {
1446 return Array.isArray(a);
1447 },
1448 obj: function (a) {
1449 return stringContains(Object.prototype.toString.call(a), 'Object');
1450 },
1451 pth: function (a) {
1452 return is.obj(a) && a.hasOwnProperty('totalLength');
1453 },
1454 svg: function (a) {
1455 return a instanceof SVGElement;
1456 },
1457 inp: function (a) {
1458 return a instanceof HTMLInputElement;
1459 },
1460 dom: function (a) {
1461 return a.nodeType || is.svg(a);
1462 },
1463 str: function (a) {
1464 return typeof a === 'string';
1465 },
1466 fnc: function (a) {
1467 return typeof a === 'function';
1468 },
1469 und: function (a) {
1470 return typeof a === 'undefined';
1471 },
1472 nil: function (a) {
1473 return is.und(a) || a === null;
1474 },
1475 hex: function (a) {
1476 return /(^#[0-9A-F]{6}$)|(^#[0-9A-F]{3}$)/i.test(a);
1477 },
1478 rgb: function (a) {
1479 return /^rgb/.test(a);
1480 },
1481 hsl: function (a) {
1482 return /^hsl/.test(a);
1483 },
1484 col: function (a) {
1485 return is.hex(a) || is.rgb(a) || is.hsl(a);
1486 },
1487 key: function (a) {
1488 return !defaultInstanceSettings.hasOwnProperty(a) && !defaultTweenSettings.hasOwnProperty(a) && a !== 'targets' && a !== 'keyframes';
1489 }
1490 }; // Easings
1491
1492 function parseEasingParameters(string) {
1493 var match = /\(([^)]+)\)/.exec(string);
1494 return match ? match[1].split(',').map(function (p) {
1495 return parseFloat(p);
1496 }) : [];
1497 } // Spring solver inspired by Webkit Copyright © 2016 Apple Inc. All rights reserved. https://webkit.org/demos/spring/spring.js
1498
1499
1500 function spring(string, duration) {
1501 var params = parseEasingParameters(string);
1502 var mass = minMax(is.und(params[0]) ? 1 : params[0], .1, 100);
1503 var stiffness = minMax(is.und(params[1]) ? 100 : params[1], .1, 100);
1504 var damping = minMax(is.und(params[2]) ? 10 : params[2], .1, 100);
1505 var velocity = minMax(is.und(params[3]) ? 0 : params[3], .1, 100);
1506 var w0 = Math.sqrt(stiffness / mass);
1507 var zeta = damping / (2 * Math.sqrt(stiffness * mass));
1508 var wd = zeta < 1 ? w0 * Math.sqrt(1 - zeta * zeta) : 0;
1509 var a = 1;
1510 var b = zeta < 1 ? (zeta * w0 + -velocity) / wd : -velocity + w0;
1511
1512 function solver(t) {
1513 var progress = duration ? duration * t / 1000 : t;
1514
1515 if (zeta < 1) {
1516 progress = Math.exp(-progress * zeta * w0) * (a * Math.cos(wd * progress) + b * Math.sin(wd * progress));
1517 } else {
1518 progress = (a + b * progress) * Math.exp(-progress * w0);
1519 }
1520
1521 if (t === 0 || t === 1) {
1522 return t;
1523 }
1524
1525 return 1 - progress;
1526 }
1527
1528 function getDuration() {
1529 var cached = cache.springs[string];
1530
1531 if (cached) {
1532 return cached;
1533 }
1534
1535 var frame = 1 / 6;
1536 var elapsed = 0;
1537 var rest = 0;
1538
1539 while (true) {
1540 elapsed += frame;
1541
1542 if (solver(elapsed) === 1) {
1543 rest++;
1544
1545 if (rest >= 16) {
1546 break;
1547 }
1548 } else {
1549 rest = 0;
1550 }
1551 }
1552
1553 var duration = elapsed * frame * 1000;
1554 cache.springs[string] = duration;
1555 return duration;
1556 }
1557
1558 return duration ? solver : getDuration;
1559 } // Basic steps easing implementation https://developer.mozilla.org/fr/docs/Web/CSS/transition-timing-function
1560
1561
1562 function steps(steps) {
1563 if (steps === void 0) steps = 10;
1564 return function (t) {
1565 return Math.ceil(minMax(t, 0.000001, 1) * steps) * (1 / steps);
1566 };
1567 } // BezierEasing https://github.com/gre/bezier-easing
1568
1569
1570 var bezier = function () {
1571 var kSplineTableSize = 11;
1572 var kSampleStepSize = 1.0 / (kSplineTableSize - 1.0);
1573
1574 function A(aA1, aA2) {
1575 return 1.0 - 3.0 * aA2 + 3.0 * aA1;
1576 }
1577
1578 function B(aA1, aA2) {
1579 return 3.0 * aA2 - 6.0 * aA1;
1580 }
1581
1582 function C(aA1) {
1583 return 3.0 * aA1;
1584 }
1585
1586 function calcBezier(aT, aA1, aA2) {
1587 return ((A(aA1, aA2) * aT + B(aA1, aA2)) * aT + C(aA1)) * aT;
1588 }
1589
1590 function getSlope(aT, aA1, aA2) {
1591 return 3.0 * A(aA1, aA2) * aT * aT + 2.0 * B(aA1, aA2) * aT + C(aA1);
1592 }
1593
1594 function binarySubdivide(aX, aA, aB, mX1, mX2) {
1595 var currentX,
1596 currentT,
1597 i = 0;
1598
1599 do {
1600 currentT = aA + (aB - aA) / 2.0;
1601 currentX = calcBezier(currentT, mX1, mX2) - aX;
1602
1603 if (currentX > 0.0) {
1604 aB = currentT;
1605 } else {
1606 aA = currentT;
1607 }
1608 } while (Math.abs(currentX) > 0.0000001 && ++i < 10);
1609
1610 return currentT;
1611 }
1612
1613 function newtonRaphsonIterate(aX, aGuessT, mX1, mX2) {
1614 for (var i = 0; i < 4; ++i) {
1615 var currentSlope = getSlope(aGuessT, mX1, mX2);
1616
1617 if (currentSlope === 0.0) {
1618 return aGuessT;
1619 }
1620
1621 var currentX = calcBezier(aGuessT, mX1, mX2) - aX;
1622 aGuessT -= currentX / currentSlope;
1623 }
1624
1625 return aGuessT;
1626 }
1627
1628 function bezier(mX1, mY1, mX2, mY2) {
1629 if (!(0 <= mX1 && mX1 <= 1 && 0 <= mX2 && mX2 <= 1)) {
1630 return;
1631 }
1632
1633 var sampleValues = new Float32Array(kSplineTableSize);
1634
1635 if (mX1 !== mY1 || mX2 !== mY2) {
1636 for (var i = 0; i < kSplineTableSize; ++i) {
1637 sampleValues[i] = calcBezier(i * kSampleStepSize, mX1, mX2);
1638 }
1639 }
1640
1641 function getTForX(aX) {
1642 var intervalStart = 0;
1643 var currentSample = 1;
1644 var lastSample = kSplineTableSize - 1;
1645
1646 for (; currentSample !== lastSample && sampleValues[currentSample] <= aX; ++currentSample) {
1647 intervalStart += kSampleStepSize;
1648 }
1649
1650 --currentSample;
1651 var dist = (aX - sampleValues[currentSample]) / (sampleValues[currentSample + 1] - sampleValues[currentSample]);
1652 var guessForT = intervalStart + dist * kSampleStepSize;
1653 var initialSlope = getSlope(guessForT, mX1, mX2);
1654
1655 if (initialSlope >= 0.001) {
1656 return newtonRaphsonIterate(aX, guessForT, mX1, mX2);
1657 } else if (initialSlope === 0.0) {
1658 return guessForT;
1659 } else {
1660 return binarySubdivide(aX, intervalStart, intervalStart + kSampleStepSize, mX1, mX2);
1661 }
1662 }
1663
1664 return function (x) {
1665 if (mX1 === mY1 && mX2 === mY2) {
1666 return x;
1667 }
1668
1669 if (x === 0 || x === 1) {
1670 return x;
1671 }
1672
1673 return calcBezier(getTForX(x), mY1, mY2);
1674 };
1675 }
1676
1677 return bezier;
1678 }();
1679
1680 var penner = function () {
1681 // Based on jQuery UI's implemenation of easing equations from Robert Penner (http://www.robertpenner.com/easing)
1682 var eases = {
1683 linear: function () {
1684 return function (t) {
1685 return t;
1686 };
1687 }
1688 };
1689 var functionEasings = {
1690 Sine: function () {
1691 return function (t) {
1692 return 1 - Math.cos(t * Math.PI / 2);
1693 };
1694 },
1695 Circ: function () {
1696 return function (t) {
1697 return 1 - Math.sqrt(1 - t * t);
1698 };
1699 },
1700 Back: function () {
1701 return function (t) {
1702 return t * t * (3 * t - 2);
1703 };
1704 },
1705 Bounce: function () {
1706 return function (t) {
1707 var pow2,
1708 b = 4;
1709
1710 while (t < ((pow2 = Math.pow(2, --b)) - 1) / 11) {}
1711
1712 return 1 / Math.pow(4, 3 - b) - 7.5625 * Math.pow((pow2 * 3 - 2) / 22 - t, 2);
1713 };
1714 },
1715 Elastic: function (amplitude, period) {
1716 if (amplitude === void 0) amplitude = 1;
1717 if (period === void 0) period = .5;
1718 var a = minMax(amplitude, 1, 10);
1719 var p = minMax(period, .1, 2);
1720 return function (t) {
1721 return t === 0 || t === 1 ? t : -a * Math.pow(2, 10 * (t - 1)) * Math.sin((t - 1 - p / (Math.PI * 2) * Math.asin(1 / a)) * (Math.PI * 2) / p);
1722 };
1723 }
1724 };
1725 var baseEasings = ['Quad', 'Cubic', 'Quart', 'Quint', 'Expo'];
1726 baseEasings.forEach(function (name, i) {
1727 functionEasings[name] = function () {
1728 return function (t) {
1729 return Math.pow(t, i + 2);
1730 };
1731 };
1732 });
1733 Object.keys(functionEasings).forEach(function (name) {
1734 var easeIn = functionEasings[name];
1735 eases['easeIn' + name] = easeIn;
1736
1737 eases['easeOut' + name] = function (a, b) {
1738 return function (t) {
1739 return 1 - easeIn(a, b)(1 - t);
1740 };
1741 };
1742
1743 eases['easeInOut' + name] = function (a, b) {
1744 return function (t) {
1745 return t < 0.5 ? easeIn(a, b)(t * 2) / 2 : 1 - easeIn(a, b)(t * -2 + 2) / 2;
1746 };
1747 };
1748
1749 eases['easeOutIn' + name] = function (a, b) {
1750 return function (t) {
1751 return t < 0.5 ? (1 - easeIn(a, b)(1 - t * 2)) / 2 : (easeIn(a, b)(t * 2 - 1) + 1) / 2;
1752 };
1753 };
1754 });
1755 return eases;
1756 }();
1757
1758 function parseEasings(easing, duration) {
1759 if (is.fnc(easing)) {
1760 return easing;
1761 }
1762
1763 var name = easing.split('(')[0];
1764 var ease = penner[name];
1765 var args = parseEasingParameters(easing);
1766
1767 switch (name) {
1768 case 'spring':
1769 return spring(easing, duration);
1770
1771 case 'cubicBezier':
1772 return applyArguments(bezier, args);
1773
1774 case 'steps':
1775 return applyArguments(steps, args);
1776
1777 default:
1778 return applyArguments(ease, args);
1779 }
1780 } // Strings
1781
1782
1783 function selectString(str) {
1784 try {
1785 var nodes = document.querySelectorAll(str);
1786 return nodes;
1787 } catch (e) {
1788 return;
1789 }
1790 } // Arrays
1791
1792
1793 function filterArray(arr, callback) {
1794 var len = arr.length;
1795 var thisArg = arguments.length >= 2 ? arguments[1] : void 0;
1796 var result = [];
1797
1798 for (var i = 0; i < len; i++) {
1799 if (i in arr) {
1800 var val = arr[i];
1801
1802 if (callback.call(thisArg, val, i, arr)) {
1803 result.push(val);
1804 }
1805 }
1806 }
1807
1808 return result;
1809 }
1810
1811 function flattenArray(arr) {
1812 return arr.reduce(function (a, b) {
1813 return a.concat(is.arr(b) ? flattenArray(b) : b);
1814 }, []);
1815 }
1816
1817 function toArray(o) {
1818 if (is.arr(o)) {
1819 return o;
1820 }
1821
1822 if (is.str(o)) {
1823 o = selectString(o) || o;
1824 }
1825
1826 if (o instanceof NodeList || o instanceof HTMLCollection) {
1827 return [].slice.call(o);
1828 }
1829
1830 return [o];
1831 }
1832
1833 function arrayContains(arr, val) {
1834 return arr.some(function (a) {
1835 return a === val;
1836 });
1837 } // Objects
1838
1839
1840 function cloneObject(o) {
1841 var clone = {};
1842
1843 for (var p in o) {
1844 clone[p] = o[p];
1845 }
1846
1847 return clone;
1848 }
1849
1850 function replaceObjectProps(o1, o2) {
1851 var o = cloneObject(o1);
1852
1853 for (var p in o1) {
1854 o[p] = o2.hasOwnProperty(p) ? o2[p] : o1[p];
1855 }
1856
1857 return o;
1858 }
1859
1860 function mergeObjects(o1, o2) {
1861 var o = cloneObject(o1);
1862
1863 for (var p in o2) {
1864 o[p] = is.und(o1[p]) ? o2[p] : o1[p];
1865 }
1866
1867 return o;
1868 } // Colors
1869
1870
1871 function rgbToRgba(rgbValue) {
1872 var rgb = /rgb\((\d+,\s*[\d]+,\s*[\d]+)\)/g.exec(rgbValue);
1873 return rgb ? "rgba(" + rgb[1] + ",1)" : rgbValue;
1874 }
1875
1876 function hexToRgba(hexValue) {
1877 var rgx = /^#?([a-f\d])([a-f\d])([a-f\d])$/i;
1878 var hex = hexValue.replace(rgx, function (m, r, g, b) {
1879 return r + r + g + g + b + b;
1880 });
1881 var rgb = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
1882 var r = parseInt(rgb[1], 16);
1883 var g = parseInt(rgb[2], 16);
1884 var b = parseInt(rgb[3], 16);
1885 return "rgba(" + r + "," + g + "," + b + ",1)";
1886 }
1887
1888 function hslToRgba(hslValue) {
1889 var hsl = /hsl\((\d+),\s*([\d.]+)%,\s*([\d.]+)%\)/g.exec(hslValue) || /hsla\((\d+),\s*([\d.]+)%,\s*([\d.]+)%,\s*([\d.]+)\)/g.exec(hslValue);
1890 var h = parseInt(hsl[1], 10) / 360;
1891 var s = parseInt(hsl[2], 10) / 100;
1892 var l = parseInt(hsl[3], 10) / 100;
1893 var a = hsl[4] || 1;
1894
1895 function hue2rgb(p, q, t) {
1896 if (t < 0) {
1897 t += 1;
1898 }
1899
1900 if (t > 1) {
1901 t -= 1;
1902 }
1903
1904 if (t < 1 / 6) {
1905 return p + (q - p) * 6 * t;
1906 }
1907
1908 if (t < 1 / 2) {
1909 return q;
1910 }
1911
1912 if (t < 2 / 3) {
1913 return p + (q - p) * (2 / 3 - t) * 6;
1914 }
1915
1916 return p;
1917 }
1918
1919 var r, g, b;
1920
1921 if (s == 0) {
1922 r = g = b = l;
1923 } else {
1924 var q = l < 0.5 ? l * (1 + s) : l + s - l * s;
1925 var p = 2 * l - q;
1926 r = hue2rgb(p, q, h + 1 / 3);
1927 g = hue2rgb(p, q, h);
1928 b = hue2rgb(p, q, h - 1 / 3);
1929 }
1930
1931 return "rgba(" + r * 255 + "," + g * 255 + "," + b * 255 + "," + a + ")";
1932 }
1933
1934 function colorToRgb(val) {
1935 if (is.rgb(val)) {
1936 return rgbToRgba(val);
1937 }
1938
1939 if (is.hex(val)) {
1940 return hexToRgba(val);
1941 }
1942
1943 if (is.hsl(val)) {
1944 return hslToRgba(val);
1945 }
1946 } // Units
1947
1948
1949 function getUnit(val) {
1950 var split = /[+-]?\d*\.?\d+(?:\.\d+)?(?:[eE][+-]?\d+)?(%|px|pt|em|rem|in|cm|mm|ex|ch|pc|vw|vh|vmin|vmax|deg|rad|turn)?$/.exec(val);
1951
1952 if (split) {
1953 return split[1];
1954 }
1955 }
1956
1957 function getTransformUnit(propName) {
1958 if (stringContains(propName, 'translate') || propName === 'perspective') {
1959 return 'px';
1960 }
1961
1962 if (stringContains(propName, 'rotate') || stringContains(propName, 'skew')) {
1963 return 'deg';
1964 }
1965 } // Values
1966
1967
1968 function getFunctionValue(val, animatable) {
1969 if (!is.fnc(val)) {
1970 return val;
1971 }
1972
1973 return val(animatable.target, animatable.id, animatable.total);
1974 }
1975
1976 function getAttribute(el, prop) {
1977 return el.getAttribute(prop);
1978 }
1979
1980 function convertPxToUnit(el, value, unit) {
1981 var valueUnit = getUnit(value);
1982
1983 if (arrayContains([unit, 'deg', 'rad', 'turn'], valueUnit)) {
1984 return value;
1985 }
1986
1987 var cached = cache.CSS[value + unit];
1988
1989 if (!is.und(cached)) {
1990 return cached;
1991 }
1992
1993 var baseline = 100;
1994 var tempEl = document.createElement(el.tagName);
1995 var parentEl = el.parentNode && el.parentNode !== document ? el.parentNode : document.body;
1996 parentEl.appendChild(tempEl);
1997 tempEl.style.position = 'absolute';
1998 tempEl.style.width = baseline + unit;
1999 var factor = baseline / tempEl.offsetWidth;
2000 parentEl.removeChild(tempEl);
2001 var convertedUnit = factor * parseFloat(value);
2002 cache.CSS[value + unit] = convertedUnit;
2003 return convertedUnit;
2004 }
2005
2006 function getCSSValue(el, prop, unit) {
2007 if (prop in el.style) {
2008 var uppercasePropName = prop.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase();
2009 var value = el.style[prop] || getComputedStyle(el).getPropertyValue(uppercasePropName) || '0';
2010 return unit ? convertPxToUnit(el, value, unit) : value;
2011 }
2012 }
2013
2014 function getAnimationType(el, prop) {
2015 if (is.dom(el) && !is.inp(el) && (!is.nil(getAttribute(el, prop)) || is.svg(el) && el[prop])) {
2016 return 'attribute';
2017 }
2018
2019 if (is.dom(el) && arrayContains(validTransforms, prop)) {
2020 return 'transform';
2021 }
2022
2023 if (is.dom(el) && prop !== 'transform' && getCSSValue(el, prop)) {
2024 return 'css';
2025 }
2026
2027 if (el[prop] != null) {
2028 return 'object';
2029 }
2030 }
2031
2032 function getElementTransforms(el) {
2033 if (!is.dom(el)) {
2034 return;
2035 }
2036
2037 var str = el.style.transform || '';
2038 var reg = /(\w+)\(([^)]*)\)/g;
2039 var transforms = new Map();
2040 var m;
2041
2042 while (m = reg.exec(str)) {
2043 transforms.set(m[1], m[2]);
2044 }
2045
2046 return transforms;
2047 }
2048
2049 function getTransformValue(el, propName, animatable, unit) {
2050 var defaultVal = stringContains(propName, 'scale') ? 1 : 0 + getTransformUnit(propName);
2051 var value = getElementTransforms(el).get(propName) || defaultVal;
2052
2053 if (animatable) {
2054 animatable.transforms.list.set(propName, value);
2055 animatable.transforms['last'] = propName;
2056 }
2057
2058 return unit ? convertPxToUnit(el, value, unit) : value;
2059 }
2060
2061 function getOriginalTargetValue(target, propName, unit, animatable) {
2062 switch (getAnimationType(target, propName)) {
2063 case 'transform':
2064 return getTransformValue(target, propName, animatable, unit);
2065
2066 case 'css':
2067 return getCSSValue(target, propName, unit);
2068
2069 case 'attribute':
2070 return getAttribute(target, propName);
2071
2072 default:
2073 return target[propName] || 0;
2074 }
2075 }
2076
2077 function getRelativeValue(to, from) {
2078 var operator = /^(\*=|\+=|-=)/.exec(to);
2079
2080 if (!operator) {
2081 return to;
2082 }
2083
2084 var u = getUnit(to) || 0;
2085 var x = parseFloat(from);
2086 var y = parseFloat(to.replace(operator[0], ''));
2087
2088 switch (operator[0][0]) {
2089 case '+':
2090 return x + y + u;
2091
2092 case '-':
2093 return x - y + u;
2094
2095 case '*':
2096 return x * y + u;
2097 }
2098 }
2099
2100 function validateValue(val, unit) {
2101 if (is.col(val)) {
2102 return colorToRgb(val);
2103 }
2104
2105 if (/\s/g.test(val)) {
2106 return val;
2107 }
2108
2109 var originalUnit = getUnit(val);
2110 var unitLess = originalUnit ? val.substr(0, val.length - originalUnit.length) : val;
2111
2112 if (unit) {
2113 return unitLess + unit;
2114 }
2115
2116 return unitLess;
2117 } // getTotalLength() equivalent for circle, rect, polyline, polygon and line shapes
2118 // adapted from https://gist.github.com/SebLambla/3e0550c496c236709744
2119
2120
2121 function getDistance(p1, p2) {
2122 return Math.sqrt(Math.pow(p2.x - p1.x, 2) + Math.pow(p2.y - p1.y, 2));
2123 }
2124
2125 function getCircleLength(el) {
2126 return Math.PI * 2 * getAttribute(el, 'r');
2127 }
2128
2129 function getRectLength(el) {
2130 return getAttribute(el, 'width') * 2 + getAttribute(el, 'height') * 2;
2131 }
2132
2133 function getLineLength(el) {
2134 return getDistance({
2135 x: getAttribute(el, 'x1'),
2136 y: getAttribute(el, 'y1')
2137 }, {
2138 x: getAttribute(el, 'x2'),
2139 y: getAttribute(el, 'y2')
2140 });
2141 }
2142
2143 function getPolylineLength(el) {
2144 var points = el.points;
2145 var totalLength = 0;
2146 var previousPos;
2147
2148 for (var i = 0; i < points.numberOfItems; i++) {
2149 var currentPos = points.getItem(i);
2150
2151 if (i > 0) {
2152 totalLength += getDistance(previousPos, currentPos);
2153 }
2154
2155 previousPos = currentPos;
2156 }
2157
2158 return totalLength;
2159 }
2160
2161 function getPolygonLength(el) {
2162 var points = el.points;
2163 return getPolylineLength(el) + getDistance(points.getItem(points.numberOfItems - 1), points.getItem(0));
2164 } // Path animation
2165
2166
2167 function getTotalLength(el) {
2168 if (el.getTotalLength) {
2169 return el.getTotalLength();
2170 }
2171
2172 switch (el.tagName.toLowerCase()) {
2173 case 'circle':
2174 return getCircleLength(el);
2175
2176 case 'rect':
2177 return getRectLength(el);
2178
2179 case 'line':
2180 return getLineLength(el);
2181
2182 case 'polyline':
2183 return getPolylineLength(el);
2184
2185 case 'polygon':
2186 return getPolygonLength(el);
2187 }
2188 }
2189
2190 function setDashoffset(el) {
2191 var pathLength = getTotalLength(el);
2192 el.setAttribute('stroke-dasharray', pathLength);
2193 return pathLength;
2194 } // Motion path
2195
2196
2197 function getParentSvgEl(el) {
2198 var parentEl = el.parentNode;
2199
2200 while (is.svg(parentEl)) {
2201 if (!is.svg(parentEl.parentNode)) {
2202 break;
2203 }
2204
2205 parentEl = parentEl.parentNode;
2206 }
2207
2208 return parentEl;
2209 }
2210
2211 function getParentSvg(pathEl, svgData) {
2212 var svg = svgData || {};
2213 var parentSvgEl = svg.el || getParentSvgEl(pathEl);
2214 var rect = parentSvgEl.getBoundingClientRect();
2215 var viewBoxAttr = getAttribute(parentSvgEl, 'viewBox');
2216 var width = rect.width;
2217 var height = rect.height;
2218 var viewBox = svg.viewBox || (viewBoxAttr ? viewBoxAttr.split(' ') : [0, 0, width, height]);
2219 return {
2220 el: parentSvgEl,
2221 viewBox: viewBox,
2222 x: viewBox[0] / 1,
2223 y: viewBox[1] / 1,
2224 w: width,
2225 h: height,
2226 vW: viewBox[2],
2227 vH: viewBox[3]
2228 };
2229 }
2230
2231 function getPath(path, percent) {
2232 var pathEl = is.str(path) ? selectString(path)[0] : path;
2233 var p = percent || 100;
2234 return function (property) {
2235 return {
2236 property: property,
2237 el: pathEl,
2238 svg: getParentSvg(pathEl),
2239 totalLength: getTotalLength(pathEl) * (p / 100)
2240 };
2241 };
2242 }
2243
2244 function getPathProgress(path, progress, isPathTargetInsideSVG) {
2245 function point(offset) {
2246 if (offset === void 0) offset = 0;
2247 var l = progress + offset >= 1 ? progress + offset : 0;
2248 return path.el.getPointAtLength(l);
2249 }
2250
2251 var svg = getParentSvg(path.el, path.svg);
2252 var p = point();
2253 var p0 = point(-1);
2254 var p1 = point(+1);
2255 var scaleX = isPathTargetInsideSVG ? 1 : svg.w / svg.vW;
2256 var scaleY = isPathTargetInsideSVG ? 1 : svg.h / svg.vH;
2257
2258 switch (path.property) {
2259 case 'x':
2260 return (p.x - svg.x) * scaleX;
2261
2262 case 'y':
2263 return (p.y - svg.y) * scaleY;
2264
2265 case 'angle':
2266 return Math.atan2(p1.y - p0.y, p1.x - p0.x) * 180 / Math.PI;
2267 }
2268 } // Decompose value
2269
2270
2271 function decomposeValue(val, unit) {
2272 // const rgx = /-?\d*\.?\d+/g; // handles basic numbers
2273 // const rgx = /[+-]?\d+(?:\.\d+)?(?:[eE][+-]?\d+)?/g; // handles exponents notation
2274 var rgx = /[+-]?\d*\.?\d+(?:\.\d+)?(?:[eE][+-]?\d+)?/g; // handles exponents notation
2275
2276 var value = validateValue(is.pth(val) ? val.totalLength : val, unit) + '';
2277 return {
2278 original: value,
2279 numbers: value.match(rgx) ? value.match(rgx).map(Number) : [0],
2280 strings: is.str(val) || unit ? value.split(rgx) : []
2281 };
2282 } // Animatables
2283
2284
2285 function parseTargets(targets) {
2286 var targetsArray = targets ? flattenArray(is.arr(targets) ? targets.map(toArray) : toArray(targets)) : [];
2287 return filterArray(targetsArray, function (item, pos, self) {
2288 return self.indexOf(item) === pos;
2289 });
2290 }
2291
2292 function getAnimatables(targets) {
2293 var parsed = parseTargets(targets);
2294 return parsed.map(function (t, i) {
2295 return {
2296 target: t,
2297 id: i,
2298 total: parsed.length,
2299 transforms: {
2300 list: getElementTransforms(t)
2301 }
2302 };
2303 });
2304 } // Properties
2305
2306
2307 function normalizePropertyTweens(prop, tweenSettings) {
2308 var settings = cloneObject(tweenSettings); // Override duration if easing is a spring
2309
2310 if (/^spring/.test(settings.easing)) {
2311 settings.duration = spring(settings.easing);
2312 }
2313
2314 if (is.arr(prop)) {
2315 var l = prop.length;
2316 var isFromTo = l === 2 && !is.obj(prop[0]);
2317
2318 if (!isFromTo) {
2319 // Duration divided by the number of tweens
2320 if (!is.fnc(tweenSettings.duration)) {
2321 settings.duration = tweenSettings.duration / l;
2322 }
2323 } else {
2324 // Transform [from, to] values shorthand to a valid tween value
2325 prop = {
2326 value: prop
2327 };
2328 }
2329 }
2330
2331 var propArray = is.arr(prop) ? prop : [prop];
2332 return propArray.map(function (v, i) {
2333 var obj = is.obj(v) && !is.pth(v) ? v : {
2334 value: v
2335 }; // Default delay value should only be applied to the first tween
2336
2337 if (is.und(obj.delay)) {
2338 obj.delay = !i ? tweenSettings.delay : 0;
2339 } // Default endDelay value should only be applied to the last tween
2340
2341
2342 if (is.und(obj.endDelay)) {
2343 obj.endDelay = i === propArray.length - 1 ? tweenSettings.endDelay : 0;
2344 }
2345
2346 return obj;
2347 }).map(function (k) {
2348 return mergeObjects(k, settings);
2349 });
2350 }
2351
2352 function flattenKeyframes(keyframes) {
2353 var propertyNames = filterArray(flattenArray(keyframes.map(function (key) {
2354 return Object.keys(key);
2355 })), function (p) {
2356 return is.key(p);
2357 }).reduce(function (a, b) {
2358 if (a.indexOf(b) < 0) {
2359 a.push(b);
2360 }
2361
2362 return a;
2363 }, []);
2364 var properties = {};
2365
2366 var loop = function (i) {
2367 var propName = propertyNames[i];
2368 properties[propName] = keyframes.map(function (key) {
2369 var newKey = {};
2370
2371 for (var p in key) {
2372 if (is.key(p)) {
2373 if (p == propName) {
2374 newKey.value = key[p];
2375 }
2376 } else {
2377 newKey[p] = key[p];
2378 }
2379 }
2380
2381 return newKey;
2382 });
2383 };
2384
2385 for (var i = 0; i < propertyNames.length; i++) loop(i);
2386
2387 return properties;
2388 }
2389
2390 function getProperties(tweenSettings, params) {
2391 var properties = [];
2392 var keyframes = params.keyframes;
2393
2394 if (keyframes) {
2395 params = mergeObjects(flattenKeyframes(keyframes), params);
2396 }
2397
2398 for (var p in params) {
2399 if (is.key(p)) {
2400 properties.push({
2401 name: p,
2402 tweens: normalizePropertyTweens(params[p], tweenSettings)
2403 });
2404 }
2405 }
2406
2407 return properties;
2408 } // Tweens
2409
2410
2411 function normalizeTweenValues(tween, animatable) {
2412 var t = {};
2413
2414 for (var p in tween) {
2415 var value = getFunctionValue(tween[p], animatable);
2416
2417 if (is.arr(value)) {
2418 value = value.map(function (v) {
2419 return getFunctionValue(v, animatable);
2420 });
2421
2422 if (value.length === 1) {
2423 value = value[0];
2424 }
2425 }
2426
2427 t[p] = value;
2428 }
2429
2430 t.duration = parseFloat(t.duration);
2431 t.delay = parseFloat(t.delay);
2432 return t;
2433 }
2434
2435 function normalizeTweens(prop, animatable) {
2436 var previousTween;
2437 return prop.tweens.map(function (t) {
2438 var tween = normalizeTweenValues(t, animatable);
2439 var tweenValue = tween.value;
2440 var to = is.arr(tweenValue) ? tweenValue[1] : tweenValue;
2441 var toUnit = getUnit(to);
2442 var originalValue = getOriginalTargetValue(animatable.target, prop.name, toUnit, animatable);
2443 var previousValue = previousTween ? previousTween.to.original : originalValue;
2444 var from = is.arr(tweenValue) ? tweenValue[0] : previousValue;
2445 var fromUnit = getUnit(from) || getUnit(originalValue);
2446 var unit = toUnit || fromUnit;
2447
2448 if (is.und(to)) {
2449 to = previousValue;
2450 }
2451
2452 tween.from = decomposeValue(from, unit);
2453 tween.to = decomposeValue(getRelativeValue(to, from), unit);
2454 tween.start = previousTween ? previousTween.end : 0;
2455 tween.end = tween.start + tween.delay + tween.duration + tween.endDelay;
2456 tween.easing = parseEasings(tween.easing, tween.duration);
2457 tween.isPath = is.pth(tweenValue);
2458 tween.isPathTargetInsideSVG = tween.isPath && is.svg(animatable.target);
2459 tween.isColor = is.col(tween.from.original);
2460
2461 if (tween.isColor) {
2462 tween.round = 1;
2463 }
2464
2465 previousTween = tween;
2466 return tween;
2467 });
2468 } // Tween progress
2469
2470
2471 var setProgressValue = {
2472 css: function (t, p, v) {
2473 return t.style[p] = v;
2474 },
2475 attribute: function (t, p, v) {
2476 return t.setAttribute(p, v);
2477 },
2478 object: function (t, p, v) {
2479 return t[p] = v;
2480 },
2481 transform: function (t, p, v, transforms, manual) {
2482 transforms.list.set(p, v);
2483
2484 if (p === transforms.last || manual) {
2485 var str = '';
2486 transforms.list.forEach(function (value, prop) {
2487 str += prop + "(" + value + ") ";
2488 });
2489 t.style.transform = str;
2490 }
2491 }
2492 }; // Set Value helper
2493
2494 function setTargetsValue(targets, properties) {
2495 var animatables = getAnimatables(targets);
2496 animatables.forEach(function (animatable) {
2497 for (var property in properties) {
2498 var value = getFunctionValue(properties[property], animatable);
2499 var target = animatable.target;
2500 var valueUnit = getUnit(value);
2501 var originalValue = getOriginalTargetValue(target, property, valueUnit, animatable);
2502 var unit = valueUnit || getUnit(originalValue);
2503 var to = getRelativeValue(validateValue(value, unit), originalValue);
2504 var animType = getAnimationType(target, property);
2505 setProgressValue[animType](target, property, to, animatable.transforms, true);
2506 }
2507 });
2508 } // Animations
2509
2510
2511 function createAnimation(animatable, prop) {
2512 var animType = getAnimationType(animatable.target, prop.name);
2513
2514 if (animType) {
2515 var tweens = normalizeTweens(prop, animatable);
2516 var lastTween = tweens[tweens.length - 1];
2517 return {
2518 type: animType,
2519 property: prop.name,
2520 animatable: animatable,
2521 tweens: tweens,
2522 duration: lastTween.end,
2523 delay: tweens[0].delay,
2524 endDelay: lastTween.endDelay
2525 };
2526 }
2527 }
2528
2529 function getAnimations(animatables, properties) {
2530 return filterArray(flattenArray(animatables.map(function (animatable) {
2531 return properties.map(function (prop) {
2532 return createAnimation(animatable, prop);
2533 });
2534 })), function (a) {
2535 return !is.und(a);
2536 });
2537 } // Create Instance
2538
2539
2540 function getInstanceTimings(animations, tweenSettings) {
2541 var animLength = animations.length;
2542
2543 var getTlOffset = function (anim) {
2544 return anim.timelineOffset ? anim.timelineOffset : 0;
2545 };
2546
2547 var timings = {};
2548 timings.duration = animLength ? Math.max.apply(Math, animations.map(function (anim) {
2549 return getTlOffset(anim) + anim.duration;
2550 })) : tweenSettings.duration;
2551 timings.delay = animLength ? Math.min.apply(Math, animations.map(function (anim) {
2552 return getTlOffset(anim) + anim.delay;
2553 })) : tweenSettings.delay;
2554 timings.endDelay = animLength ? timings.duration - Math.max.apply(Math, animations.map(function (anim) {
2555 return getTlOffset(anim) + anim.duration - anim.endDelay;
2556 })) : tweenSettings.endDelay;
2557 return timings;
2558 }
2559
2560 var instanceID = 0;
2561
2562 function createNewInstance(params) {
2563 var instanceSettings = replaceObjectProps(defaultInstanceSettings, params);
2564 var tweenSettings = replaceObjectProps(defaultTweenSettings, params);
2565 var properties = getProperties(tweenSettings, params);
2566 var animatables = getAnimatables(params.targets);
2567 var animations = getAnimations(animatables, properties);
2568 var timings = getInstanceTimings(animations, tweenSettings);
2569 var id = instanceID;
2570 instanceID++;
2571 return mergeObjects(instanceSettings, {
2572 id: id,
2573 children: [],
2574 animatables: animatables,
2575 animations: animations,
2576 duration: timings.duration,
2577 delay: timings.delay,
2578 endDelay: timings.endDelay
2579 });
2580 } // Core
2581
2582
2583 var activeInstances = [];
2584
2585 var engine = function () {
2586 var raf;
2587
2588 function play() {
2589 if (!raf && (!isDocumentHidden() || !anime.suspendWhenDocumentHidden) && activeInstances.length > 0) {
2590 raf = requestAnimationFrame(step);
2591 }
2592 }
2593
2594 function step(t) {
2595 // memo on algorithm issue:
2596 // dangerous iteration over mutable `activeInstances`
2597 // (that collection may be updated from within callbacks of `tick`-ed animation instances)
2598 var activeInstancesLength = activeInstances.length;
2599 var i = 0;
2600
2601 while (i < activeInstancesLength) {
2602 var activeInstance = activeInstances[i];
2603
2604 if (!activeInstance.paused) {
2605 activeInstance.tick(t);
2606 i++;
2607 } else {
2608 activeInstances.splice(i, 1);
2609 activeInstancesLength--;
2610 }
2611 }
2612
2613 raf = i > 0 ? requestAnimationFrame(step) : undefined;
2614 }
2615
2616 function handleVisibilityChange() {
2617 if (!anime.suspendWhenDocumentHidden) {
2618 return;
2619 }
2620
2621 if (isDocumentHidden()) {
2622 // suspend ticks
2623 raf = cancelAnimationFrame(raf);
2624 } else {
2625 // is back to active tab
2626 // first adjust animations to consider the time that ticks were suspended
2627 activeInstances.forEach(function (instance) {
2628 return instance._onDocumentVisibility();
2629 });
2630 engine();
2631 }
2632 }
2633
2634 if (typeof document !== 'undefined') {
2635 document.addEventListener('visibilitychange', handleVisibilityChange);
2636 }
2637
2638 return play;
2639 }();
2640
2641 function isDocumentHidden() {
2642 return !!document && document.hidden;
2643 } // Public Instance
2644
2645
2646 function anime(params) {
2647 if (params === void 0) params = {};
2648 var startTime = 0,
2649 lastTime = 0,
2650 now = 0;
2651 var children,
2652 childrenLength = 0;
2653 var resolve = null;
2654
2655 function makePromise(instance) {
2656 var promise = window.Promise && new Promise(function (_resolve) {
2657 return resolve = _resolve;
2658 });
2659 instance.finished = promise;
2660 return promise;
2661 }
2662
2663 var instance = createNewInstance(params);
2664 makePromise(instance);
2665
2666 function toggleInstanceDirection() {
2667 var direction = instance.direction;
2668
2669 if (direction !== 'alternate') {
2670 instance.direction = direction !== 'normal' ? 'normal' : 'reverse';
2671 }
2672
2673 instance.reversed = !instance.reversed;
2674 children.forEach(function (child) {
2675 return child.reversed = instance.reversed;
2676 });
2677 }
2678
2679 function adjustTime(time) {
2680 return instance.reversed ? instance.duration - time : time;
2681 }
2682
2683 function resetTime() {
2684 startTime = 0;
2685 lastTime = adjustTime(instance.currentTime) * (1 / anime.speed);
2686 }
2687
2688 function seekChild(time, child) {
2689 if (child) {
2690 child.seek(time - child.timelineOffset);
2691 }
2692 }
2693
2694 function syncInstanceChildren(time) {
2695 if (!instance.reversePlayback) {
2696 for (var i = 0; i < childrenLength; i++) {
2697 seekChild(time, children[i]);
2698 }
2699 } else {
2700 for (var i$1 = childrenLength; i$1--;) {
2701 seekChild(time, children[i$1]);
2702 }
2703 }
2704 }
2705
2706 function setAnimationsProgress(insTime) {
2707 var i = 0;
2708 var animations = instance.animations;
2709 var animationsLength = animations.length;
2710
2711 while (i < animationsLength) {
2712 var anim = animations[i];
2713 var animatable = anim.animatable;
2714 var tweens = anim.tweens;
2715 var tweenLength = tweens.length - 1;
2716 var tween = tweens[tweenLength]; // Only check for keyframes if there is more than one tween
2717
2718 if (tweenLength) {
2719 tween = filterArray(tweens, function (t) {
2720 return insTime < t.end;
2721 })[0] || tween;
2722 }
2723
2724 var elapsed = minMax(insTime - tween.start - tween.delay, 0, tween.duration) / tween.duration;
2725 var eased = isNaN(elapsed) ? 1 : tween.easing(elapsed);
2726 var strings = tween.to.strings;
2727 var round = tween.round;
2728 var numbers = [];
2729 var toNumbersLength = tween.to.numbers.length;
2730 var progress = void 0;
2731
2732 for (var n = 0; n < toNumbersLength; n++) {
2733 var value = void 0;
2734 var toNumber = tween.to.numbers[n];
2735 var fromNumber = tween.from.numbers[n] || 0;
2736
2737 if (!tween.isPath) {
2738 value = fromNumber + eased * (toNumber - fromNumber);
2739 } else {
2740 value = getPathProgress(tween.value, eased * toNumber, tween.isPathTargetInsideSVG);
2741 }
2742
2743 if (round) {
2744 if (!(tween.isColor && n > 2)) {
2745 value = Math.round(value * round) / round;
2746 }
2747 }
2748
2749 numbers.push(value);
2750 } // Manual Array.reduce for better performances
2751
2752
2753 var stringsLength = strings.length;
2754
2755 if (!stringsLength) {
2756 progress = numbers[0];
2757 } else {
2758 progress = strings[0];
2759
2760 for (var s = 0; s < stringsLength; s++) {
2761 strings[s];
2762 var b = strings[s + 1];
2763 var n$1 = numbers[s];
2764
2765 if (!isNaN(n$1)) {
2766 if (!b) {
2767 progress += n$1 + ' ';
2768 } else {
2769 progress += n$1 + b;
2770 }
2771 }
2772 }
2773 }
2774
2775 setProgressValue[anim.type](animatable.target, anim.property, progress, animatable.transforms);
2776 anim.currentValue = progress;
2777 i++;
2778 }
2779 }
2780
2781 function setCallback(cb) {
2782 if (instance[cb] && !instance.passThrough) {
2783 instance[cb](instance);
2784 }
2785 }
2786
2787 function countIteration() {
2788 if (instance.remaining && instance.remaining !== true) {
2789 instance.remaining--;
2790 }
2791 }
2792
2793 function setInstanceProgress(engineTime) {
2794 var insDuration = instance.duration;
2795 var insDelay = instance.delay;
2796 var insEndDelay = insDuration - instance.endDelay;
2797 var insTime = adjustTime(engineTime);
2798 instance.progress = minMax(insTime / insDuration * 100, 0, 100);
2799 instance.reversePlayback = insTime < instance.currentTime;
2800
2801 if (children) {
2802 syncInstanceChildren(insTime);
2803 }
2804
2805 if (!instance.began && instance.currentTime > 0) {
2806 instance.began = true;
2807 setCallback('begin');
2808 }
2809
2810 if (!instance.loopBegan && instance.currentTime > 0) {
2811 instance.loopBegan = true;
2812 setCallback('loopBegin');
2813 }
2814
2815 if (insTime <= insDelay && instance.currentTime !== 0) {
2816 setAnimationsProgress(0);
2817 }
2818
2819 if (insTime >= insEndDelay && instance.currentTime !== insDuration || !insDuration) {
2820 setAnimationsProgress(insDuration);
2821 }
2822
2823 if (insTime > insDelay && insTime < insEndDelay) {
2824 if (!instance.changeBegan) {
2825 instance.changeBegan = true;
2826 instance.changeCompleted = false;
2827 setCallback('changeBegin');
2828 }
2829
2830 setCallback('change');
2831 setAnimationsProgress(insTime);
2832 } else {
2833 if (instance.changeBegan) {
2834 instance.changeCompleted = true;
2835 instance.changeBegan = false;
2836 setCallback('changeComplete');
2837 }
2838 }
2839
2840 instance.currentTime = minMax(insTime, 0, insDuration);
2841
2842 if (instance.began) {
2843 setCallback('update');
2844 }
2845
2846 if (engineTime >= insDuration) {
2847 lastTime = 0;
2848 countIteration();
2849
2850 if (!instance.remaining) {
2851 instance.paused = true;
2852
2853 if (!instance.completed) {
2854 instance.completed = true;
2855 setCallback('loopComplete');
2856 setCallback('complete');
2857
2858 if (!instance.passThrough && 'Promise' in window) {
2859 resolve();
2860 makePromise(instance);
2861 }
2862 }
2863 } else {
2864 startTime = now;
2865 setCallback('loopComplete');
2866 instance.loopBegan = false;
2867
2868 if (instance.direction === 'alternate') {
2869 toggleInstanceDirection();
2870 }
2871 }
2872 }
2873 }
2874
2875 instance.reset = function () {
2876 var direction = instance.direction;
2877 instance.passThrough = false;
2878 instance.currentTime = 0;
2879 instance.progress = 0;
2880 instance.paused = true;
2881 instance.began = false;
2882 instance.loopBegan = false;
2883 instance.changeBegan = false;
2884 instance.completed = false;
2885 instance.changeCompleted = false;
2886 instance.reversePlayback = false;
2887 instance.reversed = direction === 'reverse';
2888 instance.remaining = instance.loop;
2889 children = instance.children;
2890 childrenLength = children.length;
2891
2892 for (var i = childrenLength; i--;) {
2893 instance.children[i].reset();
2894 }
2895
2896 if (instance.reversed && instance.loop !== true || direction === 'alternate' && instance.loop === 1) {
2897 instance.remaining++;
2898 }
2899
2900 setAnimationsProgress(instance.reversed ? instance.duration : 0);
2901 }; // internal method (for engine) to adjust animation timings before restoring engine ticks (rAF)
2902
2903
2904 instance._onDocumentVisibility = resetTime; // Set Value helper
2905
2906 instance.set = function (targets, properties) {
2907 setTargetsValue(targets, properties);
2908 return instance;
2909 };
2910
2911 instance.tick = function (t) {
2912 now = t;
2913
2914 if (!startTime) {
2915 startTime = now;
2916 }
2917
2918 setInstanceProgress((now + (lastTime - startTime)) * anime.speed);
2919 };
2920
2921 instance.seek = function (time) {
2922 setInstanceProgress(adjustTime(time));
2923 };
2924
2925 instance.pause = function () {
2926 instance.paused = true;
2927 resetTime();
2928 };
2929
2930 instance.play = function () {
2931 if (!instance.paused) {
2932 return;
2933 }
2934
2935 if (instance.completed) {
2936 instance.reset();
2937 }
2938
2939 instance.paused = false;
2940 activeInstances.push(instance);
2941 resetTime();
2942 engine();
2943 };
2944
2945 instance.reverse = function () {
2946 toggleInstanceDirection();
2947 instance.completed = instance.reversed ? false : true;
2948 resetTime();
2949 };
2950
2951 instance.restart = function () {
2952 instance.reset();
2953 instance.play();
2954 };
2955
2956 instance.remove = function (targets) {
2957 var targetsArray = parseTargets(targets);
2958 removeTargetsFromInstance(targetsArray, instance);
2959 };
2960
2961 instance.reset();
2962
2963 if (instance.autoplay) {
2964 instance.play();
2965 }
2966
2967 return instance;
2968 } // Remove targets from animation
2969
2970
2971 function removeTargetsFromAnimations(targetsArray, animations) {
2972 for (var a = animations.length; a--;) {
2973 if (arrayContains(targetsArray, animations[a].animatable.target)) {
2974 animations.splice(a, 1);
2975 }
2976 }
2977 }
2978
2979 function removeTargetsFromInstance(targetsArray, instance) {
2980 var animations = instance.animations;
2981 var children = instance.children;
2982 removeTargetsFromAnimations(targetsArray, animations);
2983
2984 for (var c = children.length; c--;) {
2985 var child = children[c];
2986 var childAnimations = child.animations;
2987 removeTargetsFromAnimations(targetsArray, childAnimations);
2988
2989 if (!childAnimations.length && !child.children.length) {
2990 children.splice(c, 1);
2991 }
2992 }
2993
2994 if (!animations.length && !children.length) {
2995 instance.pause();
2996 }
2997 }
2998
2999 function removeTargetsFromActiveInstances(targets) {
3000 var targetsArray = parseTargets(targets);
3001
3002 for (var i = activeInstances.length; i--;) {
3003 var instance = activeInstances[i];
3004 removeTargetsFromInstance(targetsArray, instance);
3005 }
3006 } // Stagger helpers
3007
3008
3009 function stagger(val, params) {
3010 if (params === void 0) params = {};
3011 var direction = params.direction || 'normal';
3012 var easing = params.easing ? parseEasings(params.easing) : null;
3013 var grid = params.grid;
3014 var axis = params.axis;
3015 var fromIndex = params.from || 0;
3016 var fromFirst = fromIndex === 'first';
3017 var fromCenter = fromIndex === 'center';
3018 var fromLast = fromIndex === 'last';
3019 var isRange = is.arr(val);
3020 var val1 = isRange ? parseFloat(val[0]) : parseFloat(val);
3021 var val2 = isRange ? parseFloat(val[1]) : 0;
3022 var unit = getUnit(isRange ? val[1] : val) || 0;
3023 var start = params.start || 0 + (isRange ? val1 : 0);
3024 var values = [];
3025 var maxValue = 0;
3026 return function (el, i, t) {
3027 if (fromFirst) {
3028 fromIndex = 0;
3029 }
3030
3031 if (fromCenter) {
3032 fromIndex = (t - 1) / 2;
3033 }
3034
3035 if (fromLast) {
3036 fromIndex = t - 1;
3037 }
3038
3039 if (!values.length) {
3040 for (var index = 0; index < t; index++) {
3041 if (!grid) {
3042 values.push(Math.abs(fromIndex - index));
3043 } else {
3044 var fromX = !fromCenter ? fromIndex % grid[0] : (grid[0] - 1) / 2;
3045 var fromY = !fromCenter ? Math.floor(fromIndex / grid[0]) : (grid[1] - 1) / 2;
3046 var toX = index % grid[0];
3047 var toY = Math.floor(index / grid[0]);
3048 var distanceX = fromX - toX;
3049 var distanceY = fromY - toY;
3050 var value = Math.sqrt(distanceX * distanceX + distanceY * distanceY);
3051
3052 if (axis === 'x') {
3053 value = -distanceX;
3054 }
3055
3056 if (axis === 'y') {
3057 value = -distanceY;
3058 }
3059
3060 values.push(value);
3061 }
3062
3063 maxValue = Math.max.apply(Math, values);
3064 }
3065
3066 if (easing) {
3067 values = values.map(function (val) {
3068 return easing(val / maxValue) * maxValue;
3069 });
3070 }
3071
3072 if (direction === 'reverse') {
3073 values = values.map(function (val) {
3074 return axis ? val < 0 ? val * -1 : -val : Math.abs(maxValue - val);
3075 });
3076 }
3077 }
3078
3079 var spacing = isRange ? (val2 - val1) / maxValue : val1;
3080 return start + spacing * (Math.round(values[i] * 100) / 100) + unit;
3081 };
3082 } // Timeline
3083
3084
3085 function timeline(params) {
3086 if (params === void 0) params = {};
3087 var tl = anime(params);
3088 tl.duration = 0;
3089
3090 tl.add = function (instanceParams, timelineOffset) {
3091 var tlIndex = activeInstances.indexOf(tl);
3092 var children = tl.children;
3093
3094 if (tlIndex > -1) {
3095 activeInstances.splice(tlIndex, 1);
3096 }
3097
3098 function passThrough(ins) {
3099 ins.passThrough = true;
3100 }
3101
3102 for (var i = 0; i < children.length; i++) {
3103 passThrough(children[i]);
3104 }
3105
3106 var insParams = mergeObjects(instanceParams, replaceObjectProps(defaultTweenSettings, params));
3107 insParams.targets = insParams.targets || params.targets;
3108 var tlDuration = tl.duration;
3109 insParams.autoplay = false;
3110 insParams.direction = tl.direction;
3111 insParams.timelineOffset = is.und(timelineOffset) ? tlDuration : getRelativeValue(timelineOffset, tlDuration);
3112 passThrough(tl);
3113 tl.seek(insParams.timelineOffset);
3114 var ins = anime(insParams);
3115 passThrough(ins);
3116 children.push(ins);
3117 var timings = getInstanceTimings(children, params);
3118 tl.delay = timings.delay;
3119 tl.endDelay = timings.endDelay;
3120 tl.duration = timings.duration;
3121 tl.seek(0);
3122 tl.reset();
3123
3124 if (tl.autoplay) {
3125 tl.play();
3126 }
3127
3128 return tl;
3129 };
3130
3131 return tl;
3132 }
3133
3134 anime.version = '3.2.1';
3135 anime.speed = 1; // TODO:#review: naming, documentation
3136
3137 anime.suspendWhenDocumentHidden = true;
3138 anime.running = activeInstances;
3139 anime.remove = removeTargetsFromActiveInstances;
3140 anime.get = getOriginalTargetValue;
3141 anime.set = setTargetsValue;
3142 anime.convertPx = convertPxToUnit;
3143 anime.path = getPath;
3144 anime.setDashoffset = setDashoffset;
3145 anime.stagger = stagger;
3146 anime.timeline = timeline;
3147 anime.easing = parseEasings;
3148 anime.penner = penner;
3149
3150 anime.random = function (min, max) {
3151 return Math.floor(Math.random() * (max - min + 1)) + min;
3152 };
3153
3154 class View extends Emitter {
3155 constructor() {
3156 super(); // list of added sections
3157
3158 this.sections = [];
3159 this.sectionsCount = 0;
3160 this._index = 0;
3161 this.indexes = [];
3162 this.currentSection = null;
3163 this.eventPrefix = 'view';
3164 this._loop = false;
3165 }
3166 /**
3167 * Gets current index
3168 */
3169
3170
3171 get index() {
3172 return this._index;
3173 }
3174 /**
3175 * Sets current index
3176 */
3177
3178
3179 set index(value) {
3180 if (value === this._index) {
3181 return;
3182 }
3183
3184 this._index = value;
3185 this.currentSection = this.sections[value];
3186 this.trigger('indexChange', [value], true);
3187 }
3188 /**
3189 * Gets loop value
3190 */
3191
3192
3193 get loop() {
3194 return this._loop;
3195 }
3196 /**
3197 * Sets new loop value
3198 */
3199
3200
3201 set loop(value) {
3202 if (this._loop !== value) {
3203 this._loop = value;
3204 this.update();
3205 }
3206 }
3207 /**
3208 * Gets the total number of added sections
3209 */
3210
3211
3212 get count() {
3213 return this.sectionsCount;
3214 }
3215 /**
3216 * Appends new section to the sections list
3217 * @param {MSSection} section New section instance
3218 * @param {Boolean} update Whether call view update method
3219 */
3220
3221
3222 appendSection(section, update = true) {
3223 this.sections.push(section);
3224
3225 this._afterSectionAdd(section, update);
3226 }
3227 /**
3228 * Prepends new section to the sections list
3229 * @param {MSSection} section Mew section instance
3230 * @param {Boolean} update Whether call view update method
3231 */
3232
3233
3234 prependSection(section, update = true) {
3235 this.sections.unshift(section);
3236
3237 this._afterSectionAdd(section, update);
3238 }
3239 /**
3240 * Inserts new section after given section
3241 * @param {MSSection} section Mew section instance
3242 * @param {MSSection} afterSection Target section instance
3243 * @param {Boolean} update Whether call view update method
3244 */
3245
3246
3247 insertSectionAfter(section, afterSection, update = true) {
3248 this.insertSectionAt(section, this.sections.indexOf(afterSection), update);
3249 }
3250 /**
3251 * Inserts new section after given index
3252 * @param {MSSection} section Mew section instance
3253 * @param {Number} index Target index
3254 * @param {Boolean} update Whether call view update method
3255 */
3256
3257
3258 insertSectionAt(section, index, update = true) {
3259 if (index < 0) {
3260 return;
3261 }
3262
3263 this.sections.splice(index, 0, section);
3264
3265 this._afterSectionAdd(section, update);
3266 }
3267 /**
3268 * Removes section from view
3269 * @param {MSSection} section
3270 * @param {Boolean} update Whether call view update method
3271 */
3272
3273
3274 removeSection(section, update = true) {
3275 return this.removeSectionByIndex(this.section.indexOf(section), update);
3276 }
3277 /**
3278 * Removes the section at given index
3279 * @param {Number} index section id
3280 * @param {Boolean} update Whether call view update method
3281 */
3282
3283
3284 removeSectionByIndex(index, update = true) {
3285 if (index < 0) {
3286 return false;
3287 }
3288
3289 const removedSection = this.sections.splice(index, 1);
3290 removedSection.unmount();
3291 this.trigger('sectionRemove', removedSection);
3292
3293 if (update) {
3294 this.update();
3295 }
3296
3297 return removedSection[0];
3298 }
3299 /**
3300 * Update view
3301 */
3302
3303
3304 update() {
3305 this.trigger('update', null, true);
3306 }
3307 /**
3308 * Updates sections index number
3309 */
3310
3311
3312 updateSectionsIndex() {
3313 this.sections.forEach((section, index) => {
3314 section.index = index;
3315 });
3316 }
3317 /**
3318 * Updates sections count and calls section mount method
3319 * @private
3320 * @param {MSSection} section
3321 * @param {Boolean} update Whether call view update method
3322 */
3323
3324
3325 _afterSectionAdd(section, update) {
3326 this.sectionsCount = this.sections.length;
3327 section.mount(this);
3328 this.updateSectionsIndex();
3329
3330 if (update) {
3331 this.update();
3332 }
3333
3334 this.trigger('sectionAdd', [section]);
3335 }
3336
3337 }
3338
3339 /**
3340 * This class extends View and adds and manages view element plus adding
3341 * and removing section elements to the dom.
3342 */
3343
3344 class DomView extends View {
3345 constructor() {
3346 super();
3347 this.element = document.createElement('div');
3348 this.element.classList.add(`${prefix}-view`);
3349 this.sectionsContainer = document.createElement('div');
3350 this.sectionsContainer.classList.add(`${prefix}-sections`);
3351 this.element.appendChild(this.sectionsContainer);
3352 }
3353 /**
3354 * Reads element dimension values and updates the properties
3355 */
3356
3357
3358 resize() {
3359 const width = this.element.offsetWidth;
3360 const height = this.element.offsetHeight;
3361
3362 if (width === this.width && height === this.height) {
3363 return false;
3364 }
3365
3366 this.width = width;
3367 this.height = height;
3368 this.trigger('resize', [width, height], true);
3369 return true;
3370 }
3371 /**
3372 * Appends the view element to the given target element
3373 * @param {Element} target Target element
3374 */
3375
3376
3377 appendTo(target) {
3378 target.appendChild(this.element);
3379 this.resize();
3380 this.trigger('elementAppend', [target], true);
3381 }
3382 /**
3383 * Appends new section to the sections list
3384 * @param {MSSection} section New section instance
3385 */
3386
3387
3388 appendSection(section) {
3389 this.sectionsContainer.appendChild(section.element);
3390 super.appendSection(section);
3391 }
3392 /**
3393 * Prepends new section to the sections list
3394 * @param {MSSection} section Mew section instance
3395 */
3396
3397
3398 prependSection(section) {
3399 if (this.sectionsContainer.hasChildNodes) {
3400 this.sectionsContainer.insertBefore(section.element, this.sectionsContainer.firstChild);
3401 } else {
3402 this.sectionsContainer.appendChild(section.element);
3403 }
3404
3405 super.prependSection(section);
3406 }
3407 /**
3408 * Inserts new section after given index
3409 * @param {MSSection} section Mew section instance
3410 * @param {Number} index Target index
3411 */
3412
3413
3414 insertSectionAt(section, index) {
3415 if (index < 0) {
3416 return;
3417 }
3418
3419 this.sectionsContainer.insertBefore(section.element, this.sectionsContainer.childNodes[index]);
3420 super.insertSectionAt(section, index);
3421 }
3422 /**
3423 * Removes the section at given index
3424 * @param {Number} index section id
3425 */
3426
3427
3428 removeSectionByIndex(index) {
3429 if (index < 0) {
3430 return false;
3431 }
3432
3433 this.sections[index].element.remove();
3434 return super.removeSectionByIndex(index);
3435 }
3436
3437 }
3438
3439 /**
3440 * This class calculates section positions relative to the scroll position value
3441 */
3442
3443 class ScrollView extends DomView {
3444 constructor() {
3445 super();
3446 this.activeEnteringSection = false;
3447 this.activeFactor = 0.5; // Index of visible sections in view, it may different from view index
3448
3449 this.visibleIndex = 0;
3450 this.visibleIndexes = [];
3451 this.scrollable = true; // privates
3452
3453 this._size = 0;
3454 this._position = 0;
3455 this._length = 0;
3456 }
3457 /**
3458 * Gets the current position
3459 */
3460
3461
3462 get position() {
3463 return this._position;
3464 }
3465 /**
3466 * Sets new position
3467 */
3468
3469
3470 set position(value) {
3471 if (this._position === value) {
3472 return;
3473 } // find scroll direction
3474
3475
3476 this.scrollDirection = value > this._position ? 'forward' : 'backward';
3477
3478 if (this._loop) {
3479 this._position = this.normalizePosition(value);
3480 } else {
3481 this._position = value;
3482 } // update view
3483
3484
3485 this.update(false);
3486 this.trigger('scroll', [this._position]);
3487 }
3488 /**
3489 * Gets length value
3490 */
3491
3492
3493 get length() {
3494 return this._length;
3495 }
3496 /**
3497 * Gets the view size
3498 */
3499
3500
3501 get size() {
3502 return this._size;
3503 }
3504 /**
3505 * Sets the view size
3506 */
3507
3508
3509 set size(value) {
3510 if (this._size !== value) {
3511 const changeRatio = this._size ? value / this._size : 1;
3512 this._size = value; // update position location after resize
3513
3514 let spaces = 0;
3515 this.sections.some((section, index) => {
3516 if (index < this.visibleIndex) {
3517 spaces += section.space;
3518 return false;
3519 }
3520
3521 return true;
3522 });
3523 this._position = (this._position - spaces) * changeRatio + spaces;
3524 this.update();
3525 }
3526 }
3527 /**
3528 * Arranges sections in view
3529 */
3530
3531
3532 arrange() {
3533 const lastLength = this._length;
3534 this._length = 0;
3535 this.sections.forEach((section, index) => {
3536 section.index = index;
3537 section.position = this._length;
3538 section.offset = this._length;
3539 section.calculateSize();
3540 this._length += section.size;
3541 }); // remove last section space from length
3542
3543 if (this._sectionsCount && !this._loop) {
3544 this._length -= this.sections[this._sectionsCount - 1].space;
3545 }
3546
3547 this.trigger('arrange', null, true);
3548
3549 if (this._length !== lastLength) {
3550 this.trigger('lengthChange', [this._length], this);
3551 }
3552 }
3553 /**
3554 * Locates sections in a loop based on current position value
3555 */
3556
3557
3558 locateInLoop() {
3559 if (!this._loop) {
3560 return;
3561 } // calculates new offsets if loop is enabled
3562
3563
3564 let before = 0;
3565 let balanceLength = -1;
3566 let backwardStart = 0;
3567 let backwardsLength = 0;
3568 let startSection;
3569 this.sections.some(section => {
3570 if (section.inRangeTest(this._position)) {
3571 startSection = section;
3572 return true;
3573 }
3574
3575 return false;
3576 });
3577
3578 for (let i = 0; i !== this._sectionsCount; i += 1) {
3579 const section = this.sections[(i + startSection.index) % this._sectionsCount];
3580 section.offset = startSection.position + before;
3581 before += section.size; // finds last section in view size and calculates the balance length
3582
3583 if (balanceLength === -1 && section.inRangeTest((this._position + this._size) % this._length)) {
3584 balanceLength = (this._length - before) / 2;
3585 } // finds the last section at balance length and calculates backward sections start index and their length
3586
3587
3588 if (balanceLength !== -1 && section.inRangeTest((this._position + balanceLength + this._size) % this._length)) {
3589 backwardStart = (i + 1 + startSection.index) % this._sectionsCount;
3590 backwardsLength = this._sectionsCount - (i + 1);
3591 break;
3592 }
3593 }
3594
3595 before = 0; // calculates the backward sections offset
3596
3597 for (let i = backwardsLength - 1; i >= 0; i -= 1) {
3598 const section = this.sections[(i + backwardStart) % this._sectionsCount];
3599 before += section.size;
3600 section.offset = startSection.position - before;
3601 }
3602
3603 this.trigger('loopUpdate', null, true);
3604 }
3605 /**
3606 * Updates sections offset and position values
3607 */
3608
3609
3610 update(arrange = true) {
3611 this._sectionsCount = this.sections.length;
3612
3613 if (arrange) {
3614 this.arrange();
3615 }
3616
3617 this.locateInLoop();
3618 this.updateStatusAndIndex();
3619 this.trigger('update', [this._position], true);
3620 }
3621 /**
3622 * Updates view index and sections status value
3623 */
3624
3625
3626 updateStatusAndIndex() {
3627 let indexes = [];
3628 let visibleIndexes = [];
3629 let visibleIndex;
3630 const pos = Math.round(this._position);
3631 this.sections.forEach(section => {
3632 let status = 'in';
3633
3634 if (section.offset + section.size <= pos) {
3635 status = 'passed';
3636 } else if (section.offset < pos) {
3637 status = this.scrollDirection === 'forward' ? 'leaving' : 'entering';
3638 } else if (section.offset - section.space >= pos + this._size) {
3639 status = 'pending';
3640 } else if (section.offset + section.size - section.space > pos + this._size) {
3641 status = this.scrollDirection !== 'forward' ? 'leaving' : 'entering';
3642 }
3643
3644 if (section.inRangeTest(pos)) {
3645 visibleIndex = section.index;
3646 }
3647
3648 if (status !== 'passed' && status !== 'pending') {
3649 visibleIndexes.push(section.index);
3650 }
3651
3652 section.status = status;
3653 const startOffset = section.offset - pos;
3654 const endOffset = section.offset + section.size - section.space - pos - this._size;
3655 section.pendingOffset = startOffset <= 0 ? startOffset : Math.max(0, endOffset);
3656
3657 if (this.activeEnteringSection) {
3658 const factorPos = section.size * this.activeFactor;
3659 section.active = section.offset + factorPos >= pos && section.offset + section.size - factorPos <= pos + this._size + section.space;
3660 } else {
3661 section.active = status === 'in';
3662 }
3663
3664 if (section.active) {
3665 indexes.push(section.index);
3666 }
3667 });
3668 visibleIndexes = visibleIndexes.sort((a, b) => this.sections[a].offset - this.sections[b].offset);
3669
3670 if (this.visibleIndexes.toString() !== visibleIndexes.toString()) {
3671 this.visibleIndexes = visibleIndexes;
3672 this.trigger('visibleIndexesChange', [this.visibleIndexes], true);
3673 }
3674
3675 if (this.visibleIndex !== visibleIndex) {
3676 this.visibleIndex = visibleIndex;
3677 this.trigger('visibleIndexChange', [this.visibleIndex], true);
3678 }
3679
3680 indexes = indexes.sort((a, b) => this.sections[a].offset - this.sections[b].offset);
3681
3682 if (this.indexes.toString() !== indexes.toString()) {
3683 this.indexes = indexes;
3684 this.trigger('indexesChange', [this.indexes]);
3685 }
3686
3687 const index = this.indexes[0];
3688
3689 if (this.index !== index) {
3690 this.index = index;
3691 this.trigger('indexChange', [this.index]);
3692 }
3693 }
3694
3695 normalizePositionByDirection(position, direction = 'auto') {
3696 if (this._loop) {
3697 position = this.normalizePosition(position);
3698 } else {
3699 position = Math.min(position, this._length - this._size);
3700 }
3701
3702 let change = 0;
3703
3704 if (this._loop && direction !== 'off') {
3705 const current = this._position;
3706 const target = position;
3707 const forward = current < target ? target - current : this._length - current + target;
3708 const backward = current < target ? target - this._length - current : target - current;
3709
3710 switch (direction) {
3711 case 'auto':
3712 change = Math.abs(backward) < Math.abs(forward) ? backward : forward;
3713 break;
3714
3715 case 'backward':
3716 change = backward;
3717 break;
3718
3719 default:
3720 change = forward;
3721 break;
3722 }
3723
3724 return this._position + change;
3725 }
3726
3727 return position;
3728 }
3729 /**
3730 * Scrolls to the target position
3731 * @param {Number} position Target position
3732 * @param {Boolean} animate Whether animate or not
3733 * @param {Number} duration Animation duration in seconds
3734 * @param {String} direction Specifies the direction of scrolling (Only affective when loop is enabled)
3735 * @param {Object} animParams TweenLite anim params
3736 */
3737
3738
3739 scrollTo(position, animate = true, duration = 1, direction = 'auto', animParams) {
3740 this.killScrollAnimation();
3741 position = this.normalizePositionByDirection(position, direction);
3742
3743 if (animate) {
3744 animParams = _objectSpread2(_objectSpread2({
3745 easing: 'easeOutExpo',
3746 duration: duration * 1000
3747 }, animParams), {}, {
3748 complete: () => {
3749 this.animating = false;
3750 this.trigger('scrollToAnimationEnd', undefined, true);
3751 }
3752 });
3753 animParams.position = position;
3754 this.animating = true;
3755 anime(_objectSpread2({
3756 targets: this
3757 }, animParams));
3758 } else {
3759 this.position = position;
3760 }
3761 }
3762 /**
3763 * Kills the scroll to animation tween object
3764 */
3765
3766
3767 killScrollAnimation() {
3768 if (this.animating) {
3769 anime.remove(this);
3770 this.animating = false;
3771 }
3772 }
3773 /**
3774 * Scrolls to the target section
3775 * @param {MSSection} section Target section
3776 * @param {Boolean} animate Whether animate or not
3777 * @param {Number} duration Animation duration in seconds
3778 * @param {String} direction Specifies the direction of scrolling (Only affective when loop is enabled)
3779 * @param {Object} animParams TweenLite anim params
3780 */
3781
3782
3783 goToSection(section, animate = true, duration = 1, direction = 'auto', animParams) {
3784 this.scrollTo(section.position, animate, duration, direction, animParams);
3785 }
3786 /**
3787 * Scrolls to the target index
3788 * @param {Number} section Target index
3789 * @param {Boolean} animate Whether animate or not
3790 * @param {Number} duration Animation duration in seconds
3791 * @param {String} direction Specifies the direction of scrolling (Only affective when loop is enabled)
3792 * @param {Object} animParams TweenLite anim params
3793 */
3794
3795
3796 goToIndex(index, animate = true, duration = 1, direction = 'auto', animParams) {
3797 if (index >= this.sectionsCount) {
3798 return;
3799 }
3800
3801 this.goToSection(this.sections[index], animate, duration, direction, animParams);
3802 }
3803 /**
3804 * @param {Number} position
3805 * @returns {Number} Section index at given position
3806 */
3807
3808
3809 getIndexAtPosition(position) {
3810 if (this._loop) {
3811 position = this.normalizePosition(position);
3812 }
3813
3814 position %= this._length;
3815 let returnIndex = -1;
3816 this.sections.some((section, index) => {
3817 if (!this.activeEnteringSection) {
3818 if (section.inRangeTest(position)) {
3819 returnIndex = index;
3820 return true;
3821 }
3822 } else if (section.position + section.size * this.activeFactor >= position) {
3823 returnIndex = index;
3824 return true;
3825 }
3826
3827 return false;
3828 });
3829
3830 if (returnIndex === -1) {
3831 return this._loop ? 0 : this.sectionsCount - 1;
3832 }
3833
3834 return returnIndex;
3835 }
3836 /**
3837 * @param {Number} position
3838 * @returns {Array} Section indexes between given position and view size
3839 */
3840
3841
3842 getIndexesAtPosition(position) {
3843 if (this._loop) {
3844 position = this.normalizePosition(position);
3845 } else {
3846 position = Math.min(position, this._length - this._size);
3847 }
3848
3849 const startIndex = this.getIndexAtPosition(position);
3850 const indexes = [];
3851
3852 for (let i = 0; i !== this._sectionsCount; i += 1) {
3853 let section;
3854
3855 if (this._loop) {
3856 section = this.sections[(i + startIndex) % this._sectionsCount];
3857 } else {
3858 if (i + startIndex >= this._sectionsCount) {
3859 return indexes;
3860 }
3861
3862 section = this.sections[i + startIndex];
3863 }
3864
3865 indexes.push(section.index);
3866
3867 if (!this.activeEnteringSection) {
3868 if (section.inRangeTest((position + this._size) % this._length)) {
3869 return indexes;
3870 }
3871 } else if (section.inRangeTest((position + this._size) % this._length)) {
3872 if (section.position + section.size - section.size * this.activeFactor < (position + this._size) % this._length) {
3873 return indexes;
3874 }
3875
3876 indexes.pop();
3877 return indexes;
3878 }
3879 }
3880
3881 return indexes;
3882 }
3883 /**
3884 * Recalculates the value base on length and moves it to the valid range
3885 * @param {Number} value Scroll position
3886 */
3887
3888
3889 normalizePosition(value) {
3890 value %= this._length;
3891
3892 if (value < 0) {
3893 value += this.length;
3894 }
3895
3896 return value;
3897 }
3898
3899 }
3900
3901 class Observable {
3902 constructor() {
3903 // @private
3904 this._options = {};
3905 this._defaults = {};
3906 this._observers = {};
3907 this._aliases = {};
3908 this._waitings = {};
3909 }
3910 /**
3911 * Injects values to options
3912 * @param {Object} options Plan object of options
3913 */
3914
3915
3916 inject(options) {
3917 Object.keys(options).forEach(name => {
3918 if (this._options[name] instanceof Observable) {
3919 this._options[name].inject(options[name]);
3920 } else if (!this.set(name, options[name], true)) {
3921 this._waitings[name] = options[name];
3922 }
3923 });
3924 }
3925 /**
3926 * Registers new option, if the option is already exists, updates the option
3927 * @param {String|Object} name Option name or an object of multiple options and values
3928 * @param {*} defaultValue Option default value
3929 */
3930
3931
3932 register(name, defaultValue) {
3933 if (typeof name === 'object') {
3934 const names = Object.keys(name);
3935 names.forEach(optionName => {
3936 this.register(optionName, name[optionName]);
3937 });
3938 return names;
3939 }
3940
3941 if (!Array.isArray(defaultValue) && typeof defaultValue === 'object') {
3942 this._options[name] = new Observable();
3943
3944 this._options[name].register(defaultValue);
3945 } else {
3946 this._defaults[name] = defaultValue;
3947 }
3948
3949 this._checkWaitingList(name);
3950
3951 return name;
3952 }
3953 /**
3954 * Chains an observable instance object to an other nested observable
3955 * @param {String} name Target option name
3956 * @param {Observable} observableObject Observable instance object
3957 */
3958
3959
3960 chain(name, observableObject) {
3961 if (this._aliases[name]) {
3962 name = this._aliases[name];
3963 }
3964
3965 const nested = this._isNested(name);
3966
3967 if (nested) {
3968 nested.options.chain(nested.name, observableObject);
3969 return;
3970 }
3971
3972 if (this._options[name] instanceof Observable) {
3973 const val = this._options[name];
3974 Object.assign(observableObject._aliases, val._aliases);
3975 Object.assign(observableObject._waitings, val._waitings);
3976 Object.assign(observableObject._defaults, val._defaults);
3977 Object.keys(val._observers).forEach(key => {
3978 if (Object.prototype.hasOwnProperty.call(observableObject._observers, key)) {
3979 observableObject._observers[key].concat(val._observers[key]);
3980 } else {
3981 observableObject._observers[key] = val._observers[key];
3982 }
3983 });
3984 Object.keys(val._options).forEach(key => {
3985 if (val._options[key] instanceof Observable && observableObject._options[key]) {
3986 val.chain(key, observableObject._options[key]);
3987 } else {
3988 observableObject._options[key] = val._options[key];
3989 }
3990 });
3991 observableObject.register(observableObject._defaults);
3992 }
3993
3994 this._options[name] = observableObject;
3995 }
3996 /**
3997 * Creates a new alias for an existing option
3998 * @param {String} alias Alias name
3999 * @param {String} option Target option name
4000 */
4001
4002
4003 alias(alias, option) {
4004 if (this.has(alias)) {
4005 throw new Error(`"${alias}" is already an option.`);
4006 }
4007
4008 if (this._aliases[alias]) {
4009 throw new Error(`"${alias}" is already created.`);
4010 }
4011
4012 if (!this.has(option)) {
4013 throw new Error(`"${alias}" is not registered. Register the option before defining any alias.`);
4014 }
4015
4016 this._aliases[alias] = option;
4017
4018 this._checkWaitingList(alias);
4019 }
4020 /**
4021 * Checks for option existence
4022 * @param {String} name Option name
4023 */
4024
4025
4026 has(name) {
4027 const nested = this._isNested(name);
4028
4029 if (nested) {
4030 return nested.options.has(nested.name);
4031 }
4032
4033 return has$1.call(this._options, name) || has$1.call(this._defaults, name);
4034 }
4035 /**
4036 * Checks whether option is equal to given value or not
4037 * @param {String} name Option name
4038 * @param {*} value Test value
4039 */
4040
4041
4042 is(name, value) {
4043 return this.get(name) === value;
4044 }
4045 /**
4046 * Gets option value(s)
4047 * @param {String|array} name Option name(s)
4048 */
4049
4050
4051 get(name) {
4052 if (Array.isArray(name)) {
4053 const values = {};
4054 name.forEach(key => {
4055 values[key] = this.get(key);
4056 });
4057 return values;
4058 }
4059
4060 if (this._aliases[name]) {
4061 name = this._aliases[name];
4062 }
4063
4064 const nested = this._isNested(name);
4065
4066 if (nested) {
4067 return nested.options.get(nested.name);
4068 }
4069
4070 if (has$1.call(this._options, name)) {
4071 return this._options[name];
4072 }
4073
4074 return this._defaults[name];
4075 }
4076 /**
4077 * Sets new value to the option
4078 * @param {String|Object} name Option name or an object of option and values
4079 * @param {*} value Option value
4080 * @param {Boolean} internal Whether call observers or not
4081 */
4082
4083
4084 set(name, value, internal = false) {
4085 if (typeof name === 'object') {
4086 Object.keys(name).forEach(optionName => this.set(optionName, name[optionName], internal));
4087 return true;
4088 }
4089
4090 if (this._aliases[name]) {
4091 name = this._aliases[name];
4092 }
4093
4094 const nested = this._isNested(name);
4095
4096 if (nested) {
4097 return nested.options.set(nested.name, value, internal);
4098 }
4099
4100 if (!this.has(name)) {
4101 return false;
4102 }
4103
4104 if (typeof value === 'object' && this._options[name] instanceof Observable) {
4105 this._options[name].set(value);
4106 } else {
4107 this._options[name] = value;
4108 }
4109
4110 if (!this._internalChange && !internal) {
4111 if (this._observers[name]) {
4112 this._observers[name].forEach(callback => callback(name, value));
4113 }
4114
4115 if (this._observers['*']) {
4116 this._observers['*'].forEach(callback => callback('*', value));
4117 }
4118 }
4119
4120 return true;
4121 }
4122 /**
4123 * Observes option changes
4124 * @param {String|Array} name Option name(s)
4125 * @param {Function} callback Observer function
4126 */
4127
4128
4129 observe(name, callback) {
4130 if (Array.isArray(name)) {
4131 name.forEach(optionName => this.observe(optionName, callback));
4132 return;
4133 }
4134
4135 if (name !== '*' && !this.has(name)) {
4136 throw new Error(`This option: "${name}" is not registered.`);
4137 }
4138
4139 const nested = this._isNested(name);
4140
4141 if (nested) {
4142 nested.options.observe(nested.name, callback);
4143 return;
4144 }
4145
4146 const value = this.get(name);
4147
4148 if (value instanceof Observable) {
4149 value.observe('*', callback);
4150 }
4151
4152 if (!this._observers[name]) {
4153 this._observers[name] = [];
4154 }
4155
4156 this._observers[name].push(callback);
4157 }
4158 /**
4159 * Removes observer of an option
4160 * @param {String|Array} name Option name(s)
4161 * @param {Function} callback Observer callback
4162 */
4163
4164
4165 dontObserve(name, callback) {
4166 if (Array.isArray(name)) {
4167 name.forEach(optionName => this.dontObserve(optionName, callback));
4168 return;
4169 }
4170
4171 const nested = this._isNested(name);
4172
4173 if (nested) {
4174 nested.options.dontObserve(nested.name, callback);
4175 return;
4176 }
4177
4178 const observers = this._observers[name];
4179
4180 if (observers.length) {
4181 observers.splice(observers.indexOf(callback), 1);
4182 }
4183 }
4184 /**
4185 * Stops observers
4186 */
4187
4188
4189 internalChange() {
4190 this._internalChange = true;
4191 }
4192 /**
4193 * Starts observers
4194 */
4195
4196
4197 endInternalChange() {
4198 this._internalChange = false;
4199 }
4200 /**
4201 * Gets aliases of given option
4202 * @param {String} option Option name
4203 */
4204
4205
4206 aliasesOf(option) {
4207 return Object.keys(this._aliases).filter(alias => this._aliases[alias] === option);
4208 }
4209 /**
4210 * Resets the option value to its default
4211 * @param {Name} name Option name
4212 * @param {Boolean} internal Whether call observers or not
4213 */
4214
4215
4216 reset(name, internal) {
4217 if (name === '*') {
4218 Object.keys(this._options).forEach(optionName => this.reset(optionName, internal));
4219 return;
4220 }
4221
4222 this._internalChange = internal;
4223
4224 const nested = this._isNested(name);
4225
4226 if (nested) {
4227 nested.options.reset(nested.name, internal);
4228 return;
4229 }
4230
4231 const value = this._options[name];
4232
4233 if (value !== undefined) {
4234 if (value instanceof Observable) {
4235 value.reset('*', internal);
4236 } else {
4237 this.set(name, this._defaults[name]);
4238 }
4239 }
4240
4241 this._internalChange = false;
4242 }
4243 /**
4244 * Returns all options as an object
4245 */
4246
4247
4248 toObject() {
4249 const obj = {};
4250 Object.keys(_objectSpread2(_objectSpread2({}, this._defaults), this._options)).forEach(name => {
4251 if (this._options[name] instanceof Observable) {
4252 obj[name] = this._options[name].toObject();
4253 } else {
4254 obj[name] = this.get(name);
4255 }
4256 });
4257 return obj;
4258 }
4259 /**
4260 * Get a list of all option and their info
4261 */
4262
4263
4264 list() {
4265 const list = [];
4266 Object.keys(_objectSpread2(_objectSpread2({}, this._defaults), this._options)).forEach(name => {
4267 if (this._options[name] instanceof Observable) {
4268 list.push({
4269 name,
4270 value: this._options[name].list()
4271 });
4272 } else {
4273 list.push({
4274 name,
4275 value: this._options[name],
4276 default: this._defaults[name],
4277 aliases: this.aliasesOf(name).toString(),
4278 observers: this._observers[name]
4279 });
4280 }
4281 });
4282 return list;
4283 }
4284 /**
4285 * Checks the waiting list for new option
4286 * @private
4287 */
4288
4289
4290 _checkWaitingList(name) {
4291 if (this._waitings[name] !== undefined) {
4292 this.set(name, this._waitings[name], true);
4293 this._waitings[name] = undefined;
4294 }
4295 }
4296 /**
4297 * Checks whether name is nested or not
4298 * @param {String} name Option name
4299 * @private
4300 */
4301
4302
4303 _isNested(name) {
4304 const dotIndex = name.indexOf('.');
4305
4306 if (dotIndex !== -1) {
4307 const optionVal = this.get(name.slice(0, dotIndex));
4308 return optionVal instanceof Observable ? {
4309 name: name.slice(dotIndex + 1),
4310 options: optionVal
4311 } : false;
4312 }
4313
4314 return false;
4315 }
4316
4317 }
4318
4319 /**
4320 * An abstract class defines a single interface to navigate in view
4321 * Do not make direct instance from this class
4322 */
4323
4324 class Navigator extends Emitter {
4325 /**
4326 * Constructs new Navigator
4327 * @param {MSScrollView} view
4328 * @param {Object} options Navigator options
4329 */
4330 constructor(view, options) {
4331 super();
4332 this.view = view;
4333 this.options = new Observable();
4334 this.options.register({
4335 animate: true,
4336 duration: 1,
4337 paginate: false,
4338 easing: undefined,
4339 start: 0,
4340 checkLoop: true
4341 });
4342 this.options.inject(options);
4343 this.currentIndex = 0;
4344 this.targetIndex = 0;
4345 this.count = -1;
4346 this.currentSectionIndex = 0;
4347 this.targetSectionIndex = 0;
4348 this.currentSectionIndexes = [];
4349 this.targetSectionIndexes = []; // this.view.on('sectionAdd', this.updateCurrentPosition, this);
4350 // this.view.on('sectionRemove', this.updateCurrentPosition, this);
4351 // changes to start index
4352
4353 if (this.options.get('start')) {
4354 this.goToIndex(this.options.get('start'), {
4355 animate: false
4356 }, true);
4357 }
4358 }
4359 /**
4360 * Navigates to the next section
4361 * @param {Object} params Navigation params, it overrides default options
4362 */
4363
4364
4365 next(params) {
4366 params = _objectSpread2(_objectSpread2({}, this.options.toObject()), params);
4367
4368 if (this.targetIndex + 1 >= this.count) {
4369 if (params.checkLoop && this.view.options.get('loop')) {
4370 this.goToIndex(0, params);
4371 } else {
4372 this.trigger('nextBlock');
4373 }
4374 } else {
4375 this.goToIndex(this.targetIndex + 1, params);
4376 }
4377 }
4378 /**
4379 * Navigates to the previous section
4380 * @param {Object} params Navigation params, it overrides default options
4381 */
4382
4383
4384 previous(params) {
4385 params = _objectSpread2(_objectSpread2({}, this.options.toObject()), params);
4386
4387 if (this.targetIndex - 1 < 0) {
4388 if (params.checkLoop && this.view.options.get('loop')) {
4389 this.goToIndex(this.count - 1, params);
4390 } else {
4391 this.trigger('previousBlock');
4392 }
4393 } else {
4394 this.goToIndex(this.targetIndex - 1, params);
4395 }
4396 }
4397 /**
4398 * Navigates to the given index
4399 * @param {Number} index Target index
4400 * @param {Object} params Navigation params, it overrides default options
4401 * @param {Boolean} force Whether skip index change check or not
4402 */
4403
4404
4405 goToIndex(index, params, force) {}
4406 /**
4407 * Updates the navigator manually
4408 */
4409
4410
4411 update() {
4412 this.updateTargetIndex(this.view.index);
4413 this.updateCurrentIndex();
4414 }
4415 /**
4416 * Checks whether index is in correct range
4417 * @param {Number} index
4418 * @param {Boolean} normalize Whether return in range index or not
4419 */
4420
4421
4422 checkIndex(index, normalize = true) {
4423 if (this.count === -1) {
4424 this.updateCount();
4425 }
4426
4427 if (normalize) {
4428 return Math.max(0, Math.min(index, this.count - 1));
4429 }
4430
4431 return index >= 0 && index < this.count;
4432 }
4433 /**
4434 * Calculates total page or section number
4435 */
4436
4437
4438 updateCount() {}
4439 /**
4440 * Updates target index
4441 * @param {Number} index Target index
4442 */
4443
4444
4445 updateTargetIndex(index) {
4446 [this.targetSectionIndex] = this.targetSectionIndexes;
4447
4448 if (this.targetIndex !== index) {
4449 this.targetIndex = index;
4450 this.trigger('changeStart', [this.targetIndex]);
4451 this.trigger('targetIndexChange', [this.targetIndex]);
4452 }
4453 }
4454 /**
4455 * Updates current index
4456 */
4457
4458
4459 updateCurrentIndex() {
4460 this.currentSectionIndex = this.view.index;
4461 this.currentSectionIndexes = this.view.indexes;
4462
4463 if (this.targetIndex !== this.currentIndex) {
4464 this.currentIndex = this.targetIndex;
4465 this.trigger('changeEnd', [this.currentIndex]);
4466 this.trigger('currentIndexChange', [this.currentIndex]);
4467 }
4468 }
4469 /**
4470 * Updates current position whenever a section adds to or removes from the view
4471 */
4472 // updateCurrentPosition() {
4473 // let index = this.targetIndex;
4474 // if (index >= this.view.count) {
4475 // index = this.view.count - 1;
4476 // }
4477 // this.goToIndex(index, { animate: false }, true);
4478 // }
4479
4480
4481 }
4482
4483 /*!
4484 * Copyright 2018 Averta
4485 * Friction and Spring classes are implemented based on Ralph Thomas's physics modules
4486 *
4487 * -------------------------------------------------------------------------
4488 * Copyright 2014 Ralph Thomas
4489 *
4490 * Licensed under the Apache License, Version 2.0 (the "License");
4491 * you may not use this file except in compliance with the License.
4492 * You may obtain a copy of the License at
4493 *
4494 * http://www.apache.org/licenses/LICENSE-2.0
4495 *
4496 * Unless required by applicable law or agreed to in writing, software
4497 * distributed under the License is distributed on an "AS IS" BASIS,
4498 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
4499 * See the License for the specific language governing permissions and
4500 * limitations under the License.
4501 */
4502 class Friction {
4503 constructor(drag) {
4504 this._drag = drag;
4505 this._dragLog = Math.log(drag);
4506 this._x = 0;
4507 this._v = 0;
4508 this._startTime = 0;
4509 }
4510
4511 set(x, v) {
4512 this._x = x;
4513 this._v = v;
4514 this._startTime = Date.now();
4515 }
4516
4517 x(dt) {
4518 if (dt === undefined) {
4519 dt = (Date.now() - this._startTime) / 1000;
4520 }
4521
4522 return this._x + this._v * this._drag ** dt / this._dragLog - this._v / this._dragLog;
4523 }
4524
4525 dx() {
4526 const dt = (Date.now() - this._startTime) / 1000;
4527 return this._v * this._drag ** dt;
4528 }
4529
4530 done() {
4531 return Math.abs(this.dx()) < 1;
4532 }
4533
4534 }
4535 const epsilon = 0.001;
4536
4537 function almostEqual(a, b, e) {
4538 return a > b - e && a < b + e;
4539 }
4540
4541 function almostZero(a, e) {
4542 return almostEqual(a, 0, e);
4543 }
4544 /** *
4545 * Simple Spring implementation -- this implements a damped spring using a symbolic integration
4546 * of Hooke's law: F = -kx - cv. This solution is significantly more performant and less code than
4547 * a numerical approach such as Facebook Rebound which uses RK4.
4548 *
4549 * This physics textbook explains the model:
4550 * http://www.stewartcalculus.com/data/CALCULUS%20Concepts%20and%20Contexts/upfiles/3c3-AppsOf2ndOrders_Stu.pdf
4551 *
4552 * A critically damped spring has: damping*damping - 4 * mass * springConstant === 0. If it's greater than zero
4553 * then the spring is overdamped, if it's less than zero then it's underdamped.
4554 */
4555
4556
4557 class Spring {
4558 constructor(mass, springConstant, damping) {
4559 this._m = mass;
4560 this._k = springConstant;
4561 this._c = damping;
4562 this._solution = null;
4563 this._endPosition = 0;
4564 this._startTime = 0;
4565 }
4566
4567 _solve(initial, velocity) {
4568 const c = this._c;
4569 const m = this._m;
4570 const k = this._k; // Solve the quadratic equation; root = (-c +/- sqrt(c^2 - 4mk)) / 2m.
4571
4572 const cmk = c * c - 4 * m * k;
4573
4574 if (cmk === 0) {
4575 // The spring is critically damped.
4576 // x = (c1 + c2*t) * e ^(-c/2m)*t
4577 const r = -c / (2 * m);
4578 const c1 = initial;
4579 const c2 = velocity / (r * initial);
4580 return {
4581 x(t) {
4582 return (c1 + c2 * t) * Math.E ** (r * t);
4583 },
4584
4585 dx(t) {
4586 const pow = Math.E ** (r * t);
4587 return r * (c1 + c2 * t) * pow + c2 * pow;
4588 }
4589
4590 };
4591 }
4592
4593 if (cmk > 0) {
4594 // The spring is overdamped; no bounces.
4595 // x = c1*e^(r1*t) + c2*e^(r2t)
4596 // Need to find r1 and r2, the roots, then solve c1 and c2.
4597 const r1 = (-c - Math.sqrt(cmk)) / (2 * m);
4598 const r2 = (-c + Math.sqrt(cmk)) / (2 * m);
4599 const c2 = (velocity - r1 * initial) / (r2 - r1);
4600 const c1 = initial - c2;
4601 return {
4602 x(t) {
4603 return c1 * Math.E ** (r1 * t) + c2 * Math.E ** (r2 * t);
4604 },
4605
4606 dx(t) {
4607 return c1 * r1 * Math.E ** (r1 * t) + c2 * r2 * Math.E ** (r2 * t);
4608 }
4609
4610 };
4611 } // The spring is underdamped, it has imaginary roots.
4612 // r = -(c / 2*m) +- w*i
4613 // w = sqrt(4mk - c^2) / 2m
4614 // x = (e^-(c/2m)t) * (c1 * cos(wt) + c2 * sin(wt))
4615
4616
4617 const w = Math.sqrt(4 * m * k - c * c) / (2 * m);
4618 const r = -(c / 2 * m);
4619 const c1 = initial;
4620 const c2 = (velocity - r * initial) / w;
4621 return {
4622 x(t) {
4623 return Math.E ** (r * t) * (c1 * Math.cos(w * t) + c2 * Math.sin(w * t));
4624 },
4625
4626 dx(t) {
4627 const power = Math.E ** (r * t);
4628 const cos = Math.cos(w * t);
4629 const sin = Math.sin(w * t);
4630 return power * (c2 * w * cos - c1 * w * sin) + r * power * (c2 * sin + c1 * cos);
4631 }
4632
4633 };
4634 }
4635
4636 x(dt) {
4637 if (dt === undefined) dt = (Date.now() - this._startTime) / 1000.0;
4638 return this._solution ? this._endPosition + this._solution.x(dt) : 0;
4639 }
4640
4641 dx(dt) {
4642 if (dt === undefined) dt = (Date.now() - this._startTime) / 1000.0;
4643 return this._solution ? this._solution.dx(dt) : 0;
4644 }
4645
4646 setEnd(x, velocity, t) {
4647 if (!t) t = Date.now();
4648 if (x === this._endPosition && almostZero(velocity, epsilon)) return;
4649 velocity = velocity || 0;
4650 let position = this._endPosition;
4651
4652 if (this._solution) {
4653 // Don't whack incoming velocity.
4654 if (almostZero(velocity, epsilon)) velocity = this._solution.dx((t - this._startTime) / 1000.0);
4655 position = this._solution.x((t - this._startTime) / 1000.0);
4656 if (almostZero(velocity, epsilon)) velocity = 0;
4657 if (almostZero(position, epsilon)) position = 0;
4658 position += this._endPosition;
4659 }
4660
4661 if (this._solution && almostZero(position - x, epsilon) && almostZero(velocity, epsilon)) {
4662 return;
4663 }
4664
4665 this._endPosition = x;
4666 this._solution = this._solve(position - this._endPosition, velocity);
4667 this._startTime = t;
4668 }
4669
4670 snap(x) {
4671 this._startTime = Date.now();
4672 this._endPosition = x;
4673 this._solution = {
4674 x() {
4675 return 0;
4676 },
4677
4678 dx() {
4679 return 0;
4680 }
4681
4682 };
4683 }
4684
4685 done(t) {
4686 return almostEqual(this.x(), this._endPosition, epsilon) && almostZero(this.dx(), epsilon);
4687 }
4688
4689 springConstant() {
4690 return this._k;
4691 }
4692
4693 damping() {
4694 return this._c;
4695 }
4696
4697 }
4698
4699 /* eslint-disable import/prefer-default-export */
4700 class FrictionMotion {
4701 constructor(drag) {
4702 this._drag = drag;
4703 this._x = 0;
4704 this._startTime = 0;
4705 }
4706
4707 set(x, end) {
4708 this._x = x;
4709 this._end = end;
4710 }
4711
4712 x(dt) {
4713 if (dt) {
4714 return this._end;
4715 }
4716
4717 this._x += (this._end - this._x) * this._drag;
4718 return this._x;
4719 }
4720
4721 dx() {
4722 return this._x - this._end;
4723 }
4724
4725 done() {
4726 return Math.abs(this.dx()) < 1;
4727 }
4728
4729 }
4730
4731 /**
4732 * This class is useful for creating interactive and interruptive UI animations
4733 * It animates the position value by adding friction to value changes over time.
4734 * Moreover, this class supports constraints which are useful to constraint
4735 * position changes at specific value ranges
4736 *
4737 * @author Averta [averta.net]
4738 */
4739
4740 class Slicker extends Emitter {
4741 /**
4742 * Creates new Slicker instance
4743 * @param {Number} friction Specifies the friction of value changes
4744 */
4745 constructor(friction = 0.01) {
4746 super();
4747 this._position = 0;
4748 this.animating = false;
4749 this._constraints = [];
4750 this._friction = new Friction(friction);
4751 this._frictionVal = friction;
4752 this.startPosition = null;
4753 this._tickerId = null;
4754 this._tick = this._tick.bind(this);
4755 this.eventPrefix = 'slicker';
4756 }
4757 /**
4758 * Gets the friction value
4759 */
4760
4761
4762 get friction() {
4763 return this._frictionVal;
4764 }
4765 /**
4766 * Sets the friction value
4767 */
4768
4769
4770 set friction(value) {
4771 if (value !== this._frictionVal) {
4772 this._friction = new Friction(value);
4773 this._frictionVal = value;
4774 }
4775 }
4776 /**
4777 * Gets the current position value
4778 */
4779
4780
4781 get position() {
4782 return this._position;
4783 }
4784 /**
4785 * Sets the position value
4786 */
4787
4788
4789 set position(value) {
4790 if (value === this._position) {
4791 return;
4792 }
4793
4794 if (this.startPosition === null) {
4795 this.startPosition = value;
4796 }
4797
4798 this._currentConstraint = this.findConstraint(value);
4799
4800 this._updatePosition(value);
4801 }
4802
4803 moveToPosition(position, friction = 0.5) {
4804 if (this._position === position) {
4805 return;
4806 }
4807
4808 if (this.startPosition === null) {
4809 this.startPosition = position;
4810 }
4811
4812 this._velocity = NaN;
4813 this._activeMotion = new FrictionMotion(friction);
4814
4815 this._activeMotion.set(this._position, position);
4816
4817 this._startAnimation();
4818 }
4819 /**
4820 * Gets current velocity
4821 */
4822
4823
4824 get velocity() {
4825 if (this._activeMotion) {
4826 return this._activeMotion.dx();
4827 }
4828
4829 return 0;
4830 }
4831 /**
4832 * Sets new velocity
4833 */
4834
4835
4836 set velocity(value) {
4837 if (this._velocity === value) {
4838 return;
4839 }
4840
4841 this._velocity = value;
4842
4843 this._friction.set(this._position, this._velocity);
4844
4845 this._activeMotion = this._friction;
4846
4847 const endPosition = this._friction.x(120);
4848
4849 this._targetConstraint = this.findConstraint(endPosition) || null;
4850
4851 if (this._targetConstraint) {
4852 this._currentConstraint = null;
4853
4854 this._animToConstraint(this._targetConstraint, this._position, endPosition, this._velocity);
4855
4856 this.startPosition = null;
4857 return;
4858 }
4859
4860 this._startAnimation();
4861
4862 this.trigger('push', [this._velocity], true);
4863 }
4864 /**
4865 * Stops the animation
4866 */
4867
4868
4869 stop() {
4870 this.startPosition = this._position;
4871 this.animating = false;
4872
4873 this._tick();
4874
4875 this.trigger('motionInterrupt', null, true);
4876 }
4877 /**
4878 * Animates to constraint if any found at current position
4879 */
4880
4881
4882 release(fast) {
4883 if (this._currentConstraint) {
4884 if (fast) {
4885 this._goToConstraint(this._currentConstraint, this._position, null, this._velocity);
4886 } else {
4887 this._animToConstraint(this._currentConstraint, this._position, null, this._velocity);
4888
4889 this.trigger('motionToConstraint', null, true);
4890 }
4891 }
4892 }
4893 /**
4894 * Adds new constraint
4895 * @param {Constraint} constraint
4896 */
4897
4898
4899 addConstraint(constraint) {
4900 constraint.slicker = this;
4901
4902 this._constraints.push(constraint);
4903 }
4904 /**
4905 * Removes given constraint
4906 * @param {Constraint} constraint
4907 */
4908
4909
4910 removeConstraint(constraint) {
4911 const index = this._constraints.indexOf(constraint);
4912
4913 if (index === -1) {
4914 return;
4915 }
4916
4917 this._constraints = this._constraints.splice(index, 1);
4918 }
4919
4920 removeConstraints() {
4921 this._currentConstraint = null;
4922 this._constraints = [];
4923 }
4924 /**
4925 * Returns all active constrains at given position
4926 * @param {Number} position
4927 */
4928
4929
4930 findConstraint(position) {
4931 if (!this._constraints.length) {
4932 return false;
4933 }
4934
4935 const violations = this._constraints.filter(constraint => constraint.isActive(this._position, position, this.velocity));
4936
4937 if (!violations.length) {
4938 return false;
4939 }
4940
4941 return violations.sort((a, b) => {
4942 const bp = b.getPriority(this._position, position, this.velocity);
4943 const ap = a.getPriority(this._position, position, this.velocity);
4944
4945 if (bp === 'important') {
4946 return 1;
4947 }
4948
4949 if (ap === 'important') {
4950 return -1;
4951 }
4952
4953 return b.priority - a.priority;
4954 })[0];
4955 }
4956 /**
4957 * Updates position
4958 * @private
4959 * @param {Number} value
4960 */
4961
4962
4963 _updatePosition(value) {
4964 const delta = value - this._position;
4965 this._position = value;
4966
4967 if (this._currentConstraint) {
4968 this._position -= (1 - this._currentConstraint.activeFactor) * delta;
4969 }
4970
4971 this.trigger('positionChange', [this._position], true);
4972 }
4973 /**
4974 * Starts the animation
4975 */
4976
4977
4978 _startAnimation() {
4979 if (this.animating) {
4980 return;
4981 }
4982
4983 this.animating = true;
4984 this.trigger('animationStart', null, true);
4985
4986 if (this._activeMotion !== this._friction) {
4987 this.trigger('constraintAnimationStart', null, true);
4988 }
4989
4990 const endPos = Math.round(this._activeMotion.x(120) * 100) / 100;
4991
4992 if (this.endPosition !== endPos) {
4993 this.trigger('endPositionChange', [endPos], true);
4994 }
4995
4996 this._tick();
4997 }
4998 /**
4999 * The animation ticker function
5000 */
5001
5002
5003 _tick() {
5004 if (this.animating) {
5005 if (this._activeMotion.done()) {
5006 this.animating = false;
5007
5008 this._updatePosition(Math.round(this._position * 100) / 100);
5009
5010 this._tick();
5011
5012 this.trigger('animationEnd', null, true);
5013
5014 if (this._activeMotion !== this._friction) {
5015 this.trigger('constraintAnimationEnd', null, true);
5016 }
5017
5018 return;
5019 }
5020
5021 this._updatePosition(this._activeMotion.x());
5022
5023 this._tickerId = requestAnimationFrame(this._tick);
5024 } else {
5025 cancelAnimationFrame(this._tickerId);
5026 this._velocity = 0;
5027 this._targetConstraint = null;
5028 this._currentConstraint = this.findConstraint(this._position);
5029 }
5030 }
5031 /**
5032 * Animates to constraint
5033 * @param {Constraint} constraint
5034 * @param {Number} position
5035 * @param {Number} endPosition
5036 * @param {Number} velocity
5037 */
5038
5039
5040 _animToConstraint(constraint, position, endPosition, velocity) {
5041 constraint.set(this.startPosition, position, endPosition, velocity);
5042 this._activeMotion = constraint.motion;
5043
5044 this._startAnimation();
5045 }
5046 /**
5047 * Instantly changes to constraint
5048 * @param {Constraint} constraint
5049 * @param {Number} position
5050 * @param {Number} endPosition
5051 * @param {Number} velocity
5052 */
5053
5054
5055 _goToConstraint(constraint, position, endPosition, velocity) {
5056 constraint.set(this.startPosition, position, endPosition, velocity);
5057 const endPos = Math.round(constraint.motion.x(120) * 100) / 100;
5058 this.trigger('endPositionChange', [endPos], true);
5059 this.position = endPos;
5060 this.trigger('animationEnd', null, true);
5061 }
5062
5063 }
5064
5065 /**
5066 * This class creates a constraint that checks the value by specifies operator
5067 */
5068
5069 class OperatorConstraint {
5070 /**
5071 * Creates new operator constraint instance
5072 * @param {String} operator Operator
5073 * @param {Number} value Active position value
5074 * @param {Object} options Constraint options including spring parameters
5075 */
5076 constructor(operator, value, options = {}) {
5077 this.value = value;
5078 this.operator = operator;
5079 this.activeFactor = 0.5;
5080 this.priority = 10;
5081 options = _objectSpread2({
5082 mass: 1,
5083 constant: 90,
5084 damping: 20,
5085 criticalDamping: false
5086 }, options);
5087
5088 if (options.criticalDamping) {
5089 options.damping = Math.sqrt(4 * options.mass * options.constant);
5090 }
5091
5092 this.spring = new Spring(options.mass, options.constant, options.damping);
5093 }
5094 /**
5095 * Gets the spring object
5096 */
5097
5098
5099 get motion() {
5100 return this.spring;
5101 }
5102 /**
5103 * Checks whether this constraint is active or not
5104 * @param {Number} position
5105 */
5106
5107
5108 isActive(position, endPosition) {
5109 switch (this.operator) {
5110 case '<=':
5111 return endPosition <= this.value;
5112
5113 case '>=':
5114 return endPosition >= this.value;
5115
5116 case '<':
5117 return endPosition < this.value;
5118
5119 case '>':
5120 default:
5121 return endPosition > this.value;
5122 }
5123 }
5124 /**
5125 * Sets values to spring
5126 * @param {Number} startPosition
5127 * @param {Number} position
5128 * @param {Number} endPosition
5129 * @param {Number} velocity
5130 */
5131
5132
5133 set(startPosition, position, endPosition, velocity) {
5134 this.spring.snap(position);
5135 this.spring.setEnd(this.value, velocity);
5136 }
5137
5138 getPriority() {
5139 return this.priority;
5140 }
5141
5142 }
5143
5144 /**
5145 * This class creates snapping constraint on slicker
5146 * When slicker moves inside snapping area it snaps to the closest point.
5147 * It also supports looped snapping area.
5148 */
5149
5150 class SnappingConstraint {
5151 /**
5152 * New snapping constraint instance
5153 * @param {Array} points A 2D array containing snap points and their size [snap point, snap size]
5154 * @param {Object} options Snapping constraint options
5155 */
5156 constructor(points = [], options = {}) {
5157 this.activeFactor = 1;
5158 this.priority = 20;
5159 this.points = points;
5160 this._activeRange = null;
5161 this.options = _objectSpread2({
5162 mass: 1,
5163 constant: 90,
5164 damping: 20,
5165 criticalDamping: false,
5166 paginate: true,
5167 loop: false
5168 }, options);
5169 options = _objectSpread2({}, options);
5170
5171 if (options.criticalDamping) {
5172 options.damping = Math.sqrt(4 * options.mass * options.constant);
5173 }
5174
5175 this.spring = new Spring(options.mass, options.constant, options.damping);
5176 }
5177 /**
5178 * Gets the active range
5179 */
5180
5181
5182 get activeRange() {
5183 return this._activeRange;
5184 }
5185 /**
5186 * Sets the active range
5187 */
5188
5189
5190 set activeRange(value) {
5191 this._activeRange = value;
5192 this.length = value[1] - value[0];
5193 }
5194 /**
5195 * Gets the spring object
5196 */
5197
5198
5199 get motion() {
5200 return this.spring;
5201 }
5202 /**
5203 * Finds the closest snap point to the given position
5204 * @param {Number} position
5205 */
5206
5207
5208 findPoint(position) {
5209 position = this.normalizePosition(position);
5210 let index = -1;
5211 this.points.some((point, i) => {
5212 index = i;
5213
5214 if (i !== this.points.length - 1) {
5215 return Math.abs(position - this.points[i + 1][0]) > Math.abs(position - point[0]);
5216 }
5217
5218 return true;
5219 });
5220
5221 if (this.options.loop && index === this.points.length - 1) {
5222 return Math.abs(position - this.activeRange[1]) > Math.abs(position - this.points[index][0]) ? index : 'end';
5223 }
5224
5225 return index;
5226 }
5227 /**
5228 * Recalculates the value base on length and moves it to the valid range
5229 * @param {Number} value Scroll position
5230 */
5231
5232
5233 normalizePosition(value) {
5234 if (this.options.loop) {
5235 value %= this.length || 1;
5236
5237 if (value < 0) {
5238 value += this.length;
5239 }
5240 } else {
5241 value = Math.max(0, Math.min(value, this.length));
5242 }
5243
5244 return value;
5245 }
5246 /**
5247 * Whether this constraint is active or not by considering the current position and end position of motion
5248 * @param {Number} position
5249 * @param {Number} endPosition
5250 */
5251
5252
5253 isActive(position, endPosition) {
5254 if (!this.activeRange) {
5255 return false;
5256 }
5257
5258 if (this.options.loop) {
5259 return true;
5260 }
5261
5262 return Math.max(position, endPosition) > this._activeRange[0] && Math.min(position, endPosition) < this._activeRange[1];
5263 }
5264 /**
5265 * Returns the priority of this constraint
5266 */
5267
5268
5269 getPriority() {
5270 return this.priority;
5271 }
5272 /**
5273 * Sets the constraint motion params
5274 * @param {Number} startPosition
5275 * @param {Number} position
5276 * @param {Number} endPosition
5277 * @param {Number} velocity
5278 */
5279
5280
5281 set(startPosition, position, endPosition, velocity) {
5282 let cycles = 0;
5283
5284 if (endPosition === null) {
5285 endPosition = position;
5286 }
5287
5288 if (this.options.paginate && velocity !== 0) {
5289 let pointIndex = this.findPoint(startPosition);
5290
5291 if (!this.options.loop) {
5292 startPosition = this.normalizePosition(startPosition);
5293 } else {
5294 cycles = Math.floor(startPosition / this.length);
5295
5296 if (pointIndex === 'end') {
5297 pointIndex = 0;
5298 cycles += 1;
5299 }
5300 }
5301
5302 const point = this.points[pointIndex];
5303
5304 if (velocity > 0) {
5305 endPosition = cycles * this.length + point[0] + point[1];
5306 } else if (velocity < 0) {
5307 let prevPointIndex = pointIndex - 1;
5308
5309 if (prevPointIndex === -1) {
5310 prevPointIndex = this.points.length - 1;
5311 }
5312
5313 endPosition = cycles * this.length + point[0] - this.points[prevPointIndex][1];
5314 }
5315
5316 if (!this.options.loop) {
5317 endPosition = this.normalizePosition(endPosition);
5318 }
5319 } else {
5320 let targetPoint = this.findPoint(endPosition);
5321
5322 if (this.options.loop) {
5323 cycles = Math.floor(endPosition / this.length);
5324
5325 if (targetPoint === 'end') {
5326 targetPoint = 0;
5327 cycles += 1;
5328 }
5329 } else {
5330 endPosition = this.normalizePosition(endPosition);
5331 }
5332
5333 endPosition = cycles * this.length + this.points[targetPoint][0];
5334 }
5335
5336 this.spring.snap(position);
5337 this.spring.setEnd(endPosition, velocity);
5338 }
5339
5340 }
5341
5342 /**
5343 * This class creates an interface to navigate trough section in a scroll view
5344 */
5345
5346 class ScrollNavigator extends Navigator {
5347 /**
5348 * Constructs new ScrollNavigator
5349 * @param {MSScrollView} view
5350 * @param {Object} options Navigator options
5351 */
5352 constructor(view, options = {}) {
5353 super(view, options);
5354 this.options.register({
5355 direction: 'auto',
5356 slicker: true,
5357 slickerFriction: 0.01,
5358 slickType: 'slide',
5359 // scroll, snap, slide
5360 updateIndexOnDrag: 'auto',
5361 boundariesSpring: {
5362 mass: 1,
5363 constant: 90,
5364 damping: 20,
5365 criticalDamping: false
5366 },
5367 snappingSpring: {
5368 mass: 1,
5369 constant: 90,
5370 damping: 20,
5371 criticalDamping: true
5372 }
5373 });
5374 this.options.inject(options);
5375 this.updateTargetIndex = this.updateTargetIndex.bind(this);
5376 this.updateCurrentIndex = this.updateCurrentIndex.bind(this);
5377 this.updateCount = this.updateCount.bind(this);
5378 this.view.on('arrange', this.updateCount, this);
5379 this.options.observe('paginate', this.updateCount);
5380 this.updateCount();
5381 this.setupSlicker();
5382 }
5383
5384 setupSlicker() {
5385 if (!this.options.get('slicker')) {
5386 return;
5387 }
5388
5389 this.updateSlicker = this.updateSlicker.bind(this);
5390 this.slicker = new Slicker();
5391 this.slicker.on('positionChange', this._onSlickerUpdate, this);
5392 this.slicker.on('endPositionChange', this._updateIndexBySlicker, this);
5393 this.slicker.on('animationEnd', this.updateCurrentIndex, this);
5394 this.slicker.on('push', () => this.trigger('externalEffect'));
5395 this.slicker.on('motionInterrupt', () => this.trigger('externalEffect')); // update slicker on option changes or on view sections update
5396
5397 this.options.observe(['slickType', 'boundariesSpring', 'snappingSpring', 'paginate'], this.updateSlicker);
5398 this.options.observe('slickerFriction', (name, value) => {
5399 this.slicker.friction = value;
5400 });
5401 this.view.options.observe('loop', this.updateSlicker);
5402 this.view.on('resize, sectionAdd, sectionRemove, lengthChange', this.updateSlicker);
5403 this.view.on('scrollToAnimationEnd', this.updateCurrentIndex, this);
5404 this.updateSlicker();
5405 }
5406 /**
5407 * Navigates to the next section
5408 * @param {Object} params Navigation params, it overrides default options
5409 */
5410
5411
5412 next(params) {
5413 super.next(_objectSpread2({
5414 direction: 'forward'
5415 }, params));
5416 }
5417 /**
5418 * Navigates to the previous section
5419 * @param {Object} params Navigation params, it overrides default options
5420 */
5421
5422
5423 previous(params) {
5424 super.previous(_objectSpread2({
5425 direction: 'backward'
5426 }, params));
5427 }
5428 /**
5429 * Drags the slicker by given value
5430 * @param {Number} value
5431 */
5432
5433
5434 drag(value) {
5435 this.view.killScrollAnimation();
5436
5437 if (this.slicker) {
5438 this.slicker.position += value;
5439
5440 if (this._updateIndexesOnDrag) {
5441 this._updateIndexBySlicker(null, this.slicker.position);
5442
5443 this.updateCurrentIndex();
5444 }
5445 }
5446 }
5447 /**
5448 * Adds velocity slicker
5449 * @param {Number} velocity
5450 */
5451
5452
5453 push(velocity) {
5454 this.view.killScrollAnimation();
5455
5456 if (this.slicker) {
5457 this.slicker.velocity = velocity;
5458 }
5459 }
5460 /**
5461 * Releases the slicer, it checks and moves to the related constraint if available
5462 */
5463
5464
5465 release(fast) {
5466 this.view.killScrollAnimation();
5467
5468 if (this.slicker) {
5469 this.slicker.position = this.view.position;
5470 this.slicker.release(fast);
5471 }
5472 }
5473 /**
5474 * Holds the slicker animation
5475 */
5476
5477
5478 hold() {
5479 this.view.killScrollAnimation();
5480
5481 if (this.slicker) {
5482 this.slicker.stop();
5483 }
5484 }
5485 /**
5486 * Navigates to the given index
5487 * @param {Number} index Target index
5488 * @param {Object} params Navigation params, it overrides default options
5489 * @param {Boolean} force Whether skip index change check or not
5490 */
5491
5492
5493 goToIndex(index, params, force = false) {
5494 index = this.checkIndex(index);
5495
5496 if (!force && index === this.targetIndex) {
5497 return;
5498 }
5499
5500 params = _objectSpread2(_objectSpread2({}, this.options.get(['animate', 'direction', 'duration', 'paginate', 'easing'])), params);
5501 const animParams = {};
5502
5503 if (params.easing) {
5504 animParams.easing = params.easing;
5505 }
5506
5507 const position = this.options.get('paginate') ? index * this.view.size : this.view.sections[index].position;
5508 this.updateTargetIndex(index, position);
5509 this.view.scrollTo(position, params.animate, params.duration, params.direction, animParams);
5510
5511 if (!params.animate) {
5512 this.updateCurrentIndex();
5513 }
5514 }
5515
5516 goToPosition(position, params) {
5517 const index = this.checkIndex(this.view.getIndexAtPosition(position));
5518 params = _objectSpread2(_objectSpread2({}, this.options.get(['animate', 'direction', 'duration', 'paginate', 'ease'])), params);
5519 const animParams = {};
5520
5521 if (params.ease) {
5522 animParams.ease = params.ease;
5523 }
5524
5525 this.updateTargetIndex(index, position);
5526
5527 if (params.useFriction) {
5528 this.slicker.position = this.view.normalizePositionByDirection(this.view.position);
5529 this.slicker.moveToPosition(this.view.normalizePositionByDirection(position), params.friction);
5530 return;
5531 }
5532
5533 this.view.scrollTo(position, params.animate, params.duration, params.direction, animParams);
5534
5535 if (!params.animate) {
5536 this.updateCurrentIndex();
5537 }
5538 }
5539 /**
5540 * Updates the navigator manually
5541 */
5542
5543
5544 update() {
5545 this.updateSlicker();
5546 this.updateTargetIndex(this.view.index, this.slicker.position);
5547 this.updateCurrentIndex();
5548 }
5549 /**
5550 * Updates target index
5551 * @param {Number} index Target index
5552 * @param {Number} position Current scroll view position
5553 */
5554
5555
5556 updateTargetIndex(index, position) {
5557 this.targetSectionIndexes = this.view.getIndexesAtPosition(position);
5558 super.updateTargetIndex(index);
5559 }
5560 /**
5561 * Updates current index
5562 */
5563
5564
5565 updateCurrentIndex() {
5566 if (this.slicker) {
5567 this.slicker.position = this.view.position;
5568 }
5569
5570 super.updateCurrentIndex();
5571 }
5572 /**
5573 * Calculates total page or section number
5574 */
5575
5576
5577 updateCount() {
5578 const count = this.options.get('paginate') ? Math.ceil(this.view.length / this.view.size) : this.view.count;
5579
5580 if (count !== this.count) {
5581 this.count = count;
5582 this.trigger('countChange', [this.count]);
5583 }
5584 }
5585 /**
5586 * Updates slicker based on options
5587 */
5588
5589
5590 updateSlicker() {
5591 const params = this.options.get(['slickType', 'slickerFriction', 'boundariesSpring', 'snappingSpring', 'paginate', 'updateIndexOnDrag']);
5592 const loop = this.view.options.get('loop'); // removes already added constraints
5593
5594 this.slicker.stop();
5595 this.slicker.removeConstraints(); // update friction
5596
5597 this.slicker.friction = params.slickerFriction; // update on drag
5598
5599 this._updateIndexesOnDrag = params.updateIndexOnDrag;
5600
5601 if (this._updateIndexesOnDrag === 'auto') {
5602 this._updateIndexesOnDrag = params.slickType === 'scroll';
5603 } // define snapping constraint
5604
5605
5606 if (params.slickType !== 'scroll') {
5607 let points = [];
5608 let activeRange;
5609
5610 if (params.paginate) {
5611 activeRange = [0, this.count * this.view.size];
5612
5613 for (let i = 0; i !== this.count; i += 1) {
5614 points.push([i * this.view.size, this.view.size]);
5615 }
5616 } else if (loop) {
5617 activeRange = [0, this.view.length];
5618 points = this.view.sections.map(section => [section.position, section.size]);
5619 } else {
5620 activeRange = [0, this.view.length - this.view.size];
5621 this.view.sections.some(section => {
5622 if (section.position < this.view.length - this.view.size) {
5623 points.push([section.position, section.size]);
5624 return false;
5625 }
5626
5627 points.push([this.view.length - this.view.size, this.view.size]);
5628 return true;
5629 });
5630 }
5631
5632 const snappingParams = _objectSpread2({
5633 loop,
5634 paginate: params.slickType === 'slide'
5635 }, params.snappingSpring.toObject());
5636
5637 const snappingConstraint = new SnappingConstraint(points, snappingParams);
5638 snappingConstraint.activeRange = activeRange;
5639 this.slicker.addConstraint(snappingConstraint);
5640 }
5641
5642 if (!loop) {
5643 const boundariesSpring = params.boundariesSpring.toObject();
5644 const startConstraint = new OperatorConstraint('<', 0, boundariesSpring);
5645 const endPoint = params.paginate ? (this.count - 1) * this.view.size : this.view.length - this.view.size;
5646 const endConstraint = new OperatorConstraint('>', endPoint, boundariesSpring);
5647 this.slicker.addConstraint(startConstraint);
5648 this.slicker.addConstraint(endConstraint);
5649 }
5650
5651 this.release(true);
5652 }
5653 /**
5654 * On slicker position change listener
5655 * @private
5656 */
5657
5658
5659 _onSlickerUpdate() {
5660 // update view position
5661 this.view.position = this.slicker.position;
5662 }
5663 /**
5664 * Updates the target index when end position changes in slicker
5665 * @private
5666 * @param {String} name Action name
5667 * @param {Number} value Target position value
5668 */
5669
5670
5671 _updateIndexBySlicker(name, value) {
5672 let targetIndex;
5673
5674 if (this.options.get('paginate')) {
5675 targetIndex = Math.ceil(Math.round(value / this.view.size)) % this.count;
5676 } else {
5677 targetIndex = this.view.getIndexAtPosition(value);
5678 }
5679
5680 this.updateTargetIndex(targetIndex, value);
5681 }
5682
5683 }
5684
5685 const viewClasses = {};
5686 const addonClasses = {};
5687 const controlClasses = {};
5688 /**
5689 * Composer class is responsible to gather all required parts and setups them in correct order.
5690 */
5691
5692 class Composer extends Emitter {
5693 /**
5694 * Registers new view to the composer
5695 * @param {String} name View name
5696 * @param {Class} viewClass View class
5697 */
5698 static registerView(name, viewClass) {
5699 if (has$1.call(viewClasses, name)) {
5700 throw new Error(`${name} is already registered.`);
5701 } else {
5702 viewClasses[name] = viewClass;
5703 }
5704 }
5705 /**
5706 * Registers new addon to the composer
5707 * @param {String} name Addon name
5708 * @param {Class} addonClass Addon class
5709 */
5710
5711
5712 static registerAddon(name, addonClass) {
5713 if (has$1.call(addonClasses, name)) {
5714 throw new Error(`${name} is already registered.`);
5715 } else {
5716 addonClasses[name] = addonClass;
5717 }
5718 }
5719 /**
5720 * Registers new control to the composer
5721 * @param {String} name Control name
5722 * @param {Class} controlClass Control class
5723 */
5724
5725
5726 static registerControl(name, controlClass) {
5727 if (has$1.call(controlClasses, name)) {
5728 throw new Error(`${name} is already registered.`);
5729 } else {
5730 controlClasses[name] = controlClass;
5731 }
5732 }
5733 /**
5734 * List of all registered views classes
5735 */
5736
5737
5738 static get views() {
5739 return viewClasses;
5740 }
5741 /**
5742 * List of all registered addons classes
5743 */
5744
5745
5746 static get addons() {
5747 return addonClasses;
5748 }
5749 /**
5750 * List of all registered controls classes
5751 */
5752
5753
5754 static get controls() {
5755 return controlClasses;
5756 }
5757 /**
5758 * Setups the composer
5759 * @param {String|Element} element The composer main element or single selector
5760 * @param {Object} options Composer options
5761 */
5762
5763
5764 setup(element, options = {}) {
5765 this.element = element;
5766 this.element.classList.add(`${prefix}-content-composer`);
5767 this.options = new Observable();
5768 this.options.register({
5769 sectionSelector: `.${prefix}-section`,
5770 excludeAddons: [],
5771 navigator: {},
5772 viewOptions: {},
5773 view: 'basic',
5774 sectionFit: 'cover'
5775 });
5776 this.trigger('beforeOptions', [options]);
5777 this.options.inject(options);
5778 this.initTrigger = new ActionTrigger(this._init.bind(this));
5779 this.readyTrigger = new ActionTrigger(this._ready.bind(this));
5780 this.element.classList.add(`${prefix}-on-setup`);
5781
5782 if (document.readyState === 'loading') {
5783 document.addEventListener('DOMContentLoaded', this._domReady.bind(this));
5784 } else {
5785 this._domReady();
5786 }
5787 }
5788 /**
5789 * Finds the composer element and setups addons after dom ready
5790 * @private
5791 */
5792
5793
5794 _domReady() {
5795 this.trigger('beforeDomReady');
5796 const {
5797 element
5798 } = this; // find element
5799
5800 if (typeof element === 'object' && element.nodeName) {
5801 this.element = element;
5802 } else if (typeof element === 'string') {
5803 this.element = document.querySelector(element);
5804 }
5805
5806 if (!this.element) {
5807 return;
5808 }
5809
5810 this._domReady = true;
5811 this.trigger('domReady', [this.element]);
5812
5813 this._setupAddons();
5814
5815 this.element.classList.remove(`${prefix}-on-setup`);
5816 this.element.classList.add(`${prefix}-dom-ready`);
5817 this.initTrigger.exec();
5818 }
5819 /**
5820 * Setups view, layout controller and sections
5821 * @private
5822 */
5823
5824
5825 _init() {
5826 this.trigger('beforeInit');
5827
5828 this._setupView();
5829
5830 this._setupLayout();
5831
5832 this._setupNavigator();
5833
5834 this._setupSections();
5835
5836 this.trigger('init');
5837 this.element.classList.add(`${prefix}-init`);
5838 this.readyTrigger.exec();
5839 }
5840 /**
5841 * Adds ready class name to the composer element
5842 * @private
5843 */
5844
5845
5846 _ready() {
5847 this.element.classList.add(`${prefix}-ready`);
5848 }
5849 /**
5850 * Setups all registered addons except those are excluded by options
5851 * @private
5852 */
5853
5854
5855 _setupAddons() {
5856 this.addons = {};
5857 const excludes = this.options.get('excludeAddons');
5858 this.trigger('beforeSetupAddons');
5859 Object.keys(addonClasses).forEach(addonName => {
5860 if (excludes.indexOf(addonName) === -1) {
5861 this.addons[addonName] = new addonClasses[addonName](this);
5862 }
5863 });
5864 this.trigger('afterSetupAddons');
5865 }
5866 /**
5867 * Setups the view
5868 * @private
5869 */
5870
5871
5872 _setupView() {
5873 this.trigger('beforeViewSetup');
5874 const ViewClass = viewClasses[this.options.get('view')];
5875 this.view = new ViewClass();
5876 this.options.chain('viewOptions', this.view.options);
5877 this.view.parentEmitter = this;
5878 this.view.appendTo(this.element);
5879 this.trigger('viewSetup', [this.view]);
5880 }
5881 /**
5882 * Setups layout controller
5883 * @private
5884 */
5885
5886
5887 _setupLayout() {
5888 this.trigger('beforeLayoutSetup');
5889 this.layoutController = new LayoutController(this, this.view, this.options);
5890 this.layoutController.parentEmitter = this;
5891 this.trigger('layoutSetup', [this.layoutController]);
5892 }
5893 /**
5894 * Setups the navigator
5895 * @private
5896 */
5897
5898
5899 _setupNavigator() {
5900 this.trigger('beforeNavigatorSetup');
5901
5902 if (this.view instanceof ScrollView) {
5903 // Scroll view setup
5904 this.hasScrollView = true;
5905 this.navigator = new ScrollNavigator(this.view);
5906 this.options.chain('navigator', this.navigator.options);
5907 this.navigator.parentEmitter = this;
5908 }
5909
5910 this.trigger('navigatorSetup', [this.navigator]);
5911 }
5912 /**
5913 * Finds all section in markup and append them to the view
5914 * @private
5915 */
5916
5917
5918 _setupSections() {
5919 this.trigger('beforeSectionsSetup');
5920 const sectionSelector = this.options.get('sectionSelector');
5921 this.element.querySelectorAll(`:scope > ${sectionSelector}`).forEach(element => {
5922 const section = new Section(element, this);
5923 section.parentEmitter = this;
5924 this.view.appendSection(section, false);
5925 });
5926
5927 if (this.view.sections.length) {
5928 this.view.update();
5929 this.navigator.update();
5930 }
5931
5932 this.trigger('sectionsSetup');
5933 }
5934
5935 }
5936
5937 // Idea given from x-tag project
5938 // https://github.com/x-tag
5939 const styles = window.getComputedStyle(document.documentElement, '');
5940 const pre = (Array.prototype.slice.call(styles).join('').match(/-(moz|webkit|ms)-/) || styles.OLink === '' && ['', 'o'])[1];
5941 const dom = 'WebKit|Moz|MS|O'.match(new RegExp('(' + pre + ')', 'i'))[1];
5942 const toJS = {
5943 moz: 'Moz',
5944 webkit: 'Webkit',
5945 o: 'O',
5946 ms: 'ms'
5947 };
5948 var CSSPrefix = {
5949 dom,
5950 lowercase: pre,
5951 css: '-' + pre + '-',
5952 js: toJS[pre]
5953 };
5954
5955 /**
5956 * This view locates sections in a row vertically or horizontally
5957 */
5958
5959 class PlaneView extends ScrollView {
5960 constructor() {
5961 super();
5962 this.options = new Observable();
5963 this.readOptions = this.readOptions.bind(this);
5964 this.options.observe(this.options.register({
5965 dir: 'h',
5966 reverse: false,
5967 space: 5,
5968 loop: false,
5969 instantActive: true
5970 }), this.readOptions);
5971 this.readOptions();
5972 }
5973 /**
5974 * Reads option values and updates view
5975 */
5976
5977
5978 readOptions() {
5979 const lastPositionProp = this._positionProp;
5980 const reverse = this.options.get('reverse');
5981 this._space = this.options.get('space');
5982 this._loop = this.options.get('loop');
5983 this._reverseFactor = reverse ? 1 : -1;
5984 this.activeEnteringSection = this.options.get('instantActive');
5985
5986 if (this.options.get('dir') === 'h') {
5987 this._sizeProp = 'width';
5988 this._offsetProp = 'offsetWidth';
5989 this._positionProp = reverse ? 'right' : 'left';
5990 this._transformProp = 'X';
5991 } else {
5992 this._sizeProp = 'height';
5993 this._offsetProp = 'offsetHeight';
5994 this._transformProp = 'Y';
5995 this._positionProp = reverse ? 'bottom' : 'top';
5996 } // update sections space value and reset location
5997
5998
5999 this.sections.forEach(section => {
6000 if (!section.hasCustomSpace) {
6001 section.space = this._space;
6002 }
6003
6004 section.element.style[lastPositionProp] = '';
6005 section.sizeReference = this._offsetProp;
6006 }); // update size value
6007
6008 this._size = this[this._sizeProp];
6009 this.update();
6010 }
6011 /**
6012 * Reads element dimension values and updates the properties
6013 */
6014
6015
6016 resize() {
6017 const isResized = super.resize();
6018
6019 if (isResized) {
6020 this.size = this[this._sizeProp];
6021 }
6022
6023 return isResized;
6024 }
6025 /**
6026 * Updates sections offset and position values
6027 */
6028
6029
6030 update(arrange = true) {
6031 super.update(arrange);
6032
6033 if (this._paintScheduled) {
6034 return;
6035 }
6036
6037 this._paintScheduled = true;
6038 requestAnimationFrame(() => {
6039 this.sections.forEach(section => this.locateSection(section));
6040 this.sectionsContainer.style[`${CSSPrefix.js}Transform`] = 'translate' + this._transformProp + '(' + this._position * this._reverseFactor + 'px)';
6041 this._paintScheduled = false;
6042 });
6043 }
6044 /**
6045 * Locates the section in view element
6046 * @param {NSSection} section
6047 */
6048
6049
6050 locateSection(section) {
6051 section.element.style[this._positionProp] = `${section.offset}px`;
6052 }
6053 /**
6054 * Updates section properties
6055 * @private
6056 * @param {MSSection} section
6057 */
6058
6059
6060 _afterSectionAdd(section) {
6061 if (!section.customSpace) {
6062 section.space = this._space;
6063 }
6064
6065 super._afterSectionAdd(section);
6066 }
6067
6068 } // Register view in the composer
6069
6070 Composer.registerView('basic', PlaneView);
6071
6072 const defaultViewOptions = {
6073 transform: {
6074 translateX: [0, 0],
6075 translateY: [0, 0],
6076 translateZ: [0, 0],
6077 rotateX: [0, 0],
6078 rotateY: [0, 0],
6079 rotateZ: [0, 0],
6080 scale: [1, 1],
6081 skewX: [0, 0],
6082 skewY: [0, 0]
6083 },
6084 opacity: [1, 1],
6085 limitDistance: false,
6086 limitOpacity: false,
6087 ease: null
6088 };
6089 const transformUnits = {
6090 translateX: 'px',
6091 translateY: 'px',
6092 translateZ: 'px',
6093 rotateX: 'deg',
6094 rotateY: 'deg',
6095 rotateZ: 'deg',
6096 skewY: 'deg',
6097 skewX: 'deg'
6098 };
6099
6100 const getSectionTransformStyles = (distance, transformOptions) => {
6101 const options = _objectSpread2(_objectSpread2(_objectSpread2({}, defaultViewOptions), transformOptions), {}, {
6102 transform: _objectSpread2(_objectSpread2({}, defaultViewOptions.transform), transformOptions.transform)
6103 });
6104
6105 let absDistance = Math.abs(distance);
6106 let transformString = '';
6107
6108 if (options.limitDistance) {
6109 absDistance = Math.min(absDistance, 1);
6110 }
6111
6112 const d = distance < 0 ? 0 : 1;
6113 let opacity = 1;
6114 Object.entries(options.transform).forEach(([prop, value]) => {
6115 const unit = transformUnits[prop] || '';
6116
6117 if (prop === 'scale') {
6118 if (value[d] !== 1) {
6119 const scaleNormalize = Math.abs(value[d] ** absDistance);
6120 transformString += 'scale(' + scaleNormalize + ') ';
6121 }
6122 } else if (value[d]) {
6123 transformString += prop + '(' + absDistance * value[d] + unit + ') ';
6124 }
6125 });
6126
6127 if (options.opacity[d] < 1) {
6128 if (options.limitOpacity && absDistance > 1) {
6129 opacity = 0;
6130 } else {
6131 opacity = 1 - Math.min(absDistance, 1 - options.opacity[d]);
6132 }
6133 }
6134
6135 return {
6136 opacity,
6137 transform: transformString
6138 };
6139 };
6140
6141 const presets = {
6142 fadeBasic: {
6143 className: `${prefix}-fade-basic-view`,
6144 opacity: [0.4, 0.4]
6145 },
6146 wave: {
6147 className: `${prefix}-wave-view`,
6148 transform: {
6149 translateZ: [-300, -300]
6150 }
6151 },
6152 fadeWave: {
6153 className: `${prefix}-fade-wave-view`,
6154 opacity: [0.6, 0.6],
6155 transform: {
6156 scale: [0.875, 0.875]
6157 }
6158 },
6159
6160 flow(options) {
6161 return {
6162 className: `${prefix}-flow-view`,
6163 transform: _objectSpread2(_objectSpread2(_objectSpread2({}, options.dir === 'h' && {
6164 rotateY: [-30, 30]
6165 }), options.dir === 'v' && {
6166 rotateX: [-30, 30]
6167 }), {}, {
6168 translateZ: [-600, -600]
6169 })
6170 };
6171 },
6172
6173 fadeFlow(options) {
6174 return {
6175 className: `${prefix}-fade-flow-view`,
6176 opacity: [0.6, 0.6],
6177 transform: _objectSpread2(_objectSpread2(_objectSpread2({}, options.dir === 'h' && {
6178 rotateY: [-50, 50]
6179 }), options.dir === 'v' && {
6180 rotateX: [-50, 50]
6181 }), {}, {
6182 translateZ: [-100, 100]
6183 })
6184 };
6185 }
6186
6187 };
6188 /**
6189 * This view locates sections in a row vertically or horizontally and applies transform object to each section
6190 */
6191
6192 class TransFormView extends PlaneView {
6193 constructor() {
6194 super();
6195 this.options.register({
6196 transformStyle: 'flow'
6197 });
6198 this.on('elementAppend', () => {
6199 const options = this.options.toObject();
6200 this.transformOptions = typeof presets[options.transformStyle] === 'function' ? presets[options.transformStyle](options) : presets[options.transformStyle];
6201 this.element.classList.add(`${prefix}-transform-view`);
6202 this.element.classList.add(this.transformOptions.className);
6203 });
6204 }
6205 /**
6206 * Locates the section in view element
6207 * @param {NSSection} section
6208 */
6209
6210
6211 locateSection(section) {
6212 section.element.style[this._positionProp] = `${section.offset}px`;
6213 const styles = getSectionTransformStyles(section.pendingOffset / this.size, this.transformOptions);
6214 section.element.style.transform = styles.transform;
6215 section.element.style.opacity = styles.opacity;
6216 }
6217
6218 } // Register view in the composer
6219
6220 Composer.registerView('transform', TransFormView);
6221
6222 /**
6223 * This view locates sections over each other and applies transform object to each section
6224 */
6225
6226 class BaseStackView extends PlaneView {
6227 update(arrange = true) {
6228 this._sectionsCount = this.sections.length;
6229
6230 if (arrange) {
6231 this.arrange();
6232 }
6233
6234 this.locateInLoop();
6235 this.updateStatusAndIndex();
6236 this.trigger('update', [this._position], true);
6237 this._paintScheduled = true;
6238 requestAnimationFrame(() => {
6239 this.sections.forEach(section => this.locateSection(section));
6240 this._paintScheduled = false;
6241 });
6242 }
6243 /**
6244 * Locates the section in view element and adds z-index
6245 * @param {NSSection} section
6246 */
6247
6248
6249 locateSection(section) {
6250 section.element.style.zIndex = this.count - Math.abs(Math.ceil(section.pendingOffset / this.size));
6251 }
6252
6253 } // Register view in the composer
6254
6255 Composer.registerView('baseStack', BaseStackView);
6256
6257 /**
6258 * This view locates sections over each other and applies transform object to each section
6259 */
6260
6261 class StackView extends BaseStackView {
6262 constructor() {
6263 super();
6264 this.element.classList.add(`${prefix}-stack-view`);
6265 this.options.register({
6266 scaleFactor: 0.2
6267 });
6268 this.on('elementAppend', () => {
6269 this.scaleFactor = this.options.get('scaleFactor');
6270 });
6271 }
6272 /**
6273 * Locates the section in view element and adds z-index
6274 * @param {NSSection} section
6275 */
6276
6277
6278 locateSection(section) {
6279 const distance = section.pendingOffset / this.size;
6280 const absDistance = Math.abs(distance);
6281 super.locateSection(section);
6282
6283 if (absDistance < 1) {
6284 section.element.style.visibility = '';
6285
6286 if (distance < 0) {
6287 section.element.style.transform = 'scale(' + (1 - absDistance * this.scaleFactor) + ')';
6288 } else {
6289 section.element.style.transform = `translate${this._transformProp}(${-absDistance * this.size}px)`;
6290 section.element.style.zIndex = 1000;
6291 }
6292
6293 section.element.classList.remove(`${prefix}-section-hidden`);
6294 } else {
6295 section.element.classList.add(`${prefix}-section-hidden`);
6296 }
6297 }
6298
6299 } // Register view in the composer
6300
6301 Composer.registerView('stack', StackView);
6302
6303 /**
6304 * This view locates sections over each other and fades each section
6305 */
6306
6307 class FadeView extends BaseStackView {
6308 constructor() {
6309 super();
6310 this.element.classList.add(`${prefix}-fade-view`);
6311 }
6312 /**
6313 * Locates the section in view element and adds z-index
6314 * @param {NSSection} section
6315 */
6316
6317
6318 locateSection(section) {
6319 const distance = section.pendingOffset / this.size;
6320 const absDistance = Math.abs(distance);
6321 super.locateSection(section);
6322
6323 if (absDistance < 1) {
6324 section.element.style.opacity = 1 - absDistance;
6325 section.element.classList.remove(`${prefix}-section-hidden`);
6326 } else {
6327 section.element.classList.add(`${prefix}-section-hidden`);
6328 }
6329 }
6330
6331 } // Register view in the composer
6332
6333 Composer.registerView('fade', FadeView);
6334
6335 /**
6336 * This view locates sections over each other and applies transform object to each section
6337 */
6338
6339 class MaskView extends BaseStackView {
6340 constructor() {
6341 super();
6342 this.element.classList.add(`${prefix}-mask-view`);
6343 this.options.register({
6344 maskParallax: 0.8
6345 });
6346 this.on('elementAppend', () => {
6347 this.maskParallax = this.options.get('maskParallax');
6348 });
6349 this.on('sectionAdd', this._wrapSection.bind(this));
6350 }
6351
6352 _wrapSection(action, section) {
6353 const sectionMask = document.createElement('div');
6354 sectionMask.classList.add(`${prefix}-section-mask`);
6355 section.element.parentElement.insertBefore(sectionMask, section.element);
6356 sectionMask.appendChild(section.element);
6357 section.maskElement = sectionMask;
6358 }
6359 /**
6360 * Locates the section in view element and adds z-index
6361 * @param {NSSection} section
6362 */
6363
6364
6365 locateSection(section) {
6366 const distance = section.pendingOffset / this.size;
6367 const absDistance = Math.abs(distance);
6368 super.locateSection(section);
6369
6370 if (absDistance < 1) {
6371 section.element.style.visibility = '';
6372 section.maskElement.style.transform = `translate${this._transformProp}(${-distance * this.size}px)`;
6373 section.element.style.transform = `translate${this._transformProp}(${distance * this.size * this.maskParallax}px)`;
6374 section.element.classList.remove(`${prefix}-section-hidden`);
6375 } else {
6376 section.element.classList.add(`${prefix}-section-hidden`);
6377 }
6378 }
6379
6380 } // Register view in the composer
6381
6382 Composer.registerView('mask', MaskView);
6383
6384 /**
6385 * This view locates sections over each other and applies transform object to each section
6386 */
6387
6388 class CubeView extends BaseStackView {
6389 constructor() {
6390 super();
6391 this.element.classList.add(`${prefix}-cube-view`);
6392 this.options.register({
6393 shadow: 0.8,
6394 dolly: 500
6395 });
6396 this.on('elementAppend', () => {
6397 this._rotateAxis = this.options.get('dir') === 'h' ? 'rotateY' : 'rotateX';
6398 this._rotateDir = this.options.get('dir') === 'h' ? -1 : 1;
6399 this._shadow = this.options.get('shadow');
6400 this._dolly = this.options.get('dolly');
6401 });
6402 }
6403
6404 update(arrange = true) {
6405 this._sectionsCount = this.sections.length;
6406
6407 if (arrange) {
6408 this.arrange();
6409 }
6410
6411 this.locateInLoop();
6412 this.updateStatusAndIndex();
6413 this.trigger('update', [this._position], true);
6414 this._paintScheduled = true;
6415 requestAnimationFrame(() => {
6416 this.sections.forEach(section => this.locateSection(section));
6417 this._paintScheduled = false;
6418 });
6419 }
6420 /**
6421 * Locates the section in view element and adds z-index
6422 * @param {NSSection} section
6423 */
6424
6425
6426 locateSection(section) {
6427 const distance = section.pendingOffset / this.size;
6428 const absDistance = Math.abs(distance);
6429 super.locateSection(section);
6430
6431 if (absDistance < 1) {
6432 section.element.style.visibility = '';
6433 section.element.style.transform = this._rotateAxis + '(' + distance * this._rotateDir * 90 + 'deg)';
6434 section.element.style.transformOrigin = '50% 50% -' + this.size / 2 + 'px';
6435 if (this._shadow) section.element.style.filter = `brightness(${1 - absDistance * this._shadow})`;
6436 section.element.classList.remove(`${prefix}-section-hidden`);
6437
6438 if (this._dolly && distance > 0) {
6439 this.sectionsContainer.style.transform = `translateZ(${-this._dolly / 2 + Math.abs(absDistance - 0.5) * this._dolly}px)`;
6440 }
6441 } else {
6442 section.element.classList.add(`${prefix}-section-hidden`);
6443 }
6444 }
6445
6446 } // Register view in the composer
6447
6448 Composer.registerView('cube', CubeView);
6449
6450 const layerClasses = {};
6451 /**
6452 * Layers class holds and setups layers.
6453 * It appends layers element depends on their wrap and position type to main, wrap or fixed layers containers.
6454 */
6455
6456 class Layers extends Emitter {
6457 /**
6458 * Registers new layer to the layers
6459 * @param {String} name Addon name
6460 * @param {Class} layerClass Addon class
6461 */
6462 static registerLayer(name, layerClass) {
6463 if (has$1.call(layerClasses, name)) {
6464 throw new Error(`This layer (${name}) is already registered.`);
6465 } else {
6466 layerClasses[name] = layerClass;
6467 }
6468 }
6469 /**
6470 * The list of all registered layer classes
6471 */
6472
6473
6474 static get layers() {
6475 return layerClasses;
6476 }
6477 /**
6478 * Creates new Layers instance
6479 * @param {*} holder The layer holder object
6480 * @param {String} wrapperWidth Wrapper width
6481 */
6482
6483
6484 constructor(holder, wrapperWidth) {
6485 super();
6486 this.holder = holder;
6487 this.holder.layersController = this;
6488 this.wrapperWidth = wrapperWidth; // the list of added layers
6489
6490 this.layers = [];
6491 }
6492 /**
6493 * Finds all layer elements in given target element and setups them
6494 * @param {Element} targetElement
6495 * @param {Boolean} suppressFixed
6496 */
6497
6498
6499 setupLayers(targetElement, suppressFixed) {
6500 this._initLayers(targetElement, null, suppressFixed);
6501
6502 responsiveHelper.on('breakpointChange', this._updateWrapperSize, this);
6503 this.trigger('layersSetup', [this]);
6504 }
6505 /**
6506 * Finds all layer elements in the given scope and create layer instance for each one based on the type\
6507 * @private
6508 * @param {Element} scope The scope that the query should be called
6509 * @param {Element} layerContainer The expected container that layers should be appended to, if it does not set, main layers container will considered
6510 * @param {Boolean} suppressFixed Whether suppressing checking fixed positioning type on layers or not
6511 * @param {Layer} parentLayer Parent layer instance
6512 */
6513
6514
6515 _initLayers(scope, layerContainer, suppressFixed, parentLayer) {
6516 scope.querySelectorAll(`:scope > .${prefix}-layer, :scope > a > .${prefix}-layer`).forEach((layerElement, index) => {
6517 let linkedLayer = false; // check for linked layer
6518
6519 if (layerElement.parentNode.nodeName === 'A') {
6520 linkedLayer = true;
6521 }
6522
6523 let layerType = layerElement.getAttribute('data-type') || 'custom';
6524
6525 if (!has$1.call(layerClasses, layerType)) {
6526 layerType = 'custom';
6527 }
6528
6529 const LayerClass = layerClasses[layerType];
6530
6531 if (LayerClass) {
6532 const layer = new LayerClass(layerElement, this, this.holder, index, linkedLayer, parentLayer);
6533 const wrap = layerElement.getAttribute('data-wrap') !== 'false';
6534 layer.positionType = layerElement.getAttribute('data-position');
6535
6536 if (layer.positionType === 'static') {
6537 layer.frame.classList.add(`${prefix}-static`);
6538 } else {
6539 layer.isFixed = suppressFixed !== true && layer.positionType === 'fixed';
6540 } // append layer
6541
6542
6543 if (layer.isFixed) {
6544 this._appendToFixedContainer(layer, wrap);
6545 } else if (layerContainer) {
6546 layerContainer.appendChild(layer.frame);
6547 } else {
6548 this._appendToLayersContainer(layer, wrap);
6549 } // init the layer
6550
6551
6552 layer.init();
6553 this.layers.push(layer); // check for nested layers
6554
6555 if (layer.nestable) {
6556 this._initLayers(layer.element, layer.element, true, layer);
6557 }
6558 }
6559 });
6560
6561 if (this.hasFixedLayers) {
6562 // add section status class names to the fixed layers too
6563 this.holder.on('statusChange, activated, deactivated', this._setFixedContainerClass, this);
6564 }
6565
6566 this._updateWrapperSize();
6567 }
6568 /**
6569 * @private
6570 */
6571
6572
6573 _updateWrapperSize() {
6574 const width = getResponsiveValue(this.wrapperWidth);
6575 if (this.wrapper) this.wrapper.style.maxWidth = width + 'px';
6576 if (this.fixedWrapper) this.fixedWrapper.style.maxWidth = width + 'px';
6577 }
6578 /**
6579 * Adds section status class names to the fixed layers container
6580 * @private
6581 */
6582
6583
6584 _setFixedContainerClass(action, section, currentStatus, lastStatus) {
6585 if (action === 'activated') {
6586 this.fixedContainer.classList.add(`${prefix}-active`);
6587 } else if (action === 'deactivated') {
6588 this.fixedContainer.classList.remove(`${prefix}-active`);
6589 } else {
6590 this.fixedContainer.classList.add(`${prefix}-${currentStatus}`);
6591
6592 if (lastStatus) {
6593 this.fixedContainer.classList.remove(`${prefix}-${lastStatus}`);
6594 }
6595 }
6596 }
6597 /**
6598 * Appends layer to layers container
6599 * @private
6600 * @param {Layer} layer
6601 * @param {Boolean} wrap
6602 */
6603
6604
6605 _appendToLayersContainer(layer, wrap) {
6606 if (!this.container) {
6607 this.hasLayers = true; // create the container and wrapper
6608
6609 this.container = document.createElement('div');
6610 this.container.classList.add(`${prefix}-layers-container`); // wrapper element wraps layers in a specific area
6611 // and folds the layers fold by given space value in options
6612
6613 this.layersFold = document.createElement('div');
6614 this.layersFold.classList.add(`${prefix}-layers-fold`);
6615 this.wrapper = document.createElement('div');
6616 this.wrapper.classList.add(`${prefix}-layers-wrapper`);
6617 this.container.appendChild(this.wrapper);
6618 this.wrapper.appendChild(this.layersFold);
6619 this.wrapper.style.maxWidth = this.wrapperWidth + 'px';
6620 }
6621
6622 if (wrap) {
6623 this.layersFold.appendChild(layer.frame);
6624 } else {
6625 this.container.appendChild(layer.frame);
6626 }
6627 }
6628 /**
6629 * Appends layer to fixed layers container
6630 * @private
6631 * @param {Layer} layer
6632 * @param {Boolean} wrap
6633 */
6634
6635
6636 _appendToFixedContainer(layer, wrap) {
6637 if (!this.hasFixedLayers) {
6638 this.hasFixedLayers = true; // create the container and wrapper
6639
6640 this.fixedContainer = document.createElement('div');
6641 this.fixedContainer.classList.add(`${prefix}-layers-container`);
6642 this.fixedContainer.classList.add(`${prefix}-fixed`); // wrapper element wraps layers in a specific area
6643 // and folds the layers fold by given space value in options
6644
6645 this.fixedLayersFold = document.createElement('div');
6646 this.fixedLayersFold.classList.add(`${prefix}-layers-fold`);
6647 this.fixedWrapper = document.createElement('div');
6648 this.fixedWrapper.classList.add(`${prefix}-layers-wrapper`);
6649 this.fixedWrapper.style.maxWidth = this.wrapperWidth+ 'px';
6650 this.fixedContainer.appendChild(this.fixedWrapper);
6651 this.fixedWrapper.appendChild(this.fixedLayersFold);
6652 }
6653
6654 if (wrap) {
6655 this.fixedLayersFold.appendChild(layer.frame);
6656 } else {
6657 this.fixedContainer.appendChild(layer.frame);
6658 }
6659 }
6660
6661 }
6662
6663 /**
6664 * This class setups Layers for each section in composer
6665 */
6666
6667 class LayersAdapter {
6668 /**
6669 * Creates new layers adapter
6670 * @param {Composer} composer
6671 */
6672 constructor(composer) {
6673 this.composer = composer;
6674 this.composer.options.register({
6675 fadeLayers: false
6676 });
6677 this.composer.on('beforeSectionsSetup', this._init, this);
6678 }
6679 /**
6680 * Before setting up sections in the composer
6681 */
6682
6683
6684 _init() {
6685 this.wrapperWidth = this.composer.options.get('width');
6686
6687 if (this.composer.options.get('fadeLayers')) {
6688 this.composer.element.classList.add(`${prefix}-fade-layers`);
6689 }
6690
6691 this.composer.on('sectionBeforeMount', this.readLayers, this);
6692 }
6693 /**
6694 * Setups layers over the section
6695 * @param {String} name Emitter action name
6696 * @param {Section} section Target section
6697 */
6698
6699
6700 readLayers(name, section) {
6701 if (section.layersController) {
6702 return;
6703 } // hold section (holder) auto loading content to let layers prepare assets to load
6704
6705
6706 section.loadTrigger.hold(); // read the section wrapper width
6707
6708 if (section.element.dataset.wrapperWidth) {
6709 this.wrapperWidth = section.element.dataset.wrapperWidth.split(',');
6710 }
6711
6712 const layersController = new Layers(section, this.wrapperWidth);
6713 layersController.composer = this.composer;
6714 layersController.parentEmitter = this.composer;
6715 section.layersController = layersController;
6716 layersController.setupLayers(section.element);
6717
6718 if (layersController.hasLayers) {
6719 section.element.appendChild(layersController.container);
6720 }
6721
6722 if (layersController.hasFixedLayers) {
6723 if (!this.composer.fixedLayersContainer) {
6724 const fixedLayersContainer = document.createElement('div');
6725 fixedLayersContainer.classList.add(`${prefix}-fixed-layers`);
6726 this.composer.view.element.appendChild(fixedLayersContainer);
6727 this.composer.fixedLayersContainer = fixedLayersContainer;
6728 this.composer.trigger('fixedLayersContainer');
6729 }
6730
6731 this.composer.fixedLayersContainer.appendChild(layersController.fixedContainer);
6732 } // call start loading
6733
6734
6735 section.loadTrigger.exec();
6736 }
6737
6738 }
6739 Composer.registerAddon('layersAdapter', LayersAdapter);
6740
6741 /**
6742 * Layers surface is just like a section but it only contains layers.
6743 * This class is useful to create overlay layers
6744 */
6745
6746 class LayersSurface extends Emitter {
6747 /**
6748 * Creates new layer surface instance
6749 * @param {Composer} composer
6750 * @param {Element} element
6751 */
6752 constructor(composer, element) {
6753 super();
6754 this.composer = composer;
6755 this.eventPrefix = 'layersSurface';
6756 this.element = element;
6757 this.loadTrigger = new ActionTrigger(this.loadStart.bind(this));
6758 this.readyTrigger = new ActionTrigger(this.ready.bind(this));
6759 }
6760 /**
6761 * Setups the surface and read layer attributes related to show on and hide on sections
6762 */
6763
6764
6765 setup() {
6766 this.trigger('beforeSetup', [this], true); // set isOverlay flag on each layers added as overlay on slider
6767
6768 this.layersController.layers.forEach(layer => {
6769 layer.isOnSurface = true;
6770
6771 if (layer.element.hasAttribute('data-show-on-section')) {
6772 layer.showOnSections = layer.element.getAttribute('data-show-on-section').replace(/\s+/g, '').split(',');
6773 }
6774
6775 if (layer.element.hasAttribute('data-hide-on-section')) {
6776 layer.hideOnSections = layer.element.getAttribute('data-hide-on-section').replace(/\s+/g, '').split(',');
6777 }
6778 }, this);
6779 this.loadTrigger.exec(); // trigger resize event
6780
6781 this.composer.on('resize', () => this.trigger('resize', [this], true), this);
6782 }
6783 /**
6784 * Shows or hides the layer based on current section in the composer
6785 */
6786
6787
6788 _changeLayersState() {
6789 this.layersController.layers.forEach(layer => {
6790 if (this._checkForShow(layer)) {
6791 layer.show();
6792 } else {
6793 layer.hide();
6794 }
6795 });
6796 }
6797 /**
6798 * Triggers loading start event, this event listen by layers to start loading their containing assets
6799 */
6800
6801
6802 loadStart() {
6803 this.trigger('loadingStart', [this], true);
6804 this.readyTrigger.exec();
6805 }
6806 /**
6807 * Calls after loading all assets done
6808 */
6809
6810
6811 ready() {
6812 this.element.classList.add(`${prefix}-ready`); // Added active class name to fade all layers after they loaded
6813
6814 this.element.classList.add(`${prefix}-active`);
6815 this.composer.on('changeStart, init', this._changeLayersState, this);
6816 this.trigger('ready', [this], true);
6817 }
6818 /**
6819 * Checks wether given layer should be visible over current section in composer or not.
6820 * @private
6821 * @param {Layer} layer
6822 */
6823
6824
6825 _checkForShow(layer) {
6826 const index = this.composer.navigator.targetSectionIndex;
6827 const sectionId = this.composer.view.sections[index].id;
6828 const layerHideOn = layer.hideOnSections;
6829 const layerShowOn = layer.showOnSections;
6830
6831 if (layerShowOn) {
6832 return !!sectionId && layerShowOn.indexOf(sectionId) !== -1;
6833 }
6834
6835 return !sectionId || !layerHideOn || layerHideOn.length && layerHideOn.indexOf(sectionId) === -1;
6836 }
6837
6838 }
6839
6840 /**
6841 * Creates overlay layers by layers controller and layer surface over the composer
6842 */
6843
6844 class OverlayLayersAdapter {
6845 /**
6846 * Creates new layers adapter
6847 * @param {Composer} composer
6848 */
6849 constructor(composer) {
6850 this.composer = composer;
6851 this.composer.on('beforeSectionsSetup', this._init, this);
6852 }
6853 /**
6854 * Before setting up sections in the composer
6855 */
6856
6857
6858 _init() {
6859 this.wrapperWidth = this.composer.options.get('width');
6860 this.layersContainer = this.composer.element.querySelector(`.${prefix}-overlay-layers`);
6861
6862 if (!this.layersContainer) {
6863 return;
6864 }
6865
6866 this.layersSurface = new LayersSurface(this.composer, this.layersContainer);
6867 this.layersSurface.parentEmitter = this.composer;
6868 this.composer.view.element.appendChild(this.layersContainer);
6869 const layersController = new Layers(this.layersSurface, this.wrapperWidth);
6870 layersController.parentEmitter = this.composer;
6871 layersController.composer = this.composer;
6872 this.layersController = layersController;
6873 layersController.setupLayers(this.layersContainer);
6874
6875 if (layersController.hasLayers) {
6876 this.layersSurface.element.appendChild(layersController.container);
6877 }
6878
6879 this.composer.overlayLayers = this.layersSurface;
6880 this.layersSurface.setup();
6881 }
6882
6883 }
6884 Composer.registerAddon('overlayLayersAdapter', OverlayLayersAdapter);
6885
6886 /**
6887 * CSS Transform Interface, this class adds the possibility of controlling element transform by
6888 * adding, updating or removing transform functions without interrupting other transform parts.
6889 *
6890 * @author Averta [www.averta.net]
6891 * @license MIT
6892 */
6893 class TransformInterface {
6894 /**
6895 * Constructor
6896 * @param {Element} element
6897 */
6898 constructor(element, prefix = '') {
6899 this.element = element;
6900 this.segments = [];
6901 this.transform = prefix.length ? `${prefix}Transform` : 'transform';
6902 this._id = 0;
6903 }
6904 /**
6905 * Add new transform section to the element
6906 * A segment in transform can contain any transform function
6907 * @param {String} transform Initial transform section value
6908 * @param {Number} depth Specifies the location of adding segment in transform, higher value means
6909 * after other sections
6910 *
6911 * @returns {Number} The segment ID, this ID is required to update ore remove the section
6912 */
6913
6914
6915 add(transform, depth = 0) {
6916 this._id += 1;
6917 this.segments.push({
6918 transform,
6919 depth,
6920 id: this._id
6921 });
6922
6923 this._sort();
6924
6925 if (transform && transform.length) {
6926 this._apply();
6927 }
6928
6929 return this._id;
6930 }
6931 /**
6932 * Update the segment
6933 * @param {String} transform Transform string
6934 * @param {Number} segment Segment ID
6935 * @param {Number} depth Depth [Optional]
6936 */
6937
6938
6939 update(transform, segment, depth) {
6940 const segmentIndex = this._find(segment);
6941
6942 if (segmentIndex === -1) {
6943 return;
6944 }
6945
6946 if (depth !== undefined) {
6947 this.segments[segmentIndex].depth = depth;
6948
6949 this._sort();
6950 }
6951
6952 if (transform !== null) {
6953 this.segments[segmentIndex].transform = transform;
6954
6955 this._apply();
6956 }
6957 }
6958 /**
6959 * Remove transform segment
6960 * @param {Number} segment Segment ID
6961 */
6962
6963
6964 remove(segment) {
6965 const segmentIndex = this._find(segment);
6966
6967 if (segmentIndex === -1) {
6968 return;
6969 }
6970
6971 this.segments.splice(segmentIndex, 1);
6972
6973 this._apply();
6974 }
6975 /* ------------------------------------------------------------------------------ */
6976
6977 /**
6978 * Apply transform to the element
6979 */
6980
6981
6982 _apply() {
6983 if (this.segments.length === 0) {
6984 this.element.style[this.transform] = '';
6985 return;
6986 }
6987
6988 let transformStr = '';
6989 this.segments.forEach(segment => {
6990 if (segment.transform) {
6991 transformStr += segment.transform + ' ';
6992 }
6993 });
6994 this.element.style[this.transform] = transformStr;
6995 }
6996 /**
6997 * Sort transform segments by depth value
6998 */
6999
7000
7001 _sort() {
7002 this.segments.sort((a, b) => a.depth - b.depth);
7003 }
7004 /**
7005 * Find segment object by segment ID
7006 * @param {Number} segmentId
7007 */
7008
7009
7010 _find(segmentId) {
7011 let i = -1;
7012 this.segments.some((segment, index) => {
7013 i = index;
7014 return segmentId === segment.id;
7015 });
7016 return i;
7017 }
7018
7019 }
7020
7021 const boxProperties = ['width', 'height', 'padding-bottom', 'padding-top', 'padding-left', 'padding-right']; // Resize typography style properties
7022
7023 const typographyProperties = ['font-size'];
7024 /**
7025 * This class resizes the absolute positioned layer
7026 */
7027
7028 class AbsoluteResize {
7029 /**
7030 * Creates new absolute resize handler instance
7031 * @param {Layer} layer Target layer
7032 * @param {AbsolutePosition} positionHandler Absolute position handler
7033 */
7034 constructor(layer, positionHandler) {
7035 this.layer = layer;
7036 this.positionHandler = positionHandler;
7037 this.resizeType = layer.element.getAttribute('data-resize-type') || 'scale-relocate';
7038 this.resetResize = layer.element.getAttribute('data-reset-resize') !== 'false';
7039 this.scaleType = layer.element.getAttribute('data-scale-type') || 'scale';
7040 this.upscale = layer.element.getAttribute('data-upscale') === 'true';
7041 this.scale = this.resizeType.indexOf('scale') !== -1;
7042 this.relocate = this.resizeType.indexOf('relocate') !== -1;
7043 this._firstLocate = true;
7044
7045 if (this.scale) {
7046 if (this.scaleType === 'scale') {
7047 this.scaleTransform = layer.frameTransform.add(null, 100);
7048 } else {
7049 this.layerInlineStyle = this.layer.element.getAttribute('style');
7050 }
7051
7052 this.updateBaseStyle();
7053 }
7054 }
7055 /* ------------------------------------------------------------------------------ */
7056
7057 /**
7058 * Reads layer's base styles
7059 */
7060
7061
7062 updateBaseStyle() {
7063 const scaleType = this.scaleType.toLowerCase();
7064
7065 if (scaleType === 'scale') {
7066 return;
7067 }
7068
7069 let props;
7070
7071 switch (scaleType) {
7072 case 'box':
7073 default:
7074 props = boxProperties;
7075 break;
7076
7077 case 'typography-box':
7078 props = [].concat(boxProperties, typographyProperties);
7079 break;
7080
7081 case 'typography':
7082 props = typographyProperties;
7083 }
7084
7085 this.baseStyle = {}; // reset style attribute value
7086
7087 this.layer.element.setAttribute('style', this.layerInlineStyle);
7088 props.forEach(property => {
7089 const value = getComputedStyle(this.layer.element)[property];
7090 this.baseStyle[property] = value;
7091 });
7092 }
7093 /**
7094 * Resizes the layer
7095 */
7096
7097
7098 update() {
7099 const breakpointSize = !responsiveHelper.activeBreakpoint ? getResponsiveValue(this.layer.composer.options.get('width')) : responsiveHelper.activeBreakpointSize;
7100 let scaleFactor = window.innerWidth / breakpointSize;
7101
7102 if (this.scale) {
7103 if (!this.upscale) {
7104 scaleFactor = Math.min(1, scaleFactor);
7105 }
7106
7107 if (this.scaleType === 'scale') {
7108 this.layer.frameTransform.update(`scale(${scaleFactor})`, this.scaleTransform);
7109 } else {
7110 const {
7111 positionHandler
7112 } = this;
7113 Object.keys(this.baseStyle).forEach(property => {
7114 // do not resize layer size if it's not fixed value
7115 if ((property !== 'width' || !positionHandler.floatWidth) && (property !== 'height' || !positionHandler.floatHeight)) {
7116 this.layer.element.style[property] = parseFloat(this.baseStyle[property]) * scaleFactor + 'px';
7117 }
7118 });
7119 }
7120 } // update layer position
7121
7122
7123 if (this.relocate) {
7124 const {
7125 activeOffset
7126 } = this.positionHandler;
7127 const origin = activeOffset.origin || 'tl';
7128 let locateFactor = scaleFactor;
7129 const {
7130 frame
7131 } = this.layer;
7132
7133 if (!this.upscale) {
7134 locateFactor = Math.min(1, locateFactor);
7135 }
7136
7137 if (activeOffset.x.indexOf('%') === -1) {
7138 const x = parseInt(activeOffset.x, 10) * locateFactor;
7139
7140 switch (origin.charAt(1)) {
7141 case 'l':
7142 default:
7143 frame.style.left = x + 'px';
7144 break;
7145
7146 case 'r':
7147 frame.style.right = x + 'px';
7148 break;
7149
7150 case 'c':
7151 frame.style.left = x === 0 ? '50%' : 'calc( 50% + ' + x + 'px )';
7152 }
7153 }
7154
7155 if (activeOffset.y.indexOf('%') === -1) {
7156 const y = parseInt(activeOffset.y, 10) * locateFactor;
7157
7158 switch (origin.charAt(0)) {
7159 case 't':
7160 default:
7161 frame.style.top = y + 'px';
7162 break;
7163
7164 case 'b':
7165 frame.style.bottom = y + 'px';
7166 break;
7167
7168 case 'm':
7169 frame.style.top = y === 0 ? '50%' : 'calc( 50% + ' + y + 'px )';
7170 }
7171 }
7172 }
7173 }
7174
7175 }
7176
7177 const originToTransformOrigin = {
7178 t: 'top',
7179 m: 'center',
7180 b: 'bottom',
7181 l: 'left',
7182 r: 'right',
7183 c: 'center'
7184 };
7185 /**
7186 * Layer absolute positioning class
7187 * This class locates the layer based on offset data attribute values and resizes the layer by delegation resizing to AbsoluteResizing class.
7188 */
7189
7190 class AbsolutePosition {
7191 /**
7192 * Creates new absolute position handler instance
7193 * @param {Layer} layer Target layer
7194 */
7195 constructor(layer) {
7196 this.layer = layer;
7197 layer.frame.classList.add(`${prefix}-pos-absolute`);
7198 this.layer.frame.style.zIndex = this.layer.index + 10; // read offsets
7199
7200 const offsets = _objectSpread2({
7201 none: {
7202 x: '0px',
7203 y: '0px',
7204 origin: 'tl'
7205 }
7206 }, getAttrValues(layer.element, 'offset')); // parse offset values
7207
7208
7209 Object.keys(offsets).forEach(key => {
7210 if (typeof offsets[key] === 'string') {
7211 offsets[key] = this._getOffsetObject(offsets[key]);
7212 }
7213 });
7214 this.layer.offsets = offsets;
7215
7216 if (this.layer.element.getAttribute('data-resize') !== 'false' && !this.layer.nested) {
7217 this.resizeHandler = new AbsoluteResize(this.layer, this);
7218 this.layer.holder.on('resize', this.resizeHandler.update, this.resizeHandler);
7219 }
7220
7221 responsiveHelper.on('breakpointChange', this.locate, this);
7222 }
7223 /**
7224 * Locates the layer
7225 */
7226
7227
7228 locate() {
7229 const {
7230 frame
7231 } = this.layer;
7232 const offset = getResponsiveValue(this.layer.offsets);
7233 this.activeOffset = offset;
7234
7235 if (offset.width !== undefined) {
7236 if (offset.width.indexOf('%') === -1) {
7237 this.layer.element.style.width = offset.width;
7238 frame.classList.remove(`${prefix}-float-width`);
7239 this.floatWidth = false;
7240 } else {
7241 frame.style.width = offset.width;
7242 frame.classList.add(`${prefix}-float-width`);
7243 this.floatWidth = true;
7244 }
7245 }
7246
7247 if (offset.height !== undefined) {
7248 if (offset.height.indexOf('%') === -1) {
7249 this.layer.element.style.height = offset.height;
7250 frame.classList.remove(`${prefix}-float-height`);
7251 this.floatHeight = false;
7252 } else {
7253 frame.style.height = offset.height;
7254 frame.classList.add(`${prefix}-float-height`);
7255 this.floatHeight = true;
7256 }
7257 }
7258
7259 frame.style[`${CSSPrefix.js}Transform`] = ''; // reset position styles
7260
7261 this.layer.frameTransform.update('', this._transformSegment);
7262 frame.style.top = '';
7263 frame.style.left = '';
7264 frame.style.bottom = '';
7265 frame.style.right = '';
7266 const origin = offset.origin || 'tl';
7267 const vOrigin = origin.charAt(0);
7268 const hOrigin = origin.charAt(1);
7269 let transformStr = ''; // set transform origin
7270
7271 frame.style[`${CSSPrefix.js}TransformOrigin`] = originToTransformOrigin[vOrigin] + ' ' + originToTransformOrigin[hOrigin];
7272
7273 switch (vOrigin) {
7274 case 't':
7275 default:
7276 frame.style.top = offset.y;
7277 break;
7278
7279 case 'b':
7280 frame.style.bottom = offset.y;
7281 break;
7282
7283 case 'm':
7284 if (offset.y === '0') {
7285 offset.y = '0px';
7286 }
7287
7288 transformStr = 'translateY(-50%)';
7289 frame.style.top = `calc(50% + ${offset.y})`;
7290 }
7291
7292 switch (hOrigin) {
7293 case 'l':
7294 default:
7295 frame.style.left = offset.x;
7296 break;
7297
7298 case 'r':
7299 frame.style.right = offset.x;
7300 break;
7301
7302 case 'c':
7303 if (offset.x === '0') {
7304 offset.x = '0px';
7305 }
7306
7307 frame.style.left = `calc(50% + ${offset.x})`;
7308 transformStr += ' translateX(-50%)';
7309 }
7310
7311 this.layer.frameTransform.update(transformStr, this.layer.transformSegment);
7312
7313 if (this.resizeHandler) {
7314 this.resizeHandler.updateBaseStyle();
7315 this.resizeHandler.update();
7316 }
7317 }
7318 /**
7319 * Convert the offset string the offset object
7320 * @param {String} offsetString
7321 */
7322
7323
7324 _getOffsetObject(offsetString) {
7325 const offsetObj = {};
7326 offsetString.replace(/\s/g, '').split(';').forEach(property => {
7327 property = property.split(':'); // eslint-disable-next-line prefer-destructuring
7328
7329 offsetObj[property[0]] = property[1];
7330 });
7331 return offsetObj;
7332 }
7333
7334 }
7335
7336 /*!
7337 * With thanks to Roko C. Buljan
7338 * https://stackoverflow.com/questions/9518956/javascript-convert-css-style-string-into-js-object
7339 */
7340 function cssToObject(css) {
7341 if (!css || css.length === 0) {
7342 return {};
7343 }
7344
7345 const obj = {};
7346 const s = css.toLowerCase().replace(/-(.)/g, (m, g) => g.toUpperCase()).replace(/;\s?$/g, '').split(/:|;/g);
7347
7348 for (let i = 0; i < s.length; i += 2) obj[s[i].replace(/\s/g, '')] = s[i + 1].replace(/^\s+|\s+$/g, '');
7349
7350 return obj;
7351 }
7352
7353 /**
7354 * Adds and removes styles to the target element based on active breakpoint
7355 */
7356
7357 class BreakpointStyle {
7358 /**
7359 * Creates new Breakpoint Style class
7360 * @param {Element} element Target element
7361 * @param {Object} styles [Optional] The object containing styles of each breakpoint.
7362 */
7363 constructor(element, styles) {
7364 this.element = element;
7365
7366 if (!styles) {
7367 styles = getAttrValues(element, 'style');
7368 }
7369
7370 const dataLen = Object.keys(styles).length;
7371
7372 if (dataLen === 0 || dataLen === 1 && has$1.call(styles, 'none')) {
7373 return;
7374 } // convert data
7375
7376
7377 Object.keys(styles).forEach(key => {
7378 if (key !== 'none') {
7379 styles[key] = cssToObject(styles[key]);
7380 }
7381 });
7382 styles.none = {};
7383 this.styles = styles;
7384 responsiveHelper.on('breakpointChange', this.update, this);
7385 this.lastActivePoint = 'none';
7386 this.updateBaseStyle();
7387 this.update();
7388 }
7389 /**
7390 * Reads and updates base styles from target element
7391 */
7392
7393
7394 updateBaseStyle() {
7395 this.baseStyle = cssToObject(this.element.getAttribute('style'));
7396 }
7397 /**
7398 * Updates the element styles based on active breakpoint
7399 */
7400
7401
7402 update() {
7403 const resetStyle = {};
7404
7405 if (this.lastActivePoint !== 'none') {
7406 Object.keys(this.lastStyle).forEach(key => {
7407 if (this.baseStyle[key]) {
7408 resetStyle[key] = this.baseStyle[key];
7409 } else {
7410 resetStyle[key] = '';
7411 }
7412 });
7413 }
7414
7415 this.lastActivePoint = responsiveHelper.activeBreakpoint;
7416 let style = getResponsiveValue(this.styles, this.lastActivePoint);
7417 this.lastStyle = style;
7418 style = _objectSpread2(_objectSpread2({}, resetStyle), style);
7419 requestAnimationFrame(() => {
7420 Object.keys(style).forEach(property => {
7421 this.element.style[property] = style[property];
7422 });
7423 });
7424 }
7425
7426 }
7427 /* ------------------------------------------------------------------------------ */
7428
7429 /**
7430 * Sets new class names based on active breakpoint
7431 */
7432
7433 class BreakpointClass {
7434 /**
7435 * Creates new Breakpoint Class
7436 * @param {Element} element Target element
7437 * @param {Object} styles [Optional] The object containing classes of each breakpoint.
7438 */
7439 constructor(element, classNames) {
7440 this.element = element;
7441
7442 if (!classNames) {
7443 classNames = getAttrValues(this.element, 'class');
7444 }
7445
7446 const dataLen = Object.keys(classNames).length;
7447
7448 if (dataLen === 0 || dataLen === 1 && has$1.call(classNames, 'none')) {
7449 return;
7450 } // convert data
7451
7452
7453 Object.keys(classNames).forEach(key => {
7454 if (key !== 'none') {
7455 classNames[key] = classNames[key].replace(/(\s\s)+/g, ' ').split(' ');
7456 }
7457 });
7458 this.classNames = classNames;
7459 this.classNames.none = [];
7460 responsiveHelper.on('breakpointChange', this.update, this);
7461 this.lastActivePoint = 'none';
7462 this.update();
7463 }
7464 /**
7465 * Updates class names of element based on active breakpoint
7466 */
7467
7468
7469 update() {
7470 if (this.lastActivePoint !== 'none') {
7471 this.lastClasses.forEach(className => this.element.classList.remove(className));
7472 }
7473
7474 this.lastActivePoint = responsiveHelper.activeBreakpoint;
7475 const classes = getResponsiveValue(this.classNames, this.lastActivePoint);
7476 this.lastClasses = classes;
7477 classes.forEach(className => this.element.classList.add(className));
7478 }
7479
7480 }
7481
7482 /**
7483 * It create a layer object.
7484 * Each layer type should extend this class to add further functionality.
7485 * Please do not make direct instance from this class
7486 */
7487
7488 class Layer extends Emitter {
7489 /**
7490 * Creates new layer
7491 * @param {Element} element Layer element
7492 * @param {Layers} controller Layer controller
7493 * @param {*} holder The layers holder object for sections layers, it is the section object
7494 * @param {Number} index Layer index number
7495 * @param {Boolean} isLinked Whether the layer is linked or not
7496 * @param {Layer} parent Layer's parent layer
7497 */
7498 constructor(element, controller, holder, index, isLinked, parent) {
7499 super();
7500 this.element = element;
7501 this.controller = controller;
7502 this.holder = holder;
7503 this.index = index;
7504 this.isLinked = isLinked;
7505 this.parent = parent;
7506 this.composer = this.controller.composer;
7507 this.id = element.id;
7508
7509 if (!this.composer.layersById) {
7510 this.composer.layersById = {};
7511 }
7512
7513 if (this.id) {
7514 this.composer.layersById[this.id] = this;
7515 }
7516
7517 this.parentEmitter = controller;
7518 this.eventPrefix = 'layer';
7519
7520 if (isLinked) {
7521 this.linkElement = element.parentElement;
7522 }
7523
7524 if (this.parent) {
7525 this.nested = true;
7526 } // the frame element is wraps the layer element to make the positioning more reliable
7527 // it also can be used for creating mask or parallax effect
7528
7529
7530 this.frame = document.createElement('div');
7531 this.frame.classList.add(`${prefix}-layer-frame`);
7532
7533 if (this.element.hasAttribute('data-frame-class')) {
7534 this.frame.classList.add(this.element.getAttribute('data-frame-class'));
7535 }
7536
7537 if (this.element.hasAttribute('data-frame-id')) {
7538 this.frame.classList.add(this.element.getAttribute('data-frame-id'));
7539 }
7540
7541 if (this.element.hasAttribute('data-frame-style')) {
7542 this.frame.setAttribute('style', this.element.getAttribute('data-frame-style'));
7543 } // setup query class and styles on layer element
7544
7545
7546 this.elementBreakpointStyle = new BreakpointStyle(this.element);
7547 this.elementBreakpointClass = new BreakpointClass(this.element); // setup query class and styles on layer frame
7548
7549 this.frameBreakpointStyle = new BreakpointStyle(this.frame, getAttrValues(this.element, 'frame-style'));
7550 this.frameBreakpointClass = new BreakpointClass(this.frame, getAttrValues(this.element, 'frame-class')); // consider link element
7551
7552 if (this.isLinked) {
7553 this.frame.appendChild(this.linkElement);
7554 } else {
7555 this.frame.appendChild(this.element);
7556 }
7557
7558 this.readyTrigger = new ActionTrigger(this._ready.bind(this));
7559 this.offsets = {};
7560 this.trigger('create', [this], true);
7561 }
7562 /**
7563 * Inits the layer
7564 * @param {Boolean} suppressEvents
7565 */
7566
7567
7568 init(suppressEvents) {
7569 if (!suppressEvents) {
7570 this.trigger('beforeInit', [this], true);
7571 }
7572
7573 if (this.element.hasAttribute('data-id')) {
7574 this.id = this.element.getAttribute('data-id');
7575 this.frame.classList.add(`${prefix}-id-${this.id}`);
7576 } // create transform interface for the frame element
7577 // It's necessary for controlling the element transform from other add-ons like parallax effect
7578
7579
7580 this.frameTransform = new TransformInterface(this.frame, CSSPrefix.js);
7581 this.transformSegment = this.frameTransform.add();
7582
7583 if (!this.disablePositionHandler && (!this.element.hasAttribute('data-position-handler') || this.element.getAttribute('data-position-handler') === 'absolute')) {
7584 this.positionHandler = new AbsolutePosition(this);
7585 }
7586
7587 const hideOnBps = this.element.getAttribute('data-hide-on');
7588 this.bpVisible = true;
7589
7590 if (hideOnBps) {
7591 addHideOn(this.element, hideOnBps.split(','), hidden => {
7592 this.bpVisible = !hidden;
7593 this.trigger('visibilityChange', [this, hidden], true);
7594 }, `${prefix}-layer-hidden`);
7595 }
7596
7597 this._setupContent();
7598
7599 if (!suppressEvents) {
7600 this.trigger('afterInit', [this], true);
7601 this.readyTrigger.exec();
7602 }
7603 }
7604 /**
7605 * Manipulates layer content
7606 * Overrides by layer types
7607 */
7608
7609
7610 _setupContent() {}
7611 /**
7612 * This layer is ready to appear
7613 */
7614
7615
7616 _ready() {
7617 this.ready = true;
7618
7619 if (this.positionHandler) {
7620 this.positionHandler.locate();
7621 }
7622
7623 this.trigger('ready', [this], true);
7624 }
7625
7626 }
7627
7628 /**
7629 * Custom layer type, it can contain any HTML content
7630 */
7631
7632 class CustomLayer$3 extends Layer {
7633 /**
7634 * Creates new layer
7635 * @param {Element} element Layer element
7636 * @param {Layers} controller Layer controller
7637 * @param {*} holder The layers holder object for sections layers, it is the section object
7638 * @param {Number} index Layer index number
7639 * @param {Boolean} isLinked Whether the layer is linked or not
7640 * @param {Layer} parent Layer's parent layer
7641 */
7642 constructor(element, controller, holder, index, isLinked, parent) {
7643 super(element, controller, holder, index, isLinked, parent);
7644 this.type = 'custom';
7645 this.frame.classList.add(`${prefix}-${this.type}-layer`);
7646 }
7647
7648 }
7649 Layers.registerLayer('custom', CustomLayer$3);
7650
7651 /**
7652 * Custom layer type, it can contain any HTML content
7653 */
7654
7655 class CustomLayer$2 extends Layer {
7656 /**
7657 * Creates new layer
7658 * @param {Element} element Layer element
7659 * @param {Layers} controller Layer controller
7660 * @param {*} holder The layers holder object for sections layers, it is the section object
7661 * @param {Number} index Layer index number
7662 * @param {Boolean} isLinked Whether the layer is linked or not
7663 * @param {Layer} parent Layer's parent layer
7664 */
7665 constructor(element, controller, holder, index, isLinked, parent) {
7666 super(element, controller, holder, index, isLinked, parent);
7667 this.type = 'text';
7668 this.frame.classList.add(`${prefix}-${this.type}-layer`);
7669 }
7670
7671 }
7672 Layers.registerLayer('text', CustomLayer$2);
7673
7674 /**
7675 * Custom layer type, it can contain any HTML content
7676 */
7677
7678 class CustomLayer$1 extends Layer {
7679 /**
7680 * Creates new layer
7681 * @param {Element} element Layer element
7682 * @param {Layers} controller Layer controller
7683 * @param {*} holder The layers holder object for sections layers, it is the section object
7684 * @param {Number} index Layer index number
7685 * @param {Boolean} isLinked Whether the layer is linked or not
7686 * @param {Layer} parent Layer's parent layer
7687 */
7688 constructor(element, controller, holder, index, isLinked, parent) {
7689 super(element, controller, holder, index, isLinked, parent);
7690 this.type = 'button';
7691 this.frame.classList.add(`${prefix}-${this.type}-layer`);
7692 }
7693
7694 }
7695 Layers.registerLayer('button', CustomLayer$1);
7696
7697 /**
7698 * Custom layer type, it can contain any shape content
7699 */
7700
7701 class ShapeLayer extends Layer {
7702 /**
7703 * Creates new layer
7704 * @param {Element} element Layer element
7705 * @param {Layers} controller Layer controller
7706 * @param {*} holder The layers holder object for sections layers, it is the section object
7707 * @param {Number} index Layer index number
7708 * @param {Boolean} isLinked Whether the layer is linked or not
7709 * @param {Layer} parent Layer's parent layer
7710 */
7711 constructor(element, controller, holder, index, isLinked, parent) {
7712 super(element, controller, holder, index, isLinked, parent);
7713 this.type = 'shape';
7714 this.frame.classList.add(`${prefix}-${this.type}-layer`);
7715 }
7716
7717 _setupContent() {
7718 var _this$element$querySe;
7719
7720 (_this$element$querySe = this.element.querySelector('svg')) === null || _this$element$querySe === void 0 ? void 0 : _this$element$querySe.setAttribute('preserveAspectRatio', 'none');
7721 }
7722
7723 }
7724 Layers.registerLayer('shape', ShapeLayer);
7725
7726 /**
7727 * Image layer type
7728 */
7729
7730 class ImageLayer extends Layer {
7731 /**
7732 * Creates new layer
7733 * @param {Element} element Layer element
7734 * @param {Layers} controller Layer controller
7735 * @param {*} holder The layers holder object for sections layers, it is the section object
7736 * @param {Number} index Layer index number
7737 * @param {Boolean} isLinked Whether the layer is linked or not
7738 * @param {Layer} parent Layer's parent layer
7739 */
7740 constructor(element, controller, holder, index, isLinked, parent) {
7741 super(element, controller, holder, index, isLinked, parent);
7742 this.type = 'image';
7743 this.frame.classList.add(`${prefix}-${this.type}-layer`);
7744 }
7745 /**
7746 * Manipulates layer content
7747 */
7748
7749
7750 _setupContent() {
7751 if (this.element.nodeName === 'IMG') {
7752 this.img = this.element;
7753 } else {
7754 this.img = this.element.querySelector('img');
7755 }
7756
7757 if (!this.img) {
7758 return;
7759 }
7760
7761 this.holder.readyTrigger.hold();
7762 this.holder.on('loadingStart', this._loadImage, this);
7763 }
7764 /**
7765 * Start loading image
7766 */
7767
7768
7769 _loadImage() {
7770 loadImage(this.img, this._loaded.bind(this), this._error.bind(this));
7771 }
7772 /**
7773 * Image is loaded
7774 */
7775
7776
7777 _loaded() {
7778 this.img.classList.add(`${prefix}-loaded`);
7779 this.holder.readyTrigger.exec();
7780 }
7781 /**
7782 * Image loading failed
7783 */
7784
7785
7786 _error() {
7787 this.holder.readyTrigger.exec();
7788 }
7789
7790 }
7791 Layers.registerLayer('image', ImageLayer);
7792
7793 const players = {};
7794 const loadingList = {};
7795 const alreadyAddedScripts = [];
7796 const has = Object.prototype.hasOwnProperty;
7797 class VideoElement {
7798 /**
7799 * Registers new player api adapter class
7800 * @param {String} name Player name
7801 * @param {Class} playerClass Player api adapter class
7802 */
7803 static registerPlayer(name, playerClass) {
7804 // is it already exists
7805 if (has.call(players, name)) {
7806 return;
7807 }
7808
7809 players[name] = playerClass;
7810 }
7811
7812 static get players() {
7813 return players;
7814 }
7815 /**
7816 * Creates new video element
7817 * @param {String|Element} source Video source
7818 */
7819
7820
7821 constructor(source) {
7822 this.type = 'custom';
7823
7824 if (typeof source === 'string') {
7825 this.videoSourceType = 'embed';
7826 this.type = this._getTypeBySrc(source);
7827
7828 if (has.call(players, this.type)) {
7829 this.player = new players[this.type](this);
7830 }
7831
7832 this.element = this._generateIframe(source);
7833 this.source = this.element;
7834 } else if (source.tagName === 'IFRAME') {
7835 this.videoSourceType = 'embed';
7836 this.type = this._getTypeBySrc(source.getAttribute('src'));
7837 this.element = source;
7838 this.source = source;
7839
7840 if (has.call(players, this.type)) {
7841 this.player = new players[this.type](this);
7842 }
7843 } else if (source.tagName === 'VIDEO') {
7844 this.videoSourceType = 'self-hosted';
7845 this.element = source;
7846 this.source = source;
7847 const type = source.getAttribute('data-player-type') || 'native';
7848
7849 if (has.call(players, type)) {
7850 this.type = type;
7851 this.player = new players[this.type](this);
7852 }
7853 }
7854 }
7855 /**
7856 * Setup the video player api
7857 * @param {Function} readyCallback It calls right after the api gets ready
7858 */
7859
7860
7861 setup(readyCallback, errorCallback) {
7862 if (this.type === 'custom') {
7863 return;
7864 }
7865
7866 this._readyCallback = readyCallback;
7867 this._errorCallback = errorCallback;
7868 this.player.init();
7869 }
7870 /**
7871 * Calls by the player to tell the video element that the api is ready to use
7872 */
7873
7874
7875 playerIsReady() {
7876 this.ready = true;
7877
7878 if (this._readyCallback) {
7879 this._readyCallback();
7880 }
7881 }
7882 /**
7883 * Loads the api script file, each player can use this method to load the script in the page
7884 * @param {String} src Script src
7885 * @param {Function} callback Calls after loading the script in the page
7886 */
7887
7888
7889 loadScript(src, callback) {
7890 if (loadingList[`${this.type}_isLoaded`]) {
7891 callback();
7892 return;
7893 }
7894
7895 if (!loadingList[this.type]) {
7896 loadingList[this.type] = [callback];
7897 } else {
7898 loadingList[this.type].push(callback);
7899 }
7900
7901 if (alreadyAddedScripts.indexOf(src) === -1) {
7902 alreadyAddedScripts.push(src);
7903 } else {
7904 return;
7905 }
7906
7907 const head = document.getElementsByTagName('head')[0];
7908 const script = document.createElement('script');
7909 script.type = 'text/javascript';
7910
7911 script.onload = () => {
7912 loadingList[this.type].forEach(cb => cb());
7913 loadingList[`${this.type}_isLoaded`] = true;
7914 };
7915
7916 script.onreadystatechange = script.onload;
7917
7918 if (this._errorCallback) {
7919 script.onerror = this._errorCallback;
7920 }
7921
7922 script.src = src;
7923 head.appendChild(script);
7924 }
7925 /**
7926 * Reads the src value and validates it by all registered players to find the right player
7927 * @private
7928 * @param {String} src
7929 */
7930
7931
7932 _getTypeBySrc(src) {
7933 let type = 'custom';
7934 Object.keys(players).some(playerName => {
7935 const playerInstance = players[playerName];
7936
7937 if (playerInstance.iframeEmbed && playerInstance.validate(src)) {
7938 type = playerName;
7939 return true;
7940 }
7941
7942 return false;
7943 });
7944 return type;
7945 }
7946 /**
7947 * Generates an iframe for embedding video if is needed
7948 * @private
7949 * @param {String} src
7950 */
7951
7952
7953 _generateIframe(src) {
7954 if (this.player && this.player.beforeIframe) {
7955 src = this.player.beforeIframe(src);
7956 }
7957
7958 const iframe = document.createElement('iframe');
7959 iframe.setAttribute('src', src);
7960 iframe.setAttribute('allowtransparency', 'true');
7961 iframe.setAttribute('frameborder', '0');
7962 iframe.setAttribute('scrolling', 'no');
7963 iframe.setAttribute('allowfullscreen', '');
7964
7965 if (this.player && this.player.afterIframe) {
7966 src = this.player.afterIframe(src);
7967 }
7968
7969 return iframe;
7970 }
7971
7972 }
7973
7974 class MediaElementJS {
7975 constructor(videoElement) {
7976 this.ve = videoElement;
7977 }
7978
7979 init() {
7980 if (!window.MediaElementPlayer) {
7981 throw new Error('MediaElementJS not found.');
7982 }
7983
7984 this.api = new window.MediaElementPlayer(this.ve.element, {
7985 success: () => {
7986 setTimeout(this._apiReady.bind(this), 0);
7987 }
7988 });
7989 this.ve.element = this.api.container;
7990 }
7991
7992 setupInterface() {
7993 Object.assign(this.ve, {
7994 play: this.api.play.bind(this.api),
7995 pause: this.api.pause.bind(this.api),
7996 mute: () => {
7997 this.api.setMuted(true);
7998 },
7999 unmute: () => {
8000 this.api.setMuted(false);
8001 },
8002 stop: () => {
8003 this.api.setCurrentTime(0);
8004 this.api.pause();
8005 },
8006 on: this.on.bind(this),
8007 off: this.off.bind(this)
8008 });
8009 }
8010
8011 on(type, listener) {
8012 if (type === 'play') {
8013 type = 'playing';
8014 }
8015
8016 this.ve.source.addEventListener(type, listener, false);
8017 }
8018
8019 off(type, listener) {
8020 this.ve.source.removeEventListener(type, listener, false);
8021 }
8022
8023 _apiReady() {
8024 this.setupInterface();
8025 this.ve.playerIsReady(this.api);
8026 }
8027
8028 }
8029
8030 VideoElement.registerPlayer('mejs', MediaElementJS);
8031
8032 class NativePlayer {
8033 constructor(videoElement) {
8034 this.ve = videoElement;
8035 }
8036
8037 init() {
8038 this.api = this.ve.element;
8039 this.setupInterface();
8040 this.ve.playerIsReady(this.api);
8041 }
8042
8043 setupInterface() {
8044 Object.assign(this.ve, {
8045 play: this.api.play.bind(this.api),
8046 pause: this.api.pause.bind(this.api),
8047 mute: () => {
8048 this.api.muted = true;
8049 },
8050 unmute: () => {
8051 this.api.muted = false;
8052 },
8053 stop: () => {
8054 this.api.currentTime = 0;
8055 this.api.pause();
8056 },
8057 on: this.on.bind(this),
8058 off: this.off.bind(this)
8059 });
8060 }
8061
8062 on(type, listener) {
8063 this.ve.element.addEventListener(type, listener, false);
8064 }
8065
8066 off(type, listener) {
8067 this.ve.element.removeEventListener(type, listener, false);
8068 }
8069
8070 }
8071
8072 VideoElement.registerPlayer('native', NativePlayer);
8073
8074 const apiScript$1 = 'https://player.vimeo.com/api/player.js';
8075 const validateRx$1 = /http(?:s?):\/\/(?:www\.)?\w*.?vimeo.com/;
8076 const IDMatch$1 = /(?:http?s?:\/\/)?(?:www\.)?(?:vimeo\.com)\/?(.+)/;
8077
8078 class Vimeo {
8079 static validate(src) {
8080 return validateRx$1.test(src);
8081 }
8082
8083 static get iframeEmbed() {
8084 return true;
8085 }
8086
8087 constructor(videoElement) {
8088 this.ve = videoElement;
8089 }
8090
8091 beforeIframe(src) {
8092 if (src.indexOf('/video/') === -1) {
8093 return `https://player.vimeo.com/video/${src.match(IDMatch$1)[1]}`;
8094 }
8095
8096 return src;
8097 }
8098
8099 init() {
8100 if (window.Vimeo) {
8101 this.api = new window.Vimeo.Player(this.ve.element);
8102 this.setupInterface();
8103 this.ve.playerIsReady(this.api);
8104 } else {
8105 this.ve.loadScript(apiScript$1, () => {
8106 this.init();
8107 });
8108 }
8109 }
8110
8111 setupInterface() {
8112 Object.assign(this.ve, {
8113 play: this.api.play.bind(this.api),
8114 pause: this.api.pause.bind(this.api),
8115 mute: () => {
8116 this._currentVol = this.api.getVolume();
8117 this.api.setVolume(0);
8118 },
8119 unmute: () => {
8120 this.api.setVolume(this._currentVol);
8121 },
8122 stop: () => {
8123 this.api.setCurrentTime(0);
8124 this.api.pause();
8125 },
8126 on: this.api.on.bind(this.api),
8127 off: this.api.off.bind(this.api)
8128 });
8129 }
8130
8131 }
8132
8133 VideoElement.registerPlayer('vimeo', Vimeo);
8134
8135 const apiScript = 'https://www.youtube.com/iframe_api';
8136 const validateRx = /http(?:s?):\/\/(?:www\.)?youtu(?:be\.com|\.be\/)/;
8137 const IDMatch = /(?:http?s?:\/\/)?(?:www\.)?(?:youtube\.com|youtu\.be)\/(?:watch\?v=)?(.+)/;
8138
8139 class YouTube {
8140 static validate(src) {
8141 return validateRx.test(src);
8142 }
8143
8144 static get iframeEmbed() {
8145 return true;
8146 }
8147
8148 constructor(videoElement) {
8149 this.ve = videoElement;
8150 this._apiReady = this._apiReady.bind(this);
8151 this._onStateChange = this._onStateChange.bind(this);
8152 this._listeners = {};
8153 this.ve.element.src = this.checkSrc(this.ve.element.src);
8154 }
8155
8156 checkSrc(src) {
8157 if (src.indexOf('/embed/') === -1) {
8158 src = `https://www.youtube.com/embed/${src.match(IDMatch)[1]}`;
8159 }
8160
8161 if (src.indexOf('enablejsapi') === -1) {
8162 src += `${src.indexOf('?') === -1 ? '?' : '&'}enablejsapi=1`;
8163 }
8164
8165 return src;
8166 }
8167
8168 beforeIframe(src) {
8169 return this.checkSrc(src);
8170 }
8171
8172 init(afterLoad) {
8173 if (window.YT && window.YT.Player) {
8174 this.api = new window.YT.Player(this.ve.element);
8175 this.api.addEventListener('onReady', this._apiReady, false);
8176 } else if (afterLoad) {
8177 let superCallback;
8178
8179 if (window.onYouTubeIframeAPIReady) {
8180 superCallback = window.onYouTubeIframeAPIReady;
8181 }
8182
8183 window.onYouTubeIframeAPIReady = () => {
8184 if (superCallback) {
8185 superCallback();
8186 }
8187
8188 this.init();
8189 };
8190 } else {
8191 this.ve.loadScript(apiScript, () => {
8192 this.init(true);
8193 });
8194 }
8195 }
8196
8197 setupInterface() {
8198 Object.assign(this.ve, {
8199 play: this.api.playVideo.bind(this.api),
8200 pause: this.api.pauseVideo.bind(this.api),
8201 mute: this.api.mute.bind(this.api),
8202 unmute: this.api.unMute.bind(this.api),
8203 stop: () => {
8204 this.api.seekTo(0);
8205 this.api.pauseVideo();
8206 },
8207 on: this.on.bind(this),
8208 off: this.off.bind(this)
8209 });
8210 }
8211
8212 on(type, listener) {
8213 if (!this._eventAdded) {
8214 this._eventAdded = true;
8215 this.api.addEventListener('onStateChange', this._onStateChange, false);
8216 }
8217
8218 if (!this._listeners[type]) {
8219 this._listeners[type] = [listener];
8220 } else {
8221 this._listeners[type].push(listener);
8222 }
8223 }
8224
8225 off(type, listener) {
8226 if (this._listeners[type]) {
8227 const index = this._listeners[type].indexOf(listener);
8228
8229 if (index !== -1) {
8230 this._listeners[type].splice(index, 1);
8231 }
8232 }
8233 }
8234
8235 _apiReady() {
8236 this.setupInterface();
8237 this.ve.playerIsReady(this.api);
8238 this.api.removeEventListener('onReady', this._apiReady, false);
8239 }
8240
8241 _onStateChange(e) {
8242 let type;
8243
8244 switch (e.data) {
8245 case 0:
8246 type = 'ended';
8247 break;
8248
8249 case 1:
8250 type = 'play';
8251 break;
8252
8253 case 2:
8254 type = 'pause';
8255 break;
8256
8257 default:
8258 return;
8259 }
8260
8261 if (this._listeners[type]) {
8262 this._listeners[type].forEach(listener => {
8263 listener();
8264 });
8265 }
8266 }
8267
8268 }
8269
8270 VideoElement.registerPlayer('youtube', YouTube);
8271
8272 class Plyr {
8273 constructor(videoElement) {
8274 this.ve = videoElement;
8275 }
8276
8277 init() {
8278 if (!window.plyr) {
8279 throw new Error('Plyr not found.');
8280 }
8281
8282 [this.api] = window.plyr.setup(this.ve.element);
8283 this.setupInterface();
8284 this.ve.playerIsReady(this.api);
8285 this.ve.element = this.api.getContainer();
8286 }
8287
8288 setupInterface() {
8289 Object.assign(this.ve, {
8290 play: this.api.play.bind(this.api),
8291 pause: this.api.pause.bind(this.api),
8292 stop: this.api.stop.bind(this.api),
8293 on: this.api.on.bind(this.api),
8294 // off: this.api.off.bind(this.api),
8295 mute: () => {
8296 if (!this.api.isMuted()) {
8297 this.api.toggleMute();
8298 }
8299 },
8300 unmute: () => {
8301 if (this.api.isMuted()) {
8302 this.api.toggleMute();
8303 }
8304 }
8305 });
8306 }
8307
8308 }
8309
8310 VideoElement.registerPlayer('plyr', Plyr);
8311
8312 /**
8313 * Video layer type class
8314 */
8315
8316 class VideoLayer extends Layer {
8317 /**
8318 * Creates new layer
8319 * @param {Element} element Layer element
8320 * @param {Layers} controller Layer controller
8321 * @param {*} holder The layers holder object for sections layers, it is the section object
8322 * @param {Number} index Layer index number
8323 * @param {Boolean} isLinked Whether the layer is linked or not
8324 * @param {Layer} parent Layer's parent layer
8325 */
8326 constructor(element, controller, holder, index, isLinked, parent) {
8327 super(element, controller, holder, index, isLinked, parent);
8328 this.type = 'video';
8329 this.frame.classList.add(`${prefix}-${this.type}-layer`);
8330 this.playVideo = this.playVideo.bind(this);
8331 this._videoState = 'initial';
8332 this.holder.hasVideoLayer = true; // change default scale type to box resize
8333 // if (!this.element.hasAttribute('data-scale-type')) {
8334 // this.element.setAttribute('data-scale-type', 'box');
8335 // }
8336 }
8337 /**
8338 * Start playing the video
8339 */
8340
8341
8342 playVideo(e) {
8343 if (!this.holder.active || this.holder.status === 'leaving' || this._videoState === 'playing' || !this.videoElement.ready || !this.bpVisible) {
8344 return;
8345 }
8346
8347 if (e) {
8348 this.trigger('playByBtn', [this], true);
8349 }
8350
8351 this.videoElement.play();
8352 this.element.classList.add(`${prefix}-playing`);
8353 }
8354 /**
8355 * Stop the video
8356 */
8357
8358
8359 stopVideo() {
8360 if (this._videoState === 'stopped' || !this.videoElement.ready) {
8361 return;
8362 }
8363
8364 if (this.autoPause) {
8365 this.videoElement.pause();
8366 } else {
8367 this.videoElement.stop();
8368 }
8369
8370 this.element.classList.remove(`${prefix}-playing`);
8371 }
8372 /**
8373 * Manipulates layer content
8374 */
8375
8376
8377 _setupContent() {
8378 this.coverImage = this.element.querySelector('img');
8379 this.videoSource = this.element.querySelector('iframe, video');
8380 this.autoplay = this.element.getAttribute('data-autoplay') === 'true';
8381 this.autoPause = this.element.getAttribute('data-auto-pause') === 'true'; // this.waitForAnimEnd = this.element.getAttribute( 'data-wait-for-anim' ) !== 'false';
8382
8383 if (!this.videoSource) {
8384 return;
8385 }
8386
8387 if (this.videoSource.tagName === 'IFRAME') {
8388 // make sure the iframe video does not have any autoplay parameter
8389 this.videoSource.src = this.videoSource.src.replace('autoplay=1', '');
8390 } else if (this.videoSource.tagName === 'VIDEO' && !this.videoSource.hasAttribute('data-player-type')) {
8391 const fitMode = this.videoSource.getAttribute('data-object-fit') || 'cover';
8392 this.videoSource.style.objectFit = fitMode;
8393 this.videoSource.setAttribute('data-object-fit', fitMode);
8394
8395 if (this.videoSource.hasAttribute('data-object-position')) {
8396 const fitFrom = this.videoSource.getAttribute('data-object-position');
8397 this.videoSource.style.objectPosition = fitFrom;
8398 }
8399
8400 this.videoSource.setAttribute('playsinline', '');
8401 this.videoSource.setAttribute('webkit-playsinline', '');
8402 }
8403
8404 this.videoElement = new VideoElement(this.videoSource);
8405 this.videoElement.setup(this._videoControllerReady.bind(this)); // add class name to the video player element
8406
8407 this.videoElement.element.classList.add(`${prefix}-video-player`); // make media element responsive
8408
8409 if (this.videoElement.type === 'mejs') {
8410 this.videoElement.player.api.options.stretching = 'responsive';
8411 }
8412
8413 if (this.coverImage) {
8414 this.playBtn = document.createElement('div');
8415 this.playBtn.classList.add(`${prefix}-video-btn`);
8416 this.playBtn.addEventListener('click', this.playVideo, false);
8417 this.element.appendChild(this.playBtn); // hold for loading the cover image
8418
8419 this.holder.readyTrigger.hold();
8420 }
8421
8422 if (this.autoplay) {
8423 this.holder.on('activated', this.playVideo, this);
8424 }
8425
8426 this.holder.on('deactivated', this.stopVideo, this);
8427 this.holder.on('loadingStart', this._startLoading, this);
8428 this.on('visibilityChange', (action, layer, hidden) => {
8429 if (!hidden && (this.autoplay || this.wasPlaying)) {
8430 this.playVideo();
8431 } else if (hidden) {
8432 this.wasPlaying = this._videoState === 'playing';
8433 this.stopVideo();
8434 }
8435 });
8436 }
8437 /**
8438 * On video player API
8439 */
8440
8441
8442 _videoControllerReady() {
8443 this._onVideoPlay = this._onVideoPlay.bind(this);
8444 this._onVideoPause = this._onVideoPause.bind(this);
8445 this._onVideoEnded = this._onVideoEnded.bind(this);
8446 this.videoElement.on('play', this._onVideoPlay);
8447 this.videoElement.on('pause', this._onVideoPause);
8448 this.videoElement.on('ended', this._onVideoEnded);
8449
8450 if (window.objectFitPolyfill) {
8451 window.objectFitPolyfill(this.videoSource);
8452 }
8453
8454 if (this.autoplay) {
8455 this.playVideo();
8456 }
8457 }
8458 /**
8459 * Start loading the image by replacing the src
8460 */
8461
8462
8463 _startLoading() {
8464 if (this.coverImage) {
8465 loadImage(this.coverImage, this._loaded.bind(this), this._error.bind(this));
8466 }
8467 }
8468 /**
8469 * Image is loaded
8470 */
8471
8472
8473 _loaded() {
8474 this.coverImage.classList.add(`${prefix}-loaded`);
8475 this.holder.readyTrigger.exec();
8476 }
8477 /**
8478 * Image loading failed
8479 */
8480
8481
8482 _error() {
8483 this.holder.readyTrigger.exec();
8484 }
8485 /**
8486 * On video play event listener
8487 */
8488
8489
8490 _onVideoPlay() {
8491 this._videoState = 'playing';
8492 this.trigger('videoPlay', [this], true);
8493 }
8494 /**
8495 * On video pause event listener
8496 */
8497
8498
8499 _onVideoPause() {
8500 this._videoState = 'stopped';
8501 this.trigger('videoPause', [this], true);
8502 }
8503 /**
8504 * On video ended event listener
8505 */
8506
8507
8508 _onVideoEnded() {
8509 this._videoState = 'ended';
8510 this.trigger('videoEnded', [this], true);
8511 }
8512
8513 }
8514
8515 Layers.registerLayer('video', VideoLayer);
8516
8517 // It can be overwritten by adding .ms-hotspot-pont-template element as a direct child of composer
8518
8519 const defaultPointMarkup = `<div class="${prefix}-hotspot-point ${prefix}-tooltip-point">
8520 <div class="${prefix}-point-center"></div>
8521 <div class="${prefix}-point-border"></div>
8522 </div>`;
8523 let pointMarkup;
8524 /**
8525 * Hotspot layer type class
8526 * It shows a hotspot point when user rolls over the point a tooltip appears
8527 */
8528
8529 class HotspotLayer extends Layer {
8530 /**
8531 * Creates new layer
8532 * @param {Element} element Layer element
8533 * @param {Layers} controller Layer controller
8534 * @param {*} holder The layers holder object for sections layers, it is the section object
8535 * @param {Number} index Layer index number
8536 * @param {Boolean} isLinked Whether the layer is linked or not
8537 * @param {Layer} parent Layer's parent layer
8538 */
8539 constructor(element, controller, holder, index, isLinked, parent) {
8540 super(element, controller, holder, index, isLinked, parent);
8541 this.type = 'hotspot';
8542 this._hidden = true;
8543 this.frame.classList.add(`${prefix}-${this.type}-layer`);
8544
8545 if (!pointMarkup) {
8546 const template = controller.composer.element.querySelector(`.${prefix}-hotspot-point-template`);
8547
8548 if (template) {
8549 pointMarkup = template.outerHTML;
8550 template.remove();
8551 } else {
8552 pointMarkup = defaultPointMarkup;
8553 }
8554 }
8555 }
8556 /**
8557 * Manipulates layer content
8558 */
8559
8560
8561 _setupContent() {
8562 this._mouseX = 0;
8563 this._mouseY = 0; // read data attributes from element
8564
8565 this.align = this.element.getAttribute('data-align') || 'top';
8566 this.tooltipWidth = parseInt(this.element.getAttribute('data-width'), 10) || 200;
8567 this.transparent = this.element.getAttribute('data-transparent') === 'true'; // search for custom point markup in the layer markup
8568
8569 let hotspotPoint = this.element.querySelector(`.${prefix}-hotspot-point`);
8570
8571 if (hotspotPoint) {
8572 // exclude it from the tooltip content
8573 hotspotPoint.parentElement.removeChild(hotspotPoint);
8574 }
8575
8576 this.content = this.element.innerHTML;
8577 this.element.innerHTML = '';
8578
8579 if (!this.transparent) {
8580 if (hotspotPoint) {
8581 this.element.appendChild(hotspotPoint);
8582 } else {
8583 this.element.innerHTML = pointMarkup;
8584 hotspotPoint = this.element.querySelector(`.${prefix}-hotspot-point`);
8585 }
8586 } else {
8587 hotspotPoint = this.element;
8588 }
8589
8590 this._mouseInteraction = this._mouseInteraction.bind(this);
8591 hotspotPoint.addEventListener('mouseenter', this._mouseInteraction, false);
8592 hotspotPoint.addEventListener('mouseleave', this._mouseInteraction, false); // generate tooltip markup
8593
8594 const tooltip = document.createElement('div');
8595 tooltip.classList.add(`${prefix}-hotspot-tooltip`);
8596 tooltip.classList.add(`${prefix}-align-${this.align}`);
8597
8598 if (this.element.hasAttribute('data-tooltip-class')) {
8599 this.element.getAttribute('data-tooltip-class').split(' ').forEach(className => {
8600 tooltip.classList.add(className);
8601 });
8602 }
8603
8604 this.tooltipContainer = document.createElement('div');
8605 this.tooltipContainer.classList.add(`${prefix}-tooltip-cont`);
8606 this.tooltipContainer.innerHTML = this.content;
8607 this.tooltipContainer.style.width = this.tooltipWidth + 'px';
8608
8609 if (this.element.getAttribute('data-stay-hover') === 'true') {
8610 this._tooltipMouseInteraction = this._tooltipMouseInteraction.bind(this);
8611 this.tooltipContainer.addEventListener('mouseenter', this._tooltipMouseInteraction, false);
8612 this.tooltipContainer.addEventListener('mouseleave', this._tooltipMouseInteraction, false);
8613 }
8614
8615 tooltip.appendChild(this.tooltipContainer);
8616 this.holder.composer.layoutController.primaryContainer.appendChild(tooltip);
8617 this.tooltip = tooltip;
8618 this.hotspotPoint = hotspotPoint;
8619 }
8620 /**
8621 * Mouse interaction listener on hotspot point element
8622 * @param {MouseEvent} event
8623 */
8624
8625
8626 _mouseInteraction(event) {
8627 switch (event.type) {
8628 case 'mouseenter':
8629 this._mouseX = event.clientX;
8630 this._mouseY = event.clientY;
8631
8632 this._locateTooltip();
8633
8634 setTimeout(this._showTooltip.bind(this), 1);
8635 break;
8636
8637 case 'mouseleave':
8638 default:
8639 this._hideTooltip();
8640
8641 }
8642 }
8643 /**
8644 * Mouse interaction listener on tooltip element
8645 * @param {MouseEvent} event
8646 */
8647
8648
8649 _tooltipMouseInteraction(event) {
8650 switch (event.type) {
8651 case 'mouseenter':
8652 if (this._hidden) {
8653 return;
8654 }
8655
8656 this._showTooltip();
8657
8658 break;
8659
8660 case 'mouseleave':
8661 default:
8662 this._hideTooltip();
8663
8664 }
8665 }
8666 /**
8667 * Show tooltip
8668 */
8669
8670
8671 _showTooltip() {
8672 clearTimeout(this._hideTimeout);
8673
8674 if (this._hidden) {
8675 this.tooltip.classList.add(`${prefix}-tooltip-active`);
8676 this._hidden = false;
8677 }
8678 }
8679 /**
8680 * Hide tooltip
8681 */
8682
8683
8684 _hideTooltip() {
8685 clearTimeout(this._hideTimeout);
8686 this._hideTimeout = setTimeout(() => {
8687 this._hidden = true;
8688 this.tooltip.classList.remove(`${prefix}-tooltip-active`);
8689 }, 200);
8690 }
8691 /**
8692 * Checks tooltip location in the page based on the given alignment value, if tooltip does not fit correctly,
8693 * returns alternative alignment
8694 * @param {String} align
8695 */
8696
8697
8698 _alignPolicy(align) {
8699 const tooltipHeight = this.tooltip.offsetHeight;
8700
8701 switch (align) {
8702 case 'top':
8703 default:
8704 if (this.pointY - tooltipHeight < 0) {
8705 return 'bottom';
8706 }
8707
8708 break;
8709
8710 case 'right':
8711 if (this.pointX + this.tooltipWidth > window.innerWidth) {
8712 return 'bottom';
8713 }
8714
8715 break;
8716
8717 case 'left':
8718 if (this.pointX - this.tooltipWidth < 0) {
8719 return 'bottom';
8720 }
8721
8722 }
8723
8724 return null;
8725 }
8726 /**
8727 * Locate tooltip in the page based on alignment and hotspot position
8728 * @param {String} align
8729 */
8730
8731
8732 _locateTooltip(align) {
8733 align = align || this.align;
8734 let pointX;
8735 let pointY;
8736 const margin = 20; // Add space to left or right side of tooltip if it is near to the window
8737
8738 const rect = this.frame.getBoundingClientRect();
8739 pointX = rect.left + window.pageXOffset;
8740 pointY = rect.top + window.pageYOffset;
8741
8742 if (this.transparent) {
8743 pointX += this._mouseX - rect.left;
8744 pointY += this._mouseY - rect.top;
8745 }
8746
8747 const posX = pointX; // localize
8748
8749 pointX -= this.composer.element.offsetLeft + this.composer.element.scrollLeft;
8750 pointY -= this.composer.element.offsetTop + this.composer.element.scrollTop;
8751 this.pointX = pointX;
8752 this.pointY = pointY;
8753 this.tooltipContainer.style.left = '';
8754 this.tooltipContainer.style.right = '';
8755 this.tooltipContainer.width = this.tooltipWidth + 'px';
8756 this.tooltip.classList.add(`${prefix}-no-transition`);
8757
8758 if (this._lastAlign) {
8759 this.tooltip.classList.remove(`${prefix}-align-${this._lastAlign}`);
8760 }
8761
8762 this.tooltip.classList.add(`${prefix}-align-${align}`);
8763 this._lastAlign = align;
8764
8765 if (align === 'bottom' || align === 'top') {
8766 let width = this.tooltipWidth;
8767
8768 if (this.tooltipWidth >= window.innerWidth) {
8769 this.tooltipContainer.style.width = window.innerWidth - margin * 2 + 'px';
8770 width = window.innerWidth - margin * 2;
8771 } else {
8772 this.tooltipContainer.style.width = width + 'px';
8773 }
8774
8775 let rightSpace = window.innerWidth - width / 2 - posX;
8776
8777 if (rightSpace < 0) {
8778 rightSpace -= margin;
8779 this.tooltipContainer.style.right = -rightSpace + 'px';
8780 } else {
8781 let leftSpace = posX - width / 2;
8782 leftSpace -= margin;
8783
8784 if (leftSpace < 0) {
8785 this.tooltipContainer.style.left = -leftSpace + 'px';
8786 }
8787 }
8788 }
8789
8790 const alignPolicy = this._alignPolicy(align);
8791
8792 if (alignPolicy) {
8793 this._locateTooltip(alignPolicy);
8794
8795 return;
8796 }
8797
8798 this.tooltip.style.left = pointX + 'px';
8799 this.tooltip.style.top = pointY + 'px';
8800 this.tooltip.classList.remove(`${prefix}-no-transition`);
8801 }
8802
8803 }
8804
8805 Layers.registerLayer('hotspot', HotspotLayer);
8806
8807 /**
8808 * Custom layer type, it can contain any HTML content
8809 */
8810
8811 class CustomLayer extends Layer {
8812 /**
8813 * Creates new layer
8814 * @param {Element} element Layer element
8815 * @param {Layers} controller Layer controller
8816 * @param {*} holder The layers holder object for sections layers, it is the section object
8817 * @param {Number} index Layer index number
8818 * @param {Boolean} isLinked Whether the layer is linked or not
8819 * @param {Layer} parent Layer's parent layer
8820 */
8821 constructor(element, controller, holder, index, isLinked, parent) {
8822 super(element, controller, holder, index, isLinked, parent);
8823 this.type = 'group';
8824 this.nestable = true;
8825 this.frame.classList.add(`${prefix}-${this.type}-layer`);
8826 }
8827
8828 }
8829 Layers.registerLayer('group', CustomLayer);
8830
8831 /**
8832 * Flex layer type, it can contain any HTML content
8833 */
8834
8835 class FlexLayer extends Layer {
8836 /**
8837 * Creates new layer
8838 * @param {Element} element Layer element
8839 * @param {Layers} controller Layer controller
8840 * @param {*} holder The layers holder object for sections layers, it is the section object
8841 * @param {Number} index Layer index number
8842 * @param {Boolean} isLinked Whether the layer is linked or not
8843 * @param {Layer} parent Layer's parent layer
8844 */
8845 constructor(element, controller, holder, index, isLinked, parent) {
8846 super(element, controller, holder, index, isLinked, parent);
8847 this.type = 'flex';
8848 this.nestable = true;
8849 this.frame.classList.add(`${prefix}-${this.type}-layer`);
8850 this.disablePositionHandler = true;
8851 }
8852
8853 }
8854 Layers.registerLayer('flex', FlexLayer);
8855
8856 /**
8857 * Always returns the value between 0 and given max value, useful when you need to find value in a loop
8858 * @param {Number} value Current value
8859 * @param {Number} max Max value value
8860 */
8861 /**
8862 * Whether element has the attribute(s) or not
8863 * @param {Element} element Target element
8864 * @param {RegExp} pattern The attribute regex pattern
8865 */
8866
8867 function hasAttribute(element, pattern) {
8868 let result = false;
8869 [].some.call(element.attributes, attribute => {
8870 result = pattern.test(attribute.name);
8871 return result;
8872 });
8873 return result;
8874 }
8875
8876 const _excluded = ["type"];
8877 /**
8878 * Creates in and out animations and adds animation control methods to the target object
8879 */
8880
8881 class InOutAnimation {
8882 /**
8883 * Whether the element supports animation in or animation out
8884 * @param {Element} element
8885 */
8886 static isAnimative(element) {
8887 return hasAttribute(element, /^(data(-\w+)*-animation-(in|out))$/g);
8888 }
8889 /**
8890 * Creates new instance
8891 * @param {Object|Layer} target Target element holder
8892 * @param {Element} targetElement Target element that has the animation attributes
8893 */
8894
8895
8896 constructor(target, targetElement) {
8897 this.target = target;
8898 this.element = targetElement;
8899 this.sourceElement = target.element;
8900 const inAttributes = getAttrValues(this.sourceElement, 'animation-in') || {};
8901 const outAttributes = getAttrValues(this.sourceElement, 'animation-out') || {};
8902 this.animationsData = ['none', ...breakpointNames].map(breakpoint => {
8903 const activeInAnimData = getResponsiveValue(inAttributes, breakpoint);
8904 const activeOutAnimData = getResponsiveValue(outAttributes, breakpoint);
8905 const animationIn = activeInAnimData ? this.parseAnimationData(activeInAnimData) : false;
8906 const animationOut = activeOutAnimData ? this.parseAnimationData(activeOutAnimData) : false;
8907 return {
8908 animationIn,
8909 animationOut
8910 };
8911 });
8912 responsiveHelper.on('breakpointChange', this.setAnimator, this);
8913 this.setAnimator();
8914 /**
8915 * Go to and play related animation based on given type
8916 * @param {String} phase "in" or "out"
8917 */
8918
8919 target.animateInOut = (phase, restart = false) => {
8920 if (['in', 'out'].includes(phase)) {
8921 this.startAnimation(phase, restart);
8922 }
8923 };
8924
8925 target.show = () => target.animateInOut('in');
8926
8927 target.hide = () => target.animateInOut('out');
8928 /**
8929 * Changes the target animation progress value based on given type
8930 * @param {Number} progress Between 0 and 1
8931 * @param {String} phase "in" or "out"
8932 */
8933
8934
8935 target.progressInOut = (progress, phase) => {
8936 if (['in', 'out'].includes(phase)) {
8937 this.progressAnimation(phase, progress);
8938 }
8939 };
8940 }
8941
8942 parseAnimationData(data) {
8943 const jsonStrData = data.replace(/'/g, '"');
8944 let dataObject = '';
8945
8946 try {
8947 dataObject = JSON.parse(jsonStrData);
8948 } catch (e) {
8949 console.warn('Given animation data value is not a valid JSON, animation skipped. \n ' + jsonStrData);
8950 return '';
8951 }
8952
8953 return dataObject;
8954 }
8955
8956 _animationBegin(phase) {
8957 this.status = phase + '-start';
8958 this.target.trigger(phase === 'in' ? 'animationInStart' : 'animationOutStart', [this.target, this.status], true);
8959 }
8960
8961 _animationEnd(phase) {
8962 this.status = phase + '-end';
8963 this.target.trigger(phase === 'in' ? 'animationInEnd' : 'animationOutEnd', [this.target, this.status], true);
8964
8965 if (phase === 'in') {
8966 // remove animation in after it ends
8967 this.removeActiveAnimator();
8968 }
8969 }
8970
8971 removeActiveAnimator() {
8972 if (this.activeAnimator) {
8973 this.activeAnimator.reset();
8974 this.activeAnimator = null;
8975 }
8976 }
8977
8978 generateNewAnimator(phase) {
8979 const animationData = getResponsiveValue(this.animationsData);
8980
8981 if (!this.hasAnimation(phase, animationData)) {
8982 return null;
8983 }
8984
8985 const _animationData = animationData[phase === 'in' ? 'animationIn' : 'animationOut'],
8986 {
8987 type
8988 } = _animationData,
8989 params = _objectWithoutProperties(_animationData, _excluded);
8990
8991 const newAnimator = animator__default["default"].animate(type, this.element, phase, params, null, {
8992 autoplay: false,
8993 begin: () => this._animationBegin(phase),
8994 complete: () => this._animationEnd(phase)
8995 });
8996 this.status = phase + '-init';
8997 this.activePhase = phase;
8998 return newAnimator;
8999 }
9000
9001 startAnimation(phase, restart = false) {
9002 if (phase !== this.activePhase || !this.activeAnimator) {
9003 this.removeActiveAnimator();
9004 this.activeAnimator = this.generateNewAnimator(phase);
9005 }
9006
9007 if (!this.activeAnimator) {
9008 return;
9009 }
9010
9011 const {
9012 timeline
9013 } = this.activeAnimator;
9014
9015 if (restart || phase === 'in' && this.status === 'in-init' || phase === 'out' && this.status === 'out-init') {
9016 timeline.seek(0);
9017 timeline.play();
9018 }
9019 }
9020
9021 progressAnimation(phase, progress) {
9022 if (phase !== this.activePhase || !this.activeAnimator) {
9023 this.removeActiveAnimator();
9024 this.activeAnimator = this.generateNewAnimator(phase);
9025 }
9026
9027 if (!this.activeAnimator) {
9028 return;
9029 }
9030
9031 const {
9032 timeline
9033 } = this.activeAnimator;
9034 timeline.seek(timeline.duration * progress);
9035 }
9036 /**
9037 * Changes element animation based on current active breakpoint
9038 */
9039
9040
9041 setAnimator() {
9042 if (this.status === 'in-end') {
9043 return;
9044 }
9045
9046 let startProgress = 0;
9047 let lastAnimatorWasActive = false;
9048
9049 if (!this.activePhase) {
9050 this.activePhase = 'in';
9051 }
9052
9053 if (this.activeAnimator) {
9054 const {
9055 timeline
9056 } = this.activeAnimator;
9057 startProgress = timeline.progress;
9058 lastAnimatorWasActive = timeline.began && !timeline.paused;
9059 this.removeActiveAnimator();
9060 }
9061
9062 this.activeAnimator = this.generateNewAnimator(this.activePhase);
9063
9064 if (!this.activeAnimator) {
9065 // we set in-end even if the active phase is out to remove the out animation footprint since the out animation is not set for the current breakpoint
9066 this.status = 'in-end';
9067 return;
9068 }
9069
9070 const {
9071 timeline: newTimeline
9072 } = this.activeAnimator;
9073
9074 if (startProgress) {
9075 newTimeline.seek(newTimeline.duration * (startProgress / 100));
9076 }
9077
9078 if (lastAnimatorWasActive) {
9079 newTimeline.play();
9080 }
9081 }
9082 /**
9083 * Whether animation in or out is available or not
9084 * @param {String} phase "in" or "out"
9085 */
9086
9087
9088 hasAnimation(phase, animationData) {
9089 return phase === 'in' ? !!animationData.animationIn : !!animationData.animationOut;
9090 }
9091
9092 }
9093
9094 /**
9095 * Wraps the layer element with an animation wrap container
9096 * @param {Element} layerElement
9097 */
9098
9099 function wrapLayerElement(layerElement) {
9100 if (layerElement.parentElement.classList.contains(`.${prefix}-animation-wrap`)) {
9101 return layerElement.parentElement;
9102 }
9103
9104 const animationWrap = document.createElement('div');
9105 animationWrap.classList.add(`${prefix}-animation-wrap`);
9106 layerElement.parentElement.insertBefore(animationWrap, layerElement);
9107 animationWrap.appendChild(layerElement);
9108 return animationWrap;
9109 }
9110 /**
9111 * Section status listener, it plays animation in or out on section get activated or deactivated
9112 * @param {String} action
9113 */
9114
9115
9116 function checkSectionLayerStatus(action, layer) {
9117 if (action === 'readyAndActivated') {
9118 if (!layer.waitForAction) {
9119 layer.animateInOut('in');
9120 }
9121 } else if (action === 'readyAndDeactivated') {
9122 if (layer.autoAnimateOut) {
9123 layer.animateInOut('out');
9124 }
9125 }
9126 }
9127 /**
9128 * Changes the animation progress value based on section pendingOffset value
9129 * @param {String} action
9130 * @param {Section} holder
9131 * @param {Number} offset
9132 * @param {Number} progress
9133 */
9134
9135
9136 function progressInOutAnimation(action, layer, holder, offset, progress, inIsActive, outIsActive) {
9137 if (progress >= 0 && inIsActive) {
9138 layer.progressInOut(Math.max(0, 1 - progress), 'in');
9139 } else if (progress < 0 && outIsActive) {
9140 layer.progressInOut(Math.min(1, -progress), 'out');
9141 }
9142 }
9143 /**
9144 * Sets animation in and out to the layer and controls its playback relative to the active sections in composer
9145 * @param {Layer} layer Target layer
9146 * @param {Composer} composer
9147 */
9148
9149
9150 function setInOutAnimation(layer, composer) {
9151 layer.inOutAnimation = new InOutAnimation(layer, layer.animationWrap);
9152 layer.interactiveAnimationIn = layer.element.getAttribute('data-animation-in-interactive');
9153 layer.interactiveAnimationOut = layer.element.getAttribute('data-animation-out-interactive');
9154 layer.waitForAction = layer.element.getAttribute('data-wait-for-action') === 'true';
9155 layer.waitOnAnimationOut = layer.element.getAttribute('data-animation-out-wait') !== 'false';
9156 layer.autoAnimateOut = layer.element.getAttribute('data-animation-out-on-change') === 'true' || composer.options.get('hideLayers');
9157 const isOnSurface = layer.holder instanceof LayersSurface;
9158 /**
9159 * isOnSurface flag specifies the layer is located over an isolated area or not. These layers are not relative to sections like overlay layers
9160 */
9161
9162 if (!layer.waitForAction && !isOnSurface) {
9163 controlAnimationInOut(layer);
9164 } // call animation out async to let the timeline passes the pause
9165
9166
9167 layer.on('animationInEnd', () => setTimeout(() => {
9168 if (!layer.waitOnAnimationOut && !layer.disableAutoAnimateOut) {
9169 layer.animateInOut('out');
9170 }
9171 }));
9172 }
9173
9174 function controlAnimationInOut(layer) {
9175 const onSectionStatusChange = action => checkSectionLayerStatus(action, layer);
9176
9177 let onSectionOffsetChange;
9178
9179 const switchMode = args => {
9180 const [interactiveIn, interactiveOut] = args.map(arg => arg === 'true');
9181
9182 if (onSectionOffsetChange) {
9183 layer.holder.off('pendingOffsetChange', onSectionOffsetChange);
9184 }
9185
9186 if (interactiveIn || interactiveOut) {
9187 onSectionOffsetChange = (action, holder, offset, progress) => {
9188 progressInOutAnimation(action, layer, holder, offset, progress, interactiveIn, interactiveOut);
9189 };
9190
9191 layer.disableAutoAnimateOut = true;
9192 layer.holder.on('pendingOffsetChange', onSectionOffsetChange);
9193
9194 if (layer.holder.active) {
9195 layer.holder.triggerPendingOffsetChange();
9196 }
9197 } else {
9198 layer.disableAutoAnimateOut = false;
9199 }
9200
9201 if (!interactiveIn) {
9202 if (layer.holder.active) {
9203 onSectionStatusChange('readyAndActivated');
9204 }
9205
9206 layer.holder.on('readyAndActivated', onSectionStatusChange);
9207 } else {
9208 layer.holder.off('readyAndActivated', onSectionStatusChange);
9209 }
9210
9211 if (!interactiveOut) {
9212 // if (!layer.holder.active) {
9213 // onSectionStatusChange('readyAndDeactivated');
9214 // }
9215 layer.holder.on('readyAndDeactivated', onSectionStatusChange);
9216 } else {
9217 layer.holder.off('readyAndDeactivated', onSectionStatusChange);
9218 }
9219 };
9220
9221 watchMultipleResponsiveValues([layer.interactiveAnimationIn, layer.interactiveAnimationOut], switchMode);
9222 } // /**
9223 // * Controls steps animation of the layer on view's index change or scrolls
9224 // * @param {Layer} layer Target layer
9225 // * @param {Composer} composer
9226 // */
9227 // function setStepAnimation(layer, composer) {
9228 // layer.stepAnimation = new StepAnimation(
9229 // layer,
9230 // layer.animationWrap,
9231 // composer.layoutController,
9232 // composer.view.sectionsCount
9233 // );
9234 // layer.hasStepAnimation = true;
9235 // layer.interactiveAnimation = layer.element.getAttribute('data-interactive-animation') === 'true';
9236 // const { view } = composer;
9237 // if (!layer.interactiveAnimation) {
9238 // composer.on('targetIndexChange', (action, index) => layer.animateToStep(index));
9239 // } else {
9240 // composer.on('scroll', () => {
9241 // if (view.visibleIndex === undefined) {
9242 // return;
9243 // }
9244 // const targetIndex = view.visibleIndex;
9245 // const unitProgress = 1 / view.sectionsCount;
9246 // const targetSection = view.sections[targetIndex];
9247 // layer.progressStep(
9248 // unitProgress * targetSection.index +
9249 // Math.abs(targetSection.pendingOffset / targetSection.size) * unitProgress
9250 // );
9251 // });
9252 // }
9253 // }
9254
9255 /**
9256 * Layer animation adapter class
9257 * It reads animation data attributes from the layer element and applies them to the layer animation wrapper element
9258 */
9259
9260
9261 class AnimationAdapter {
9262 constructor(composer) {
9263 this.composer = composer;
9264 this.composer.options.register({
9265 hideLayers: true
9266 });
9267 this._stepAnimationLayers = [];
9268 composer.on('layerBeforeInit', this._checkLayer, this); // composer.on('sectionsSetup', this._checkStepAnimationLayers, this);
9269 }
9270 /**
9271 * Checks the layer for whether is has animation or not
9272 * @param {String} action
9273 * @param {Layer} layer
9274 */
9275
9276
9277 _checkLayer(action, layer) {
9278 if (InOutAnimation.isAnimative(layer.element)) {
9279 layer.animationWrap = wrapLayerElement(layer.element);
9280 setInOutAnimation(layer, this.composer);
9281 } // if (StepAnimation.isAnimative(layer.element)) {
9282 // layer.animationWrap = wrapLayerElement(layer.element);
9283 // v;
9284 // this._stepAnimationLayers.push(layer);
9285 // }
9286
9287 } // _checkStepAnimationLayers() {
9288 // this._stepAnimationLayers.forEach((layer) => {
9289 // setStepAnimation(layer, this.composer);
9290 // });
9291 // }
9292
9293
9294 }
9295
9296 Composer.registerAddon('layerAnimationAdapter', AnimationAdapter);
9297
9298 // ---------------------------------------------------------------------------------------------
9299 // constants
9300 const te = ('ontouchstart' in document);
9301 const pe = window.PointerEvent;
9302 const mpe = window.MSPointerEvent;
9303 const iOS = /iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream;
9304 /* eslint-disable */
9305
9306 const startEvent = pe ? 'pointerdown' : mpe ? 'MSPointerDown' : te ? 'touchstart' : 'mousedown'; // prettier-ignore
9307
9308 const endEvent = pe ? 'pointerup' : mpe ? 'MSPointerUp' : te ? 'touchend' : 'mouseup'; // prettier-ignore
9309
9310 const moveEvent = pe ? 'pointermove' : mpe ? 'MSPointerMove' : te ? 'touchmove' : 'mousemove'; // prettier-ignore
9311
9312 const cancelEvent = pe ? 'pointercancel' : mpe ? 'MSPointerCancel' : te ? 'touchcancel' : ''; // prettier-ignore
9313
9314 /* eslint-enable */
9315 // ---------------------------------------------------------------------------------------------
9316
9317 class Swipe {
9318 constructor(element) {
9319 this.element = element;
9320 this._direction = 'horizontal';
9321 this.noSwipeSelector = ''; // input, textarea, button, .no-swipe, .ms-no-swipe';
9322
9323 this.preventDefault = 'auto';
9324 this._lastStatus = {};
9325 this._touchStart = this._touchStart.bind(this);
9326 this._touchEnd = this._touchEnd.bind(this);
9327 this._touchMove = this._touchMove.bind(this);
9328 this._touchCancel = this._touchCancel.bind(this);
9329 this._reset = this._reset.bind(this);
9330 this.enable();
9331 }
9332
9333 get direction() {
9334 return this._direction;
9335 }
9336
9337 set direction(value) {
9338 this._direction = value;
9339 let touchAction = 'pan-x pan-y';
9340
9341 if (value !== 'both') {
9342 touchAction = value === 'horizontal' ? 'pan-y' : 'pan-x';
9343 }
9344
9345 this.element.style.msTouchAction = touchAction;
9346 this.element.style.touchAction = touchAction;
9347 }
9348 /**
9349 * Detects the swipe direction
9350 * @param {Number} newX
9351 * @param {Number} newY
9352 */
9353
9354
9355 _getDirection(newX, newY) {
9356 switch (this._direction) {
9357 case 'horizontal':
9358 return newX <= this.startX ? 'left' : 'right';
9359
9360 case 'vertical':
9361 return newY <= this.startY ? 'up' : 'down';
9362
9363 case 'both':
9364 default:
9365 if (Math.abs(newX - this.startX) > Math.abs(newY - this.startY)) {
9366 return newX <= this.startX ? 'left' : 'right';
9367 }
9368
9369 return newY <= this.startY ? 'up' : 'down';
9370 }
9371 }
9372 /**
9373 * Event default preventing helper. It checks the movement with the desired direction
9374 * @param {Number} newX
9375 * @param {Number} newY
9376 * @return {Boolean}
9377 */
9378
9379
9380 _preventDefaultEvent(newX, newY) {
9381 if (this.preventDefault !== 'auto') {
9382 return this.preventDefault;
9383 }
9384
9385 if (this._preventLock) {
9386 return true;
9387 }
9388
9389 const horizontal = Math.abs(newX - this.startX) > Math.abs(newY - this.startY);
9390 this._preventLock = this._direction === 'horizontal' && horizontal || this._direction === 'vertical' && !horizontal;
9391 return this._preventLock;
9392 }
9393 /**
9394 * Generates the status object, this object passes to the onSwipe callback that contains useful info about swipe gesture
9395 * @param {Event} event
9396 * @return {Object} Status object
9397 */
9398
9399
9400 _createStatusObject(event) {
9401 const status = {};
9402 const tempX = this._lastStatus.distanceX || 0;
9403 const tempY = this._lastStatus.distanceY || 0;
9404 status.timeStamp = Date.now();
9405 status.distanceX = event.pageX - this.startX;
9406 status.distanceY = event.pageY - this.startY;
9407 status.moveX = status.distanceX - tempX;
9408 status.moveY = status.distanceY - tempY;
9409 let dt = status.timeStamp - this._lastStatus.timeStamp || 0;
9410 dt /= 1000;
9411 status.dt = dt; // calculate the velocityX
9412
9413 if (dt === 0 || status.moveX === 0 && (event.pageX <= 2 || event.pageX >= window.screen.width - 2)) {
9414 status.velocityX = this._lastStatus.velocityX;
9415 } else {
9416 status.velocityX = status.moveX / dt;
9417 } // calculate the velocityY
9418
9419
9420 if (dt === 0 || status.moveY === 0 && (event.pageY <= 2 || event.pageY >= window.screen.height - 2)) {
9421 status.velocityY = this._lastStatus.velocityY;
9422 } else {
9423 status.velocityY = status.moveY / dt;
9424 }
9425
9426 status.duration = status.timeStamp - this.startTime;
9427 status.direction = this._getDirection(event.pageX, event.pageY);
9428 return status;
9429 }
9430 /* ------------------------------------------------------------------------------ */
9431 // event listeners
9432
9433 /**
9434 * Touch start event listener function
9435 * @param {Event} event
9436 */
9437
9438
9439 _touchStart(event) {
9440 if (!this.enabled && this.touchStarted && event.target.closest(this.noSwipeSelector, this.element)) {
9441 return;
9442 }
9443
9444 if (event.pointerType && event.pointerType === 'mouse') {
9445 event.preventDefault();
9446 }
9447
9448 const swipeEvent = event.type === 'touchstart' ? event.touches[0] : event;
9449 this.startX = swipeEvent.pageX;
9450 this.startY = swipeEvent.pageY;
9451 this.startTime = Date.now();
9452 document.addEventListener(endEvent, this._touchEnd, false);
9453
9454 if (!iOS) {
9455 document.addEventListener(moveEvent, this._touchMove, {
9456 passive: false
9457 });
9458 }
9459
9460 if (cancelEvent.length) {
9461 document.addEventListener(cancelEvent, this._touchCancel, false);
9462 }
9463
9464 const status = this._createStatusObject(swipeEvent);
9465
9466 status.phase = 'start';
9467 this.onSwipe(status);
9468 this._lastStatus = status;
9469 this.touchStarted = true;
9470 }
9471 /**
9472 * Touch move event listener function
9473 * @param {Event} event
9474 */
9475
9476
9477 _touchMove(event) {
9478 if (!this.touchStarted) {
9479 return;
9480 }
9481
9482 const swipeEvent = event.type === 'touchmove' ? event.touches[0] : event;
9483
9484 const status = this._createStatusObject(swipeEvent);
9485
9486 if (this._preventDefaultEvent(swipeEvent.pageX, swipeEvent.pageY)) {
9487 event.preventDefault();
9488 event.stopPropagation();
9489 event.stopImmediatePropagation();
9490 } else {
9491 return;
9492 }
9493
9494 clearTimeout(this._autoResetTimeout);
9495 this._autoResetTimeout = setTimeout(this._reset, 60, swipeEvent);
9496 status.phase = 'move';
9497 this._lastStatus = status;
9498 this.onSwipe(status);
9499 }
9500 /**
9501 * Touch end event listener
9502 * @param {Event} event
9503 */
9504
9505
9506 _touchEnd(event) {
9507 const status = this._lastStatus;
9508 event.preventDefault();
9509 document.removeEventListener(endEvent, this._touchEnd, false);
9510
9511 if (!iOS) {
9512 document.addEventListener(moveEvent, this._touchMove, {
9513 passive: false
9514 });
9515 }
9516
9517 if (cancelEvent.length) {
9518 document.removeEventListener(cancelEvent, this._touchCancel, false);
9519 }
9520
9521 clearTimeout(this._autoResetTimeout);
9522 this._autoResetTimeout = setTimeout(this._reset, 60);
9523
9524 if (Date.now() - status.timeStamp > 200) {
9525 status.velocityX = 0;
9526 status.velocityY = 0;
9527 }
9528
9529 status.phase = 'end';
9530 this.touchStarted = false;
9531 this.onSwipe(status);
9532 }
9533 /**
9534 * Touch cancel event listener
9535 * @param {Event} event
9536 */
9537
9538
9539 _touchCancel(event) {
9540 this._touchEnd(event);
9541 }
9542 /* ------------------------------------------------------------------------------ */
9543
9544 /**
9545 * Resets the touch swipe properties
9546 * @param {Event} event
9547 */
9548
9549
9550 _reset(event) {
9551 this.reset = false;
9552 this._lastStatus = {};
9553 this.startTime = Date.now();
9554
9555 if (event) {
9556 this.startX = event.pageX;
9557 this.startY = event.pageY;
9558 } else {
9559 this.startX = null;
9560 this.startY = null;
9561 }
9562
9563 this._preventLock = false;
9564 }
9565 /* ------------------------------------------------------------------------------ */
9566
9567 /**
9568 * Enable touch swipe detection
9569 */
9570
9571
9572 enable() {
9573 if (this.enabled) {
9574 return;
9575 }
9576
9577 this.enabled = true;
9578
9579 if (iOS) {
9580 document.addEventListener(moveEvent, this._touchMove, {
9581 passive: false
9582 });
9583 }
9584
9585 this.element.addEventListener(startEvent, this._touchStart, {
9586 passive: false
9587 }); // add touch action style
9588
9589 this.direction = this._direction;
9590 }
9591 /**
9592 * Disable touch swipe detection
9593 */
9594
9595
9596 disable() {
9597 if (!this.enabled) {
9598 return;
9599 }
9600
9601 this.element.style.msTouchAction = '';
9602 this.element.style.touchAction = '';
9603 this.enabled = false;
9604 this.element.removeEventListener(startEvent, this._touchStart, false);
9605 document.removeEventListener(endEvent, this._touchEnd, false);
9606 document.removeEventListener(moveEvent, this._touchMove, false);
9607
9608 if (cancelEvent.length) {
9609 document.removeEventListener(cancelEvent, this._touchCancel, false);
9610 }
9611 }
9612
9613 }
9614
9615 /**
9616 * This handler adds swipe gesture navigation support
9617 */
9618
9619 class SwipeHandler {
9620 /**
9621 * Creates new swipe hander
9622 * @param {MSNavigator} navigator
9623 */
9624 constructor(navigator) {
9625 this.navigator = navigator;
9626 this.swipe = new Swipe(navigator.view.element);
9627 this._updateDirection = this._updateDirection.bind(this);
9628 this.navigator.view.options.observe('dir', this._updateDirection);
9629 this.navigator.view.options.observe('reverse', this._updateDirection);
9630
9631 this._updateDirection();
9632
9633 this._scrollNavigatorAdapter = this._scrollNavigatorAdapter.bind(this);
9634 this.swipe.onSwipe = this._scrollNavigatorAdapter;
9635 }
9636 /**
9637 * Enables swipe events
9638 */
9639
9640
9641 enable() {
9642 this.swipe.enable();
9643 }
9644 /**
9645 * Disables swipe events
9646 */
9647
9648
9649 disable() {
9650 this.swipe.disable();
9651 }
9652 /**
9653 * Updates touch swipe direction
9654 * @private
9655 */
9656
9657
9658 _updateDirection() {
9659 const value = this.navigator.view.options.get('dir');
9660 const reverse = this.navigator.view.options.get('reverse');
9661 this._reverseFactor = reverse ? 1 : -1;
9662 this.direction = value;
9663
9664 if (value === 'h') {
9665 this._movement = 'moveX';
9666 this._velocity = 'velocityX';
9667 this.swipe.direction = 'horizontal';
9668 } else {
9669 this._movement = 'moveY';
9670 this._velocity = 'velocityY';
9671 this.swipe.direction = 'vertical';
9672 }
9673 }
9674 /**
9675 * Swipe adapter function for scroll navigator
9676 * @param {Object} status
9677 */
9678
9679
9680 _scrollNavigatorAdapter(status) {
9681 switch (status.phase) {
9682 case 'start':
9683 this.navigator.hold();
9684 this.navigator.trigger('swipeStart', [this.navigator, this]);
9685 break;
9686
9687 case 'move':
9688 this.navigator.drag(status[this._movement] * this._reverseFactor);
9689 this.navigator.trigger('swipeMove', [this.navigator, this]);
9690 break;
9691
9692 case 'end':
9693 case 'cancel':
9694 default:
9695 if (status[this._velocity]) {
9696 this.navigator.push(status[this._velocity] * this._reverseFactor);
9697 } else {
9698 this.navigator.release();
9699 }
9700
9701 this.navigator.trigger('swipeEnd', [this.navigator, this]);
9702 }
9703 }
9704
9705 }
9706
9707 /**
9708 * Swipe handler adapter addon
9709 */
9710
9711 class SwipeGesture {
9712 constructor(composer) {
9713 this.composer = composer;
9714 this.composer.options.observe(this.composer.options.register({
9715 mouse: true,
9716 swipe: true
9717 }), this.checkOptions.bind(this));
9718 this.composer.options.alias('touch', 'swipe');
9719 this.composer.once('navigatorSetup', (action, navigator) => {
9720 this.swipeHandler = new SwipeHandler(navigator);
9721 this.enable = this.swipeHandler.enable.bind(this.swipeHandler);
9722 this.disable = this.swipeHandler.disable.bind(this.swipeHandler);
9723 this.checkOptions();
9724 });
9725 }
9726 /**
9727 * Checks options and disables or enables the swipe handler
9728 */
9729
9730
9731 checkOptions() {
9732 const options = this.composer.options.get(['mouse', 'swipe']);
9733
9734 if (options.swipe && (isTouch || options.mouse)) {
9735 this.enable();
9736 } else {
9737 this.disable();
9738 }
9739 }
9740
9741 }
9742 Composer.registerAddon('swipeGesture', SwipeGesture);
9743
9744 class AutoHeight {
9745 constructor(composer) {
9746 this.composer = composer;
9747 this.composer.on('init', this._setup, this);
9748 }
9749
9750 update() {
9751 let height = 0;
9752 this.composer.view.indexes.forEach(index => {
9753 height = Math.max(this.composer.view.sections[index].element.offsetHeight, height);
9754 });
9755 this.composer.view.element.style.height = `${height}px`;
9756 }
9757
9758 _setup() {
9759 this.composer.options.observe('autoHeight', this._checkOption.bind(this));
9760
9761 this._checkOption(null, this.composer.options.get('autoHeight'));
9762
9763 let scrollbarWidth = document.body.clientWidth - window.innerWidth;
9764 this.composer.view.element.addEventListener('transitionend', event => {
9765 // we check the page vertical scrollbar width to make sure that changing view height does not make horizontal scroll
9766 const newScrollbarWidthCheck = document.body.clientWidth - window.innerWidth;
9767
9768 if (event.target === this.composer.view.element && event.propertyName === 'height' && scrollbarWidth !== newScrollbarWidthCheck) {
9769 scrollbarWidth = newScrollbarWidthCheck;
9770 this.composer.layoutController.update();
9771 }
9772 });
9773 }
9774
9775 _checkOption(name, active) {
9776 if (name) {
9777 // call layout controller update only if it called by an observer
9778 this.composer.layoutController.update();
9779 }
9780
9781 this.composer.element.classList[active ? 'add' : 'remove'](`${prefix}-auto-height`);
9782 this.composer[active ? 'on' : 'off']('indexesChange, sectionResize, resize', this.update, this);
9783
9784 if (active) {
9785 this.update();
9786 } else {
9787 this.composer.view.element.style.height = '';
9788 }
9789 }
9790
9791 }
9792 Composer.registerAddon('autoHeight', AutoHeight);
9793
9794 /**
9795 * Slide background video controller.
9796 * It search for .ms-bg-video element and sets it as video background
9797 */
9798
9799 class BackgroundVideoController {
9800 constructor(section) {
9801 const videoSource = section.element.querySelector(`.${prefix}-bg-video`);
9802 section.hasBackgroundVideo = !!videoSource;
9803
9804 if (!section.hasBackgroundVideo) {
9805 return;
9806 }
9807
9808 this.videoSource = videoSource;
9809 this.section = section;
9810 this.composer = section.composer; // bg video is loop and muted by default
9811
9812 this.looped = videoSource.getAttribute('data-loop') !== 'false';
9813 videoSource.muted = videoSource.getAttribute('data-muted') !== 'false'; // whether go to next section after video complete or not
9814
9815 this.goNext = videoSource.getAttribute('data-goto-next') === 'true';
9816 this.autoPause = videoSource.getAttribute('data-auto-pause') === 'true';
9817 this.videoContainer = document.createElement('div');
9818 this.videoContainer.classList.add('ms-bg-video-container');
9819 this.videoContainer.appendChild(videoSource);
9820 section.element.appendChild(this.videoContainer); // set object fit
9821 // objectFit(videoSource, 'cover');
9822
9823 responsiveObjectFit(videoSource, 'cover'); // iOS plays inline attribute
9824
9825 videoSource.setAttribute('playsinline', '');
9826 videoSource.setAttribute('webkit-playsinline', ''); // waite for video ready
9827 // section.readyTrigger.hold()
9828
9829 this._videoReady = this._videoReady.bind(this);
9830 videoSource.addEventListener('loadstart', this._videoReady, false);
9831 videoSource.addEventListener('loadedmetadata', this._videoReady, false);
9832
9833 if (videoSource.readyState > 0) {
9834 this._videoReady();
9835 }
9836
9837 section.on('activated, deactivated', this._sectionStateChange, this);
9838 }
9839 /**
9840 * On video player api ready
9841 */
9842
9843
9844 _videoReady() {
9845 if (this.videoReady) {
9846 return;
9847 }
9848
9849 this.videoReady = true;
9850 this._videoStateChange = this._videoStateChange.bind(this);
9851 this.videoSource.addEventListener('play', this._videoStateChange, false);
9852 this.videoSource.addEventListener('pause', this._videoStateChange, false);
9853 this.videoSource.addEventListener('ended', this._videoStateChange, false);
9854
9855 if (window.objectFitPolyfill) {
9856 window.objectFitPolyfill(this.videoSource);
9857 }
9858
9859 if (this.section.active) {
9860 this.videoSource.play();
9861 } else {
9862 this.videoSource.pause();
9863 this.videoSource.currentTime = 0;
9864 } // this.section.readyTrigger.exec();
9865
9866 }
9867 /**
9868 * On video state change including play, pause and ended
9869 * @param {Event} event
9870 */
9871
9872
9873 _videoStateChange(event) {
9874 switch (event.type) {
9875 case 'play':
9876 default:
9877 this.videoState = 'playing';
9878 this.section.trigger('backgroundVideoPlay', [this.section], true);
9879 break;
9880
9881 case 'pause':
9882 this.videoState = 'stopped';
9883 this.section.trigger('backgroundVideoPause', [this.section], true);
9884 break;
9885
9886 case 'ended':
9887 this.videoState = 'ended';
9888 this.section.trigger('backgroundVideoEnded', [this.section], true);
9889
9890 if (this.goNext) {
9891 this.composer.navigator.next();
9892 } else if (this.looped) {
9893 this.videoSource.play();
9894 }
9895
9896 }
9897 }
9898 /**
9899 * On section select state change
9900 * @param {String} action - select or deselect actions
9901 * @param {MSSlide} section
9902 */
9903
9904
9905 _sectionStateChange(action) {
9906 if (!this.videoReady) {
9907 return;
9908 }
9909
9910 switch (action) {
9911 case 'activated':
9912 default:
9913 this.videoSource.play();
9914 break;
9915
9916 case 'deactivated':
9917 this.videoSource.pause();
9918
9919 if (!this.autoPause) {
9920 this.videoSource.currentTime = 0;
9921 }
9922
9923 }
9924 }
9925
9926 }
9927 /* ------------------------------------------------------------------------------ */
9928
9929 /**
9930 * Section background video addon
9931 */
9932
9933
9934 class SectionBackgroundVideo {
9935 constructor(composer) {
9936 this.composer = composer;
9937 this.activeSlides = [];
9938 this.composer.on('sectionBeforeMount', this._checkSection, this);
9939 }
9940 /**
9941 * After setting up each section, it checks the section element content for section video source
9942 * @param {String} action
9943 * @param {MSSlide} section
9944 */
9945
9946
9947 _checkSection(action, section) {
9948 if (!section.firstMount) {
9949 return;
9950 }
9951
9952 section.backgroundVideoController = new BackgroundVideoController(section);
9953
9954 if (section.hasBackgroundVideo) {
9955 return;
9956 }
9957
9958 this.activeSlides.push(section);
9959 }
9960
9961 }
9962
9963 Composer.registerAddon('sectionBackgroundVideo', SectionBackgroundVideo);
9964
9965 /**
9966 * This addon ready loading and section loading elements from markup and adds them in proper location
9967 */
9968
9969 class Loading {
9970 constructor(composer) {
9971 this.composer = composer;
9972 this.composer.options.register({
9973 sectionLoading: 'auto' // Specifies the type of sections loading, `auto` reads the markup for `ms-section-loading` then 'ms-loading-container'. `off` does not add any loading for sections
9974
9975 });
9976 this.loadingElement = composer.element.querySelector(`.${prefix}-loading-container`); // create loading if it's not added in the markup
9977
9978 if (!this.loadingElement) {
9979 this.loadingElement = document.createElement('div');
9980 this.loadingElement.classList.add(`${prefix}-loading-container`);
9981 const loadingSymbol = document.createElement('div');
9982 loadingSymbol.classList.add(`${prefix}-loading`);
9983 this.loadingElement.appendChild(loadingSymbol);
9984 this.composer.element.appendChild(this.loadingElement);
9985 }
9986
9987 this.composer.on('init', this._afterInit, this);
9988 }
9989 /**
9990 * Add loading to sections after composer init
9991 */
9992
9993
9994 _afterInit() {
9995 if (this.composer.options.get('sectionLoading') !== 'off') {
9996 this.sectionLoadingTemplate = this.composer.element.querySelector(`.${prefix}-section-loading`) || this.loadingElement.cloneNode(true);
9997 this.sectionLoadingTemplate.remove();
9998 this.composer.view.sections.forEach(this._setupLoadingOnSection, this);
9999 }
10000 }
10001 /**
10002 * Adds loading element to section
10003 * @param {Section} section
10004 */
10005
10006
10007 _setupLoadingOnSection(section) {
10008 if (section.isReady) {
10009 return;
10010 }
10011
10012 const loadingElement = this.sectionLoadingTemplate.cloneNode(true);
10013 section.element.appendChild(loadingElement);
10014 }
10015
10016 }
10017
10018 Composer.registerAddon('loading', Loading);
10019
10020 /**
10021 * This addon disables click events over content while user starts swiping
10022 */
10023
10024 class DisableClicks {
10025 constructor(composer) {
10026 this.composer = composer;
10027 this.actions = composer.actions;
10028 this.composer.on('init', this._init, this);
10029 }
10030 /**
10031 * Initialize the addon and check for swipe enabled on composer to add required actions
10032 * @param {[type]} action [description]
10033 * @return {[type]} [description]
10034 */
10035
10036
10037 _init() {
10038 this._checkClick = this._checkClick.bind(this);
10039 this.composer.view.element.addEventListener('click', this._checkClick, false);
10040 this.composer.on('swipeStart', this._swipeInteraction, this);
10041 this.composer.on('swipeMove', this._swipeInteraction, this);
10042 this.composer.on('swipeEnd', this._swipeInteraction, this);
10043 }
10044 /**
10045 * Check swipe status and disable links if required
10046 */
10047
10048
10049 _swipeInteraction(action) {
10050 clearTimeout(this._to);
10051
10052 if (action === 'swipeStart') {
10053 this._clickDisabled = true;
10054 this._hadMove = false;
10055 } else if (action === 'swipeMove') {
10056 this._hadMove = true;
10057 } else if (this._hadMove) {
10058 this._hadMove = false;
10059 this._to = setTimeout(() => {
10060 this._clickDisabled = false;
10061 }, 5);
10062 } else {
10063 this._clickDisabled = false;
10064 }
10065 }
10066 /**
10067 * Disable link on click
10068 */
10069
10070
10071 _checkClick(e) {
10072 if (this._clickDisabled) {
10073 e.preventDefault();
10074 e.stopPropagation();
10075 }
10076 }
10077
10078 }
10079
10080 Composer.registerAddon('disableClicks', DisableClicks);
10081
10082 /**
10083 * Smart loading addon
10084 * It adds the `preload` option to the composer which can be used to control the procedure of loading assets.
10085 * The `preload` options has three types of functionality, first load assets of sections in sequence, second, load all assets then show the composer
10086 * and load only nearby sections
10087 */
10088
10089 class SmartLoader {
10090 constructor(composer) {
10091 this.composer = composer;
10092 this.composer.options.register({
10093 preload: 0 // Specifies number of sections which will be loaded by composer. 0 value means the composer loads sections in sequence.
10094
10095 });
10096 this.composer.on('init', this._start, this, 100);
10097 this.composer.on('sectionBeforeMount', (action, section) => section.loadTrigger.hold(), this, 100);
10098 this.composer.on('layersSurfaceBeforeSetup', this._checkSurfaceLayers, this);
10099 }
10100 /**
10101 * Starts loading content
10102 */
10103
10104
10105 _start() {
10106 const preloadMode = this.composer.options.get('preload');
10107
10108 if (preloadMode === 0) {
10109 this._loadSectionsInSequence();
10110 } else if (preloadMode === 'all') {
10111 this._waitForAllSections();
10112 } else if (typeof preloadMode === 'number') {
10113 this._loadNearby = preloadMode;
10114 } // add preload mode class name
10115
10116
10117 this.composer.element.classList.add(`${prefix}-preload-${preloadMode}`);
10118 this.composer.on('targetIndexChange', this._checkCurrentSection, this);
10119
10120 this._checkCurrentSection();
10121 }
10122 /**
10123 * Prevents composer to load before all surface layers (overlay layers) get loaded
10124 * @param {String} action
10125 * @param {LayersSurface} surface
10126 */
10127
10128
10129 _checkSurfaceLayers(action, surface) {
10130 const preloadMode = this.composer.options.get('preload');
10131
10132 if (preloadMode === 'all') {
10133 surface.loadTrigger.hold();
10134 this.composer.readyTrigger.hold();
10135 surface.on('ready', () => this.composer.readyTrigger.exec(), this);
10136 surface.loadTrigger.exec();
10137 }
10138 }
10139 /**
10140 * Starts loading current section and its nearby sections
10141 */
10142
10143
10144 _checkCurrentSection() {
10145 this.composer.navigator.targetSectionIndexes.forEach(index => {
10146 this.composer.view.sections[index].loadTrigger.exec();
10147
10148 if (this._loadNearby) {
10149 this._loadNearbySections(index, this._loadNearby);
10150 }
10151 });
10152 }
10153 /**
10154 * Starts loading nearby sections of given index
10155 * @param {Number} index
10156 * @param {Number} num The number of sections that are considered as nearby
10157 */
10158
10159
10160 _loadNearbySections(index, num) {
10161 let targetIndex;
10162 const {
10163 sections
10164 } = this.composer.view;
10165 const {
10166 loop
10167 } = this.composer.view;
10168 const len = sections.length;
10169
10170 for (let i = 1; i !== num + 1; i += 1) {
10171 targetIndex = index + i;
10172
10173 if (targetIndex >= len) {
10174 if (loop) {
10175 targetIndex %= len;
10176 sections[targetIndex].loadTrigger.exec();
10177 }
10178 } else {
10179 sections[targetIndex].loadTrigger.exec();
10180 }
10181
10182 targetIndex = index - i;
10183
10184 if (targetIndex < 0) {
10185 if (loop) {
10186 targetIndex += len;
10187 sections[targetIndex].loadTrigger.exec();
10188 }
10189 } else {
10190 sections[targetIndex].loadTrigger.exec();
10191 }
10192 }
10193 }
10194 /**
10195 * Starts loading sections in sequence
10196 */
10197
10198
10199 _loadSectionsInSequence(index) {
10200 if (index === this.composer.view.sections.length) {
10201 return;
10202 }
10203
10204 if (index === undefined) {
10205 index = 0;
10206 }
10207
10208 const section = this.composer.view.sections[index];
10209
10210 if (!section.isReady) {
10211 section.on('ready', () => {
10212 this._loadSectionsInSequence(index + 1);
10213 }, this);
10214 section.loadTrigger.exec();
10215 } else {
10216 this._loadSectionsInSequence(index + 1);
10217 }
10218 }
10219 /**
10220 * Prevents content to appear before all assets get loaded
10221 */
10222
10223
10224 _waitForAllSections() {
10225 this.composer.readyTrigger.charge(this.composer.view.sections.length);
10226 this.composer.view.sections.forEach(section => {
10227 if (section.isReady) {
10228 this.composer.readyTrigger.exec();
10229 } else {
10230 section.on('ready', () => this.composer.readyTrigger.exec(), this);
10231 section.loadTrigger.exec();
10232 }
10233 });
10234 }
10235
10236 }
10237
10238 Composer.registerAddon('smartLoader', SmartLoader);
10239
10240 /**
10241 * Section video controller class, it creates video element on section and controls its playback.
10242 */
10243
10244 class SectionVideoController {
10245 constructor(section) {
10246 let videoSource = section.element.querySelector('.ms-section-video, a[data-type="video"]');
10247
10248 if (!videoSource) {
10249 this.noSource = true;
10250 return;
10251 }
10252
10253 this.section = section;
10254 this.composer = section.composer;
10255 section.videoController = this;
10256 this.autoplay = videoSource.getAttribute('data-autoplay') === 'true';
10257 this.goNext = videoSource.getAttribute('data-goto-next') === 'true';
10258
10259 if (videoSource.tagName === 'A') {
10260 const src = videoSource.getAttribute('href');
10261 videoSource.remove();
10262 videoSource = src;
10263 } else if (videoSource.tagName === 'VIDEO' && !videoSource.hasAttribute('data-player-type')) {
10264 if (videoSource.hasAttribute('data-object-fit')) {
10265 videoSource.style.objectFit = videoSource.getAttribute('data-object-fit');
10266 }
10267
10268 if (videoSource.hasAttribute('data-object-position')) {
10269 videoSource.style.objectPosition = videoSource.getAttribute('data-object-position');
10270 }
10271 }
10272
10273 this._videoElementReady = this._videoElementReady.bind(this);
10274 this.videoElement = new VideoElement(videoSource);
10275 this.videoElement.setup(this._videoElementReady); // make media element responsive
10276
10277 if (this.videoElement.type === 'mejs') {
10278 this.videoElement.player.api.options.stretching = 'responsive';
10279 } // remove the class name from source
10280
10281
10282 this.videoElement.source.classList.remove('ms-section-video'); // add the class name to the player container
10283
10284 this.videoElement.element.classList.add('ms-section-video'); // if the video is added by an A element;
10285
10286 if (typeof videoSource === 'string') {
10287 section.element.appendChild(this.videoElement.element);
10288 } // add play and close button
10289
10290
10291 this.playBtn = document.createElement('div');
10292 this.playBtn.classList.add('ms-section-video-btn');
10293 this.playBtn.addEventListener('click', this.playVideo.bind(this), false);
10294 section.element.appendChild(this.playBtn);
10295 this.closeBtn = document.createElement('div');
10296 this.closeBtn.classList.add('ms-section-video-close-btn');
10297 this.closeBtn.addEventListener('click', this.closeVideo.bind(this), false);
10298 section.element.appendChild(this.closeBtn);
10299 section.on('activated, deactivated', this._sectionStateChange, this);
10300 }
10301 /**
10302 * Start playing section video
10303 */
10304
10305
10306 playVideo() {
10307 if (this.videoElement.ready) {
10308 this.videoElement.play();
10309 }
10310
10311 this.section.element.classList.add(`${prefix}-video-open`);
10312 this.section.trigger('videoOpen', [this.section, this], true);
10313 }
10314 /**
10315 * Stop the section video and close it
10316 */
10317
10318
10319 closeVideo() {
10320 if (this.videoElement.ready) {
10321 this.videoElement.stop();
10322 }
10323
10324 this.section.element.classList.remove(`${prefix}-video-open`);
10325 this.section.trigger('videoClose', [this.section, this], true);
10326 }
10327 /**
10328 * On video player api ready
10329 */
10330
10331
10332 _videoElementReady() {
10333 if (this.section.active && this.autoplay) {
10334 this.playVideo();
10335 }
10336
10337 this._onVideoPlay = this._onVideoPlay.bind(this);
10338 this._onVideoPause = this._onVideoPause.bind(this);
10339 this._onVideoEnded = this._onVideoEnded.bind(this);
10340 this.videoElement.on('play', this._onVideoPlay);
10341 this.videoElement.on('pause', this._onVideoPause);
10342 this.videoElement.on('ended', this._onVideoEnded);
10343 }
10344 /**
10345 * Video play event listener
10346 */
10347
10348
10349 _onVideoPlay() {
10350 this._videoState = 'playing';
10351 this.section.trigger('videoPlay', [this.section, this], true);
10352 }
10353 /**
10354 * Video pause event listener
10355 */
10356
10357
10358 _onVideoPause() {
10359 this._videoState = 'stopped';
10360 this.section.trigger('videoPause', [this.section, this], true);
10361 }
10362 /**
10363 * Video ended event listener
10364 */
10365
10366
10367 _onVideoEnded() {
10368 this._videoState = 'ended';
10369 this.section.trigger('videoEnded', [this.section, this], true);
10370
10371 if (this.goNext && this.composer.next) {
10372 this.composer.next();
10373 }
10374 }
10375 /**
10376 * On section select state change
10377 * @param {Section} section
10378 */
10379
10380
10381 _sectionStateChange(action) {
10382 switch (action) {
10383 case 'select':
10384 default:
10385 if (this.autoplay) {
10386 this.playVideo();
10387 }
10388
10389 break;
10390
10391 case 'deselect':
10392 this.closeVideo();
10393 }
10394 }
10395
10396 }
10397 /* ------------------------------------------------------------------------------ */
10398
10399
10400 class SectionVideo {
10401 constructor(composer) {
10402 this.composer = composer;
10403 this.actions = composer.actions;
10404 this.options = composer.options;
10405 this.activeSections = [];
10406 this.composer.on('init', () => {
10407 this.composer.view.sections.forEach(this._checkSection, this);
10408 });
10409 }
10410 /**
10411 * After setting up each section, it checks the section element content for section video source
10412 * @param {String} action
10413 * @param {Section} section
10414 */
10415
10416
10417 _checkSection(section) {
10418 const sectionVideoController = new SectionVideoController(section);
10419
10420 if (sectionVideoController.noSource) {
10421 return;
10422 }
10423
10424 this.activeSections.push(section);
10425 }
10426
10427 }
10428
10429 Composer.registerAddon('sectionVideo', SectionVideo);
10430
10431 const classNameMatchTest = new RegExp(`${prefix}-hide-on-(tablet|desktop|phone)`, 'g');
10432 /**
10433 * This addon ready loading and section loading elements from markup and adds them in proper location
10434 */
10435
10436 class HideOn {
10437 constructor(composer) {
10438 var _this$composerElement, _this$hideBreakpoints, _this$hideBreakpoints2;
10439
10440 this.composer = composer;
10441 this.composerElement = this.composer.element;
10442 this.hideBreakpoints = (_this$composerElement = this.composerElement.getAttribute('class').match(classNameMatchTest)) === null || _this$composerElement === void 0 ? void 0 : _this$composerElement.map(className => className.split('-').slice(-1)[0]);
10443
10444 if ((_this$hideBreakpoints = this.hideBreakpoints) !== null && _this$hideBreakpoints !== void 0 && _this$hideBreakpoints.includes(findBreakpoint().name || 'desktop')) {
10445 this._contentIsOnHold = true;
10446 this.composer.isHidden = true;
10447 composer.initTrigger.hold();
10448 }
10449
10450 if ((_this$hideBreakpoints2 = this.hideBreakpoints) !== null && _this$hideBreakpoints2 !== void 0 && _this$hideBreakpoints2.length) {
10451 responsiveHelper.on('breakpointChange', this.update, this);
10452 }
10453 }
10454
10455 update(action, breakpoint) {
10456 var _this$hideBreakpoints3;
10457
10458 if ((_this$hideBreakpoints3 = this.hideBreakpoints) !== null && _this$hideBreakpoints3 !== void 0 && _this$hideBreakpoints3.includes(breakpoint)) {
10459 this.composer.isHidden = true;
10460 this.composer.trigger('visibilityChange', [true]);
10461 } else {
10462 this.composer.isHidden = false;
10463
10464 if (this._contentIsOnHold) {
10465 this._contentIsOnHold = false;
10466 this.composer.initTrigger.exec();
10467 }
10468
10469 this.composer.trigger('visibilityChange', [false]);
10470 }
10471 }
10472
10473 }
10474
10475 Composer.registerAddon('hideOn', HideOn);
10476
10477 /**
10478 * This addon ready loading and section loading elements from markup and adds them in proper location
10479 */
10480
10481 class KeyboardNav {
10482 constructor(composer) {
10483 this.composer = composer;
10484 this.composerElement = this.composer.element;
10485 this.composer.options.register({
10486 keyboard: false
10487 });
10488 this.composer.on('init', this.setup, this);
10489 }
10490
10491 setup() {
10492 const keyboardOptions = this.composer.options.get('keyboard');
10493 const defaultOptions = {
10494 checkLoop: false,
10495 activeOnHover: false
10496 };
10497
10498 if (keyboardOptions) {
10499 this.activeOptions = _objectSpread2(_objectSpread2({}, defaultOptions), typeof keyboardOptions === 'object' ? keyboardOptions : undefined);
10500 this._onKeydown = this._onKeydown.bind(this);
10501
10502 if (this.activeOptions.activeOnHover) {
10503 this.composerElement.tabIndex = 0;
10504 this._mouseInteraction = this._mouseInteraction.bind(this);
10505 this.composerElement.addEventListener('mouseenter', this._mouseInteraction, false);
10506 this.composerElement.addEventListener('mouseleave', this._mouseInteraction, false);
10507 } else {
10508 document.addEventListener('keydown', this._onKeydown);
10509 }
10510 }
10511 }
10512
10513 _mouseInteraction(event) {
10514 switch (event.type) {
10515 case 'mouseenter':
10516 this.composerElement.focus();
10517 this.composerElement.addEventListener('keydown', this._onKeydown, false);
10518 break;
10519
10520 case 'mouseleave':
10521 this.composerElement.blur();
10522 this.composerElement.removeEventListener('keydown', this._onKeydown, false);
10523 break;
10524 }
10525 }
10526
10527 _onKeydown(event) {
10528 const {
10529 which
10530 } = event;
10531 const {
10532 checkLoop
10533 } = this.activeOptions;
10534
10535 if (which === 37 || which === 40) {
10536 this.composer.navigator.previous({
10537 checkLoop
10538 });
10539 } else if (which === 38 || which === 39) {
10540 this.composer.navigator.next({
10541 checkLoop
10542 });
10543 }
10544 }
10545
10546 }
10547
10548 Composer.registerAddon('keyboardNav', KeyboardNav);
10549
10550 // more info: https://github.com/facebook/fixed-data-table/blob/master/src/vendor_upstream/dom/normalizeWheel.js
10551
10552 const LINE_HEIGHT = 40; // delay between effective wheel events
10553
10554 const WHEEL_THRESHOLD = 300; // minimum valid delta amount to slide
10555
10556 const SLIDE_MIN_DELTA = 20;
10557 /**
10558 * This addon ready loading and section loading elements from markup and adds them in proper location
10559 */
10560
10561 class MouseWheelNav {
10562 constructor(composer) {
10563 this.composer = composer;
10564 this.composerElement = this.composer.element;
10565 this.composer.options.register({
10566 mouseWheel: false
10567 });
10568 this.composer.on('init', this.setup, this);
10569 }
10570
10571 setup() {
10572 const mouseWheelOptions = this.composer.options.get('mouseWheel');
10573 const defaultOptions = {
10574 activeOnAppear: true,
10575 preventDefault: 'auto',
10576 friction: 0.09
10577 };
10578 this._slideByWheel = this._slideByWheel.bind(this);
10579 this._scrollByWheel = this._scrollByWheel.bind(this);
10580 this._wheelDeltaBuffer = 0;
10581 this._lastWheelTime = 0;
10582
10583 if (mouseWheelOptions) {
10584 this.options = _objectSpread2(_objectSpread2({}, defaultOptions), typeof mouseWheelOptions === 'object' ? mouseWheelOptions : undefined);
10585 const slickType = this.composer.options.get('navigator.slickType');
10586 this.view = this.composer.view;
10587
10588 if (slickType === 'scroll') {
10589 this._readViewPosition = true;
10590 this.loop = this.composer.options.get('viewOptions.loop');
10591 this.composer.navigator.on('externalEffect', () => {
10592 this._readViewPosition = true;
10593 });
10594 this.composerElement.addEventListener('wheel', this._scrollByWheel, false);
10595 } else {
10596 this.composerElement.addEventListener('wheel', this._slideByWheel, false);
10597 }
10598 }
10599 }
10600 /**
10601 * Checks the slider element bounding values to ensure it's located in the browser view
10602 * @param {Number} direction - Direction of scrolling
10603 * @return {Boolean} The true value means the slider is not in the view and page needs to scroll
10604 */
10605
10606
10607 _checkContentLocation(direction) {
10608 const bounding = this.composerElement.getBoundingClientRect();
10609
10610 if (direction < 0 && bounding.top < 0) {
10611 return true;
10612 }
10613
10614 if (direction > 0 && bounding.bottom > window.innerHeight) {
10615 return true;
10616 }
10617
10618 return false;
10619 }
10620 /**
10621 * Navigates between slides by mouse wheel
10622 * @param {WheelEvent} event
10623 */
10624
10625
10626 _slideByWheel(event) {
10627 let delta = event.deltaY;
10628
10629 if (this.options.activeOnAppear && this._checkContentLocation(delta)) {
10630 return;
10631 }
10632
10633 if (this.options.preventDefault === 'auto' && (this.composer.navigator.currentIndex === this.composer.navigator.count - 1 && delta > 1 || this.composer.navigator.currentIndex === 0 && delta < 1)) {
10634 return;
10635 }
10636
10637 if (this.options.preventDefault) {
10638 event.preventDefault();
10639 }
10640
10641 if (event.timeStamp - this._lastWheelTime < WHEEL_THRESHOLD) {
10642 return;
10643 } // delta in LINE units
10644
10645
10646 if (event.deltaMode === 1) {
10647 delta *= LINE_HEIGHT;
10648 }
10649
10650 if (Math.abs(delta) < SLIDE_MIN_DELTA) {
10651 return;
10652 }
10653
10654 if (delta < 0) {
10655 this.composer.navigator.previous();
10656 } else {
10657 this.composer.navigator.next();
10658 }
10659
10660 this._lastWheelTime = event.timeStamp;
10661 }
10662 /**
10663 * Scroll between slides by wheel
10664 * @param {WheelEvent} event
10665 */
10666
10667
10668 _scrollByWheel(event) {
10669 let delta = event.deltaY;
10670
10671 if (this.options.activeOnAppear && this._checkContentLocation(delta)) {
10672 return;
10673 } // delta in LINE units
10674
10675
10676 if (event.deltaMode === 1) {
10677 delta *= LINE_HEIGHT;
10678 } // reached to the last slide ?
10679
10680
10681 if (this.targetScrollPosition >= this.view.length - this.view.size && delta > 1) {
10682 if (this.options.preventDefault === 'auto' || !this.options.preventDefault) {
10683 return;
10684 }
10685 } // is at first slide ?
10686
10687
10688 if (this.targetScrollPosition <= 0 && delta < 1) {
10689 if (this.options.preventDefault === 'auto' || !this.options.preventDefault) {
10690 return;
10691 }
10692 } // auto or true
10693
10694
10695 if (this.options.preventDefault) {
10696 event.preventDefault();
10697 }
10698
10699 if (this._readViewPosition) {
10700 this._readViewPosition = false;
10701 this.targetScrollPosition = this.view.position;
10702 }
10703
10704 this.targetScrollPosition += delta;
10705
10706 if (!this.loop || this.options.preventDefault === 'auto') {
10707 this.targetScrollPosition = Math.max(Math.min(this.view.length - this.view.size, this.targetScrollPosition), 0);
10708 }
10709
10710 this.composer.navigator.goToPosition(this.targetScrollPosition, {
10711 useFriction: this.options.friction !== 0,
10712 friction: this.options.friction
10713 });
10714 this._lastWheelTime = event.timeStamp;
10715 }
10716
10717 }
10718
10719 Composer.registerAddon('mouseWheelNav', MouseWheelNav);
10720
10721 /**
10722 * This addon adds grab and grabbing cursors on the composer element
10723 */
10724
10725 class GrabCursor {
10726 constructor(composer) {
10727 this.composer = composer;
10728 this.composer.options.register({
10729 useGrabCursor: true
10730 });
10731 this.composer.on('init', this._afterInit, this);
10732 }
10733 /**
10734 * Add cursor class names to the composer element
10735 */
10736
10737
10738 _afterInit() {
10739 if (this.composer.options.get('useGrabCursor')) {
10740 const {
10741 element
10742 } = this.composer;
10743 element.classList.add(`${prefix}-cursor-grab`);
10744 this.composer.on('swipeStart', () => element.classList.add(`${prefix}-cursor-grabbing`));
10745 this.composer.on('swipeEnd', () => element.classList.remove(`${prefix}-cursor-grabbing`));
10746 }
10747 }
10748
10749 }
10750
10751 Composer.registerAddon('grabCursor', GrabCursor);
10752
10753 const list = [];
10754 let isStopped = true;
10755
10756 const tick = () => {
10757 if (isStopped) return;
10758 list.forEach(item => item());
10759 requestAnimationFrame(tick);
10760 };
10761
10762 const start = () => {
10763 if (!isStopped) return;
10764 isStopped = false;
10765 tick();
10766 };
10767 const stop = () => {
10768 isStopped = true;
10769 };
10770 const add = listener => {
10771 list.push(listener);
10772
10773 if (list.length === 1) {
10774 start();
10775 }
10776
10777 return list.length;
10778 };
10779 const remove = listener => {
10780 list.splice(list.indexOf(listener), 1);
10781
10782 if (list.length === 0) {
10783 stop();
10784 }
10785 };
10786
10787 class Timer {
10788 constructor(delay, autoStart) {
10789 this.delay = delay;
10790 this.currentCount = 0;
10791 this.paused = false;
10792 this.onTimer = null;
10793 if (autoStart) this.start();
10794 this.update = this.update.bind(this);
10795 }
10796
10797 start() {
10798 this.paused = false;
10799 this.lastTime = Date.now();
10800 add(this.update);
10801 }
10802
10803 stop() {
10804 this.paused = true;
10805 remove(this.update);
10806 }
10807
10808 reset() {
10809 this.currentCount = 0;
10810 this.paused = true;
10811 this.lastTime = Date.now();
10812 }
10813
10814 update() {
10815 if (this.paused || Date.now() - this.lastTime < this.delay) return;
10816 this.currentCount += 1;
10817 this.lastTime = Date.now();
10818 if (this.onTimer) this.onTimer(this.getTime());
10819 }
10820
10821 getTime() {
10822 return this.delay * this.currentCount;
10823 }
10824
10825 }
10826
10827 const defaultOptions = {
10828 autostart: false,
10829 duration: 3,
10830 // Global value for section duration. Used when section duration is not set for section(s).
10831 autoStartAfterVideo: true,
10832 // Don't start timer until background video starts
10833 pauseOnHover: true,
10834 // Pause autoPlay timer when mouse is over composer
10835 resetTimerOnBlur: true,
10836 // Resets Timer after mouse leaves the composer
10837 pauseAtEnd: 'auto',
10838 // Pauses composer timer after showing last section.
10839 navigatorParams: {
10840 animate: true,
10841 duration: 1.5,
10842 easing: 'easeOutExpo'
10843 }
10844 };
10845
10846 class Slideshow {
10847 constructor(composer) {
10848 this.composer = composer;
10849 this.composer.options.register({
10850 slideshow: false
10851 });
10852 this.timer = new Timer(100);
10853 this.timer.onTimer = this._onTimer.bind(this);
10854 this.mouseEntered = false;
10855 this.composer.on('init', this.setup, this, 100);
10856 }
10857 /**
10858 * Starts loading content
10859 */
10860
10861
10862 setup() {
10863 const slideshowOptions = this.composer.options.get('slideshow');
10864 this.options = _objectSpread2(_objectSpread2({}, defaultOptions), typeof slideshowOptions === 'object' ? slideshowOptions : {
10865 autostart: !!slideshowOptions
10866 }); // add required methods to the API
10867
10868 this._registerAutoPlayMethods(); // Set default duration and then check for section duration
10869
10870
10871 this._readSectionSlideshowDataAttrs();
10872
10873 this.loop = this.composer.view.options.get('loop');
10874
10875 if (this.options.autostart) {
10876 this._start();
10877
10878 this._waitForVideo();
10879 } else {
10880 // hard pause
10881 // it prevents the auto play starts with internal actions
10882 this.composer.slideshow.pause();
10883 }
10884
10885 this.composer.on('changeStart', this._reset, this);
10886 this.composer.on('swipeStart', this._reset, this);
10887 this.composer.on('changeEnd', this._onChangeEnd, this); // Check if mouse interactions enabled
10888
10889 if (this.options.pauseOnHover) {
10890 this._mouseInteraction = this._mouseInteraction.bind(this);
10891 this.composer.element.addEventListener('mouseover', this._mouseInteraction, false);
10892 this.composer.element.addEventListener('mouseenter', this._mouseInteraction, false);
10893 this.composer.element.addEventListener('mouseleave', this._mouseInteraction, false);
10894 } // Pause timer when section video has opened
10895
10896
10897 this.composer.on('sectionVideoOpen', this._pause, this);
10898 this.composer.on('sectionVideoClose', this._start, this);
10899 }
10900 /**
10901 * Registers methods in the composer API
10902 */
10903
10904
10905 _registerAutoPlayMethods() {
10906 this.composer.slideshow = {
10907 currentTime: () => this.durationProgress,
10908 // Start timer
10909 resume: () => {
10910 this._hardPause = false;
10911 this.composer.paused = false;
10912
10913 this._start();
10914 },
10915 // Pause timer
10916 pause: () => {
10917 this._hardPause = true;
10918 this.composer.paused = true;
10919
10920 this._pause();
10921 },
10922 // Reset timer
10923 reset: () => this._reset,
10924 // isPaused flag
10925 isPaused: () => this._hardPause
10926 };
10927 }
10928 /**
10929 * Updates section duration (duration) for current section
10930 */
10931
10932
10933 _readSectionSlideshowDataAttrs() {
10934 this.duration = this.options.duration;
10935 const {
10936 slideshowDuration,
10937 slideshowPause
10938 } = this.composer.view.currentSection.element.dataset;
10939
10940 if (slideshowDuration) {
10941 this.duration = slideshowDuration;
10942 }
10943
10944 if (slideshowPause) {
10945 this.composer.slideshow.pause();
10946 }
10947
10948 this.duration *= 1000;
10949 }
10950 /**
10951 * Starts section timer
10952 */
10953
10954
10955 _start() {
10956 if (this._hardPause) {
10957 return;
10958 }
10959
10960 this._isPaused = false;
10961 this.timer.start();
10962 this.composer.trigger('slideshowStart');
10963 }
10964 /**
10965 * Pauses section timer
10966 */
10967
10968
10969 _pause() {
10970 this._isPaused = true;
10971 this.timer.stop();
10972 this.composer.trigger('slideshowPaused');
10973 }
10974 /**
10975 * Resets section timer and progress
10976 */
10977
10978
10979 _reset() {
10980 this.timer.reset();
10981 this.durationProgress = 0;
10982 this.composer.trigger('slideshowTimerUpdate', [this.durationProgress]);
10983 this.composer.trigger('slideshowTimerReset');
10984 }
10985 /**
10986 * Runs after changing section
10987 * Get section duration, check for mouse interaction, add section video actions
10988 */
10989
10990
10991 _onChangeEnd() {
10992 if ((this.options.pauseAtEnd === 'auto' && !this.loop || this.options.pauseAtEnd === true) && this.composer.navigator.targetIndex === this.composer.navigator.count - 1) {
10993 this.composer.slideshow.pause();
10994 return;
10995 }
10996
10997 this._readSectionSlideshowDataAttrs();
10998
10999 if (!this.mouseEntered) {
11000 this._start();
11001
11002 this._waitForVideo();
11003 }
11004 }
11005 /**
11006 * Runs continuously
11007 * Change section after section duration
11008 * Calculates section progress
11009 */
11010
11011
11012 _onTimer() {
11013 if (this.timer.getTime() >= this.duration) {
11014 this.composer.navigator.next(this.options.navigatorParams);
11015 }
11016
11017 this.durationProgress = this.timer.getTime() / this.duration * 100;
11018 this.composer.trigger('slideshowTimerUpdate', [this.durationProgress]);
11019 }
11020 /**
11021 * Checks mouse interaction to pause/resume/reset timer
11022 */
11023
11024
11025 _mouseInteraction(event) {
11026 switch (event.type) {
11027 case 'mouseenter':
11028 case 'mouseover':
11029 this.mouseEntered = true;
11030
11031 this._pause();
11032
11033 break;
11034
11035 case 'mouseleave':
11036 this.mouseEntered = false;
11037
11038 if (this.options.resetTimerOnBlur) {
11039 this._reset();
11040 }
11041
11042 this._start();
11043 }
11044 }
11045 /**
11046 * Checks for existing video and related option to start timer after starting video
11047 */
11048
11049
11050 _waitForVideo() {
11051 const waitForVideo = this.options.autoStartAfterVideo;
11052 const {
11053 view: {
11054 currentSection: {
11055 backgroundVideoController,
11056 hasBackgroundVideo
11057 }
11058 }
11059 } = this.composer;
11060
11061 if (waitForVideo && hasBackgroundVideo && backgroundVideoController.videoState !== 'playing') {
11062 this._reset();
11063
11064 this.composer.on('sectionBackgroundVideoPlay', this._start, this);
11065 this.composer.on('sectionBackgroundVideoPlay', console.log, this);
11066 }
11067 }
11068
11069 }
11070
11071 Composer.registerAddon('slideshow', Slideshow);
11072
11073 function createCommonjsModule(fn) {
11074 var module = { exports: {} };
11075 return fn(module, module.exports), module.exports;
11076 }
11077
11078 /* smoothscroll v0.4.4 - 2019 - Dustan Kasten, Jeremias Menichelli - MIT License */
11079 var smoothscroll = createCommonjsModule(function (module, exports) {
11080 (function () {
11081
11082 function polyfill() {
11083 // aliases
11084 var w = window;
11085 var d = document; // return if scroll behavior is supported and polyfill is not forced
11086
11087 if ('scrollBehavior' in d.documentElement.style && w.__forceSmoothScrollPolyfill__ !== true) {
11088 return;
11089 } // globals
11090
11091
11092 var Element = w.HTMLElement || w.Element;
11093 var SCROLL_TIME = 468; // object gathering original scroll methods
11094
11095 var original = {
11096 scroll: w.scroll || w.scrollTo,
11097 scrollBy: w.scrollBy,
11098 elementScroll: Element.prototype.scroll || scrollElement,
11099 scrollIntoView: Element.prototype.scrollIntoView
11100 }; // define timing method
11101
11102 var now = w.performance && w.performance.now ? w.performance.now.bind(w.performance) : Date.now;
11103 /**
11104 * indicates if a the current browser is made by Microsoft
11105 * @method isMicrosoftBrowser
11106 * @param {String} userAgent
11107 * @returns {Boolean}
11108 */
11109
11110 function isMicrosoftBrowser(userAgent) {
11111 var userAgentPatterns = ['MSIE ', 'Trident/', 'Edge/'];
11112 return new RegExp(userAgentPatterns.join('|')).test(userAgent);
11113 }
11114 /*
11115 * IE has rounding bug rounding down clientHeight and clientWidth and
11116 * rounding up scrollHeight and scrollWidth causing false positives
11117 * on hasScrollableSpace
11118 */
11119
11120
11121 var ROUNDING_TOLERANCE = isMicrosoftBrowser(w.navigator.userAgent) ? 1 : 0;
11122 /**
11123 * changes scroll position inside an element
11124 * @method scrollElement
11125 * @param {Number} x
11126 * @param {Number} y
11127 * @returns {undefined}
11128 */
11129
11130 function scrollElement(x, y) {
11131 this.scrollLeft = x;
11132 this.scrollTop = y;
11133 }
11134 /**
11135 * returns result of applying ease math function to a number
11136 * @method ease
11137 * @param {Number} k
11138 * @returns {Number}
11139 */
11140
11141
11142 function ease(k) {
11143 return 0.5 * (1 - Math.cos(Math.PI * k));
11144 }
11145 /**
11146 * indicates if a smooth behavior should be applied
11147 * @method shouldBailOut
11148 * @param {Number|Object} firstArg
11149 * @returns {Boolean}
11150 */
11151
11152
11153 function shouldBailOut(firstArg) {
11154 if (firstArg === null || typeof firstArg !== 'object' || firstArg.behavior === undefined || firstArg.behavior === 'auto' || firstArg.behavior === 'instant') {
11155 // first argument is not an object/null
11156 // or behavior is auto, instant or undefined
11157 return true;
11158 }
11159
11160 if (typeof firstArg === 'object' && firstArg.behavior === 'smooth') {
11161 // first argument is an object and behavior is smooth
11162 return false;
11163 } // throw error when behavior is not supported
11164
11165
11166 throw new TypeError('behavior member of ScrollOptions ' + firstArg.behavior + ' is not a valid value for enumeration ScrollBehavior.');
11167 }
11168 /**
11169 * indicates if an element has scrollable space in the provided axis
11170 * @method hasScrollableSpace
11171 * @param {Node} el
11172 * @param {String} axis
11173 * @returns {Boolean}
11174 */
11175
11176
11177 function hasScrollableSpace(el, axis) {
11178 if (axis === 'Y') {
11179 return el.clientHeight + ROUNDING_TOLERANCE < el.scrollHeight;
11180 }
11181
11182 if (axis === 'X') {
11183 return el.clientWidth + ROUNDING_TOLERANCE < el.scrollWidth;
11184 }
11185 }
11186 /**
11187 * indicates if an element has a scrollable overflow property in the axis
11188 * @method canOverflow
11189 * @param {Node} el
11190 * @param {String} axis
11191 * @returns {Boolean}
11192 */
11193
11194
11195 function canOverflow(el, axis) {
11196 var overflowValue = w.getComputedStyle(el, null)['overflow' + axis];
11197 return overflowValue === 'auto' || overflowValue === 'scroll';
11198 }
11199 /**
11200 * indicates if an element can be scrolled in either axis
11201 * @method isScrollable
11202 * @param {Node} el
11203 * @param {String} axis
11204 * @returns {Boolean}
11205 */
11206
11207
11208 function isScrollable(el) {
11209 var isScrollableY = hasScrollableSpace(el, 'Y') && canOverflow(el, 'Y');
11210 var isScrollableX = hasScrollableSpace(el, 'X') && canOverflow(el, 'X');
11211 return isScrollableY || isScrollableX;
11212 }
11213 /**
11214 * finds scrollable parent of an element
11215 * @method findScrollableParent
11216 * @param {Node} el
11217 * @returns {Node} el
11218 */
11219
11220
11221 function findScrollableParent(el) {
11222 while (el !== d.body && isScrollable(el) === false) {
11223 el = el.parentNode || el.host;
11224 }
11225
11226 return el;
11227 }
11228 /**
11229 * self invoked function that, given a context, steps through scrolling
11230 * @method step
11231 * @param {Object} context
11232 * @returns {undefined}
11233 */
11234
11235
11236 function step(context) {
11237 var time = now();
11238 var value;
11239 var currentX;
11240 var currentY;
11241 var elapsed = (time - context.startTime) / SCROLL_TIME; // avoid elapsed times higher than one
11242
11243 elapsed = elapsed > 1 ? 1 : elapsed; // apply easing to elapsed time
11244
11245 value = ease(elapsed);
11246 currentX = context.startX + (context.x - context.startX) * value;
11247 currentY = context.startY + (context.y - context.startY) * value;
11248 context.method.call(context.scrollable, currentX, currentY); // scroll more if we have not reached our destination
11249
11250 if (currentX !== context.x || currentY !== context.y) {
11251 w.requestAnimationFrame(step.bind(w, context));
11252 }
11253 }
11254 /**
11255 * scrolls window or element with a smooth behavior
11256 * @method smoothScroll
11257 * @param {Object|Node} el
11258 * @param {Number} x
11259 * @param {Number} y
11260 * @returns {undefined}
11261 */
11262
11263
11264 function smoothScroll(el, x, y) {
11265 var scrollable;
11266 var startX;
11267 var startY;
11268 var method;
11269 var startTime = now(); // define scroll context
11270
11271 if (el === d.body) {
11272 scrollable = w;
11273 startX = w.scrollX || w.pageXOffset;
11274 startY = w.scrollY || w.pageYOffset;
11275 method = original.scroll;
11276 } else {
11277 scrollable = el;
11278 startX = el.scrollLeft;
11279 startY = el.scrollTop;
11280 method = scrollElement;
11281 } // scroll looping over a frame
11282
11283
11284 step({
11285 scrollable: scrollable,
11286 method: method,
11287 startTime: startTime,
11288 startX: startX,
11289 startY: startY,
11290 x: x,
11291 y: y
11292 });
11293 } // ORIGINAL METHODS OVERRIDES
11294 // w.scroll and w.scrollTo
11295
11296
11297 w.scroll = w.scrollTo = function () {
11298 // avoid action when no arguments are passed
11299 if (arguments[0] === undefined) {
11300 return;
11301 } // avoid smooth behavior if not required
11302
11303
11304 if (shouldBailOut(arguments[0]) === true) {
11305 original.scroll.call(w, arguments[0].left !== undefined ? arguments[0].left : typeof arguments[0] !== 'object' ? arguments[0] : w.scrollX || w.pageXOffset, // use top prop, second argument if present or fallback to scrollY
11306 arguments[0].top !== undefined ? arguments[0].top : arguments[1] !== undefined ? arguments[1] : w.scrollY || w.pageYOffset);
11307 return;
11308 } // LET THE SMOOTHNESS BEGIN!
11309
11310
11311 smoothScroll.call(w, d.body, arguments[0].left !== undefined ? ~~arguments[0].left : w.scrollX || w.pageXOffset, arguments[0].top !== undefined ? ~~arguments[0].top : w.scrollY || w.pageYOffset);
11312 }; // w.scrollBy
11313
11314
11315 w.scrollBy = function () {
11316 // avoid action when no arguments are passed
11317 if (arguments[0] === undefined) {
11318 return;
11319 } // avoid smooth behavior if not required
11320
11321
11322 if (shouldBailOut(arguments[0])) {
11323 original.scrollBy.call(w, arguments[0].left !== undefined ? arguments[0].left : typeof arguments[0] !== 'object' ? arguments[0] : 0, arguments[0].top !== undefined ? arguments[0].top : arguments[1] !== undefined ? arguments[1] : 0);
11324 return;
11325 } // LET THE SMOOTHNESS BEGIN!
11326
11327
11328 smoothScroll.call(w, d.body, ~~arguments[0].left + (w.scrollX || w.pageXOffset), ~~arguments[0].top + (w.scrollY || w.pageYOffset));
11329 }; // Element.prototype.scroll and Element.prototype.scrollTo
11330
11331
11332 Element.prototype.scroll = Element.prototype.scrollTo = function () {
11333 // avoid action when no arguments are passed
11334 if (arguments[0] === undefined) {
11335 return;
11336 } // avoid smooth behavior if not required
11337
11338
11339 if (shouldBailOut(arguments[0]) === true) {
11340 // if one number is passed, throw error to match Firefox implementation
11341 if (typeof arguments[0] === 'number' && arguments[1] === undefined) {
11342 throw new SyntaxError('Value could not be converted');
11343 }
11344
11345 original.elementScroll.call(this, // use left prop, first number argument or fallback to scrollLeft
11346 arguments[0].left !== undefined ? ~~arguments[0].left : typeof arguments[0] !== 'object' ? ~~arguments[0] : this.scrollLeft, // use top prop, second argument or fallback to scrollTop
11347 arguments[0].top !== undefined ? ~~arguments[0].top : arguments[1] !== undefined ? ~~arguments[1] : this.scrollTop);
11348 return;
11349 }
11350
11351 var left = arguments[0].left;
11352 var top = arguments[0].top; // LET THE SMOOTHNESS BEGIN!
11353
11354 smoothScroll.call(this, this, typeof left === 'undefined' ? this.scrollLeft : ~~left, typeof top === 'undefined' ? this.scrollTop : ~~top);
11355 }; // Element.prototype.scrollBy
11356
11357
11358 Element.prototype.scrollBy = function () {
11359 // avoid action when no arguments are passed
11360 if (arguments[0] === undefined) {
11361 return;
11362 } // avoid smooth behavior if not required
11363
11364
11365 if (shouldBailOut(arguments[0]) === true) {
11366 original.elementScroll.call(this, arguments[0].left !== undefined ? ~~arguments[0].left + this.scrollLeft : ~~arguments[0] + this.scrollLeft, arguments[0].top !== undefined ? ~~arguments[0].top + this.scrollTop : ~~arguments[1] + this.scrollTop);
11367 return;
11368 }
11369
11370 this.scroll({
11371 left: ~~arguments[0].left + this.scrollLeft,
11372 top: ~~arguments[0].top + this.scrollTop,
11373 behavior: arguments[0].behavior
11374 });
11375 }; // Element.prototype.scrollIntoView
11376
11377
11378 Element.prototype.scrollIntoView = function () {
11379 // avoid smooth behavior if not required
11380 if (shouldBailOut(arguments[0]) === true) {
11381 original.scrollIntoView.call(this, arguments[0] === undefined ? true : arguments[0]);
11382 return;
11383 } // LET THE SMOOTHNESS BEGIN!
11384
11385
11386 var scrollableParent = findScrollableParent(this);
11387 var parentRects = scrollableParent.getBoundingClientRect();
11388 var clientRects = this.getBoundingClientRect();
11389
11390 if (scrollableParent !== d.body) {
11391 // reveal element inside parent
11392 smoothScroll.call(this, scrollableParent, scrollableParent.scrollLeft + clientRects.left - parentRects.left, scrollableParent.scrollTop + clientRects.top - parentRects.top); // reveal parent in viewport unless is fixed
11393
11394 if (w.getComputedStyle(scrollableParent).position !== 'fixed') {
11395 w.scrollBy({
11396 left: parentRects.left,
11397 top: parentRects.top,
11398 behavior: 'smooth'
11399 });
11400 }
11401 } else {
11402 // reveal element in viewport
11403 w.scrollBy({
11404 left: clientRects.left,
11405 top: clientRects.top,
11406 behavior: 'smooth'
11407 });
11408 }
11409 };
11410 }
11411
11412 {
11413 // commonjs
11414 module.exports = {
11415 polyfill: polyfill
11416 };
11417 }
11418 })();
11419 });
11420
11421 smoothscroll.polyfill();
11422 /* ------------------------------------------------------------------------------ */
11423 // actions list
11424
11425 const actions = composer => ({
11426 openURL(url, target) {
11427 window.open(url, target);
11428 },
11429
11430 slideshow(action) {
11431 if (['resume', 'pause', 'reset'].includes(action)) {
11432 var _composer$slideshow$a, _composer$slideshow;
11433
11434 (_composer$slideshow$a = (_composer$slideshow = composer.slideshow)[action]) === null || _composer$slideshow$a === void 0 ? void 0 : _composer$slideshow$a.call(_composer$slideshow);
11435 }
11436 },
11437
11438 gotoSlide(to) {
11439 if (['next', 'previous'].includes(to)) {
11440 var _composer$navigator$t, _composer$navigator;
11441
11442 (_composer$navigator$t = (_composer$navigator = composer.navigator)[to]) === null || _composer$navigator$t === void 0 ? void 0 : _composer$navigator$t.call(_composer$navigator);
11443 } else if (!Number.isNaN(to)) {
11444 composer.navigator.gotoIndex(to);
11445 } else {
11446 const targetSectionIndex = composer.view.sections.findIndex(section => section.id === to);
11447
11448 if (targetSectionIndex >= 0) {
11449 composer.navigator.gotoIndex(targetSectionIndex);
11450 }
11451 }
11452 },
11453
11454 scrollTo(to) {
11455 if (to === 'below') {
11456 window.scrollTo({
11457 top: window.scrollY + composer.element.getBoundingClientRect().bottom,
11458 behavior: 'smooth'
11459 });
11460 } else {
11461 var _document$querySelect;
11462
11463 (_document$querySelect = document.querySelector(to)) === null || _document$querySelect === void 0 ? void 0 : _document$querySelect.scrollIntoView({
11464 behavior: 'smooth'
11465 });
11466 }
11467 },
11468
11469 backgroundVideo(action) {
11470 var _composer$view$curren, _composer$view$curren2;
11471
11472 const bgVideo = (_composer$view$curren = composer.view.currentSection) === null || _composer$view$curren === void 0 ? void 0 : (_composer$view$curren2 = _composer$view$curren.backgroundVideoController) === null || _composer$view$curren2 === void 0 ? void 0 : _composer$view$curren2.videoSource;
11473
11474 if (!bgVideo) {
11475 return;
11476 }
11477
11478 try {
11479 switch (action) {
11480 case 'mute':
11481 bgVideo.muted = true;
11482 break;
11483
11484 case 'unmute':
11485 bgVideo.muted = false;
11486 break;
11487
11488 case 'toggleSound':
11489 bgVideo.muted = !bgVideo.muted;
11490 break;
11491
11492 case 'toggle':
11493 if (bgVideo.paused) {
11494 bgVideo.play();
11495 } else {
11496 bgVideo.pause();
11497 }
11498
11499 break;
11500
11501 case 'stop':
11502 bgVideo.pause();
11503 bgVideo.currentTime = 0;
11504 break;
11505
11506 case 'play':
11507 bgVideo.play();
11508 break;
11509
11510 case 'pause':
11511 bgVideo.pause();
11512 break;
11513
11514 case 'restart':
11515 bgVideo.play();
11516 bgVideo.currentTime = 0;
11517 break;
11518
11519 default:
11520 } // eslint-disable-next-line no-empty
11521
11522 } catch (e) {}
11523 },
11524
11525 elements(target, action) {
11526 var _targetLayer$inOutAni, _targetLayer$inOutAni2;
11527
11528 const targetLayer = composer.layersById[target];
11529
11530 if (!targetLayer) {
11531 return;
11532 }
11533
11534 switch (action) {
11535 case 'show':
11536 targetLayer === null || targetLayer === void 0 ? void 0 : targetLayer.animateInOut('in', true);
11537 break;
11538
11539 case 'hide':
11540 targetLayer === null || targetLayer === void 0 ? void 0 : targetLayer.animateInOut('out', true);
11541 break;
11542
11543 case 'toggle':
11544 if ((targetLayer === null || targetLayer === void 0 ? void 0 : (_targetLayer$inOutAni = targetLayer.inOutAnimation) === null || _targetLayer$inOutAni === void 0 ? void 0 : _targetLayer$inOutAni.activePhase) === 'in') {
11545 targetLayer === null || targetLayer === void 0 ? void 0 : targetLayer.animateInOut('out', true);
11546 }
11547
11548 if ((targetLayer === null || targetLayer === void 0 ? void 0 : (_targetLayer$inOutAni2 = targetLayer.inOutAnimation) === null || _targetLayer$inOutAni2 === void 0 ? void 0 : _targetLayer$inOutAni2.activePhase) === 'out') {
11549 targetLayer === null || targetLayer === void 0 ? void 0 : targetLayer.animateInOut('in', true);
11550 }
11551
11552 break;
11553 }
11554 }
11555
11556 });
11557
11558 const domEvents = ['click', 'mouseenter', 'mouseleave'];
11559
11560 const assignActions = (actionFunctions, htmlElement, from) => {
11561 const actionsData = htmlElement.dataset.actions;
11562
11563 if (!actionsData) {
11564 return;
11565 }
11566
11567 let actionsList = [];
11568
11569 try {
11570 actionsList = JSON.parse(actionsData.replace(/'/g, '"')); // eslint-disable-next-line no-empty
11571 } catch (e) {}
11572
11573 actionsList.forEach(([actionName, actionEvent, actionDelay, ...actionParams]) => {
11574 if (domEvents.includes(actionEvent)) {
11575 htmlElement.addEventListener(actionEvent, () => callAction(actionFunctions[actionName], actionParams, actionDelay));
11576 } else {
11577 from.on(actionEvent, () => callAction(actionFunctions[actionName], actionParams, actionDelay));
11578 }
11579 });
11580 };
11581
11582 const callAction = (actionCallback, actionParams, delay = 0) => {
11583 if (delay) {
11584 setTimeout(() => {
11585 actionCallback === null || actionCallback === void 0 ? void 0 : actionCallback.apply(null, actionParams);
11586 }, delay);
11587 } else {
11588 actionCallback === null || actionCallback === void 0 ? void 0 : actionCallback.apply(null, actionParams);
11589 }
11590 };
11591 /**
11592 * This addon ready loading and section loading elements from markup and adds them in proper location
11593 */
11594
11595
11596 class Actions {
11597 constructor(composer) {
11598 this.composer = composer;
11599 this.composer.on('init', this._afterInit, this);
11600 this.composer.on('layerCreate', this._setLayerActions, this);
11601 this.composer.actions = actions(composer);
11602 }
11603 /**
11604 * Add loading to sections after composer init
11605 */
11606
11607
11608 _afterInit() {
11609 this.composer.view.sections.forEach(section => assignActions(this.composer.actions, section.element, section));
11610 }
11611
11612 _setLayerActions(action, layer) {
11613 assignActions(this.composer.actions, layer.element, layer);
11614 }
11615 /**
11616 * Adds loading element to section
11617 * @param {Section} section
11618 */
11619
11620
11621 _setupLoadingOnSection(section) {
11622 if (section.isReady) {
11623 return;
11624 }
11625
11626 const loadingElement = this.sectionLoadingTemplate.cloneNode(true);
11627 section.element.appendChild(loadingElement);
11628 }
11629
11630 }
11631
11632 Composer.registerAddon('actions', Actions);
11633
11634 /**
11635 * This addon adds the proper revert styles class name to the composer
11636 */
11637
11638 class RevertStyles {
11639 constructor(composer) {
11640 this.composer = composer;
11641 this.composer.options.register({
11642 useRevertStyles: true
11643 });
11644 this.composer.on('init', this._afterInit, this);
11645 }
11646 /**
11647 * Add cursor class names to the composer element
11648 */
11649
11650
11651 _afterInit() {
11652 if (this.composer.options.get('useRevertStyles')) {
11653 var _window, _window$CSS;
11654
11655 if ((_window = window) !== null && _window !== void 0 && (_window$CSS = _window.CSS) !== null && _window$CSS !== void 0 && _window$CSS.supports('all', 'revert')) {
11656 this.composer.element.classList.add(`${prefix}-modern-revert-styles`);
11657 } else {
11658 this.composer.element.classList.remove(`${prefix}-modern-revert-styles`);
11659 this.composer.element.classList.add(`${prefix}-legacy-revert-styles`);
11660 }
11661 }
11662 }
11663
11664 }
11665
11666 Composer.registerAddon('revertStyles', RevertStyles);
11667
11668 /**
11669 * Master Slider main class
11670 * Each instance of this class creates new slider
11671 */
11672
11673 class MasterSlider extends Composer {
11674 static setup(selector, options) {
11675 const targetElement = document.querySelector(selector);
11676
11677 if (!targetElement) {
11678 return undefined;
11679 }
11680
11681 const slider = new MasterSlider();
11682 slider.setup(targetElement, options);
11683 MasterSlider.instances.push(slider);
11684 return slider;
11685 }
11686
11687 setup(element, options = {}) {
11688 super.setup(element, options); // update composer default values and register new options
11689
11690 this.options.register({});
11691 }
11692
11693 }
11694
11695 _defineProperty(MasterSlider, "instances", []);
11696
11697 return MasterSlider;
11698
11699 }));
11700 //# sourceMappingURL=masterslider.js.map
11701