diff --git a/.gitignore b/.gitignore index a7bc3c4..d2c913c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,8 @@ node_modules/ -dist/ -.package-lock.json +.idea +.vscode .DS_Store + +package-lock.json +yarn.lock diff --git a/.npmignore b/.npmignore index f733c4b..82c49b9 100644 --- a/.npmignore +++ b/.npmignore @@ -1 +1,7 @@ config/ +node_modules/ + +.editorconfig +.vscode + +.DS_Store diff --git a/README.md b/README.md index ba29c8a..fbc85ac 100644 --- a/README.md +++ b/README.md @@ -5,10 +5,87 @@ A Glide.js extension for Vue ### Installation + ``` npm install --save vue-glide ``` +### Usage + +```js +import VueGlide from "@glidejs/vue-glide/src"; + +Vue.use(VueGlide); + +new Vue({ + // ... +}); + +// OR +import Glide from "@glidejs/vue-glide/src/components/Glide"; +import GlideTrack from "@glidejs/vue-glide/src/components/Track"; +import GlideSlide from "@glidejs/vue-glide/src/components/Slide"; + +new Vue({ + components: { + Glide, + GlideTrack, + GlideSlide, + }, + + // ... +}); +``` + +Inside a component + +```html + + + + + + + + + + + + + + + + + + + + + + + + + Previous + + + + Next + + + + +``` + +Import the glidejs stylesheet (scss) + +```scss +@import "~@glidejs/glide/src/assets/sass/glide.core"; +@import "~@glidejs/glide/src/assets/sass/glide.theme"; // (optional) +``` + ## Credits - [Jędrzej Chałubek][https://github.com/jedrzejchalubek] - creator diff --git a/dist/vue-glide.esm.js b/dist/vue-glide.esm.js new file mode 100644 index 0000000..473a5d4 --- /dev/null +++ b/dist/vue-glide.esm.js @@ -0,0 +1,3698 @@ +/*! + * VueGlide.js v3.1.0 + * (c) 2017-2019 jedrzejchalubek (https://jedrzejchalubek.com) + * Released under the MIT License. + */ + +var _typeof2 = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; + +/*! + * Glide.js v3.2.6 + * (c) 2013-2019 Jędrzej Chałubek (http://jedrzejchalubek.com/) + * Released under the MIT License. + */ + +var defaults = { + /** + * Type of the movement. + * + * Available types: + * `slider` - Rewinds slider to the start/end when it reaches the first or last slide. + * `carousel` - Changes slides without starting over when it reaches the first or last slide. + * + * @type {String} + */ + type: 'slider', + + /** + * Start at specific slide number defined with zero-based index. + * + * @type {Number} + */ + startAt: 0, + + /** + * A number of slides visible on the single viewport. + * + * @type {Number} + */ + perView: 1, + + /** + * Focus currently active slide at a specified position in the track. + * + * Available inputs: + * `center` - Current slide will be always focused at the center of a track. + * `0,1,2,3...` - Current slide will be focused on the specified zero-based index. + * + * @type {String|Number} + */ + focusAt: 0, + + /** + * A size of the gap added between slides. + * + * @type {Number} + */ + gap: 10, + + /** + * Change slides after a specified interval. Use `false` for turning off autoplay. + * + * @type {Number|Boolean} + */ + autoplay: false, + + /** + * Stop autoplay on mouseover event. + * + * @type {Boolean} + */ + hoverpause: true, + + /** + * Allow for changing slides with left and right keyboard arrows. + * + * @type {Boolean} + */ + keyboard: true, + + /** + * Stop running `perView` number of slides from the end. Use this + * option if you don't want to have an empty space after + * a slider. Works only with `slider` type and a + * non-centered `focusAt` setting. + * + * @type {Boolean} + */ + bound: false, + + /** + * Minimal swipe distance needed to change the slide. Use `false` for turning off a swiping. + * + * @type {Number|Boolean} + */ + swipeThreshold: 80, + + /** + * Minimal mouse drag distance needed to change the slide. Use `false` for turning off a dragging. + * + * @type {Number|Boolean} + */ + dragThreshold: 120, + + /** + * A maximum number of slides to which movement will be made on swiping or dragging. Use `false` for unlimited. + * + * @type {Number|Boolean} + */ + perTouch: false, + + /** + * Moving distance ratio of the slides on a swiping and dragging. + * + * @type {Number} + */ + touchRatio: 0.5, + + /** + * Angle required to activate slides moving on swiping or dragging. + * + * @type {Number} + */ + touchAngle: 45, + + /** + * Duration of the animation in milliseconds. + * + * @type {Number} + */ + animationDuration: 400, + + /** + * Allows looping the `slider` type. Slider will rewind to the first/last slide when it's at the start/end. + * + * @type {Boolean} + */ + rewind: true, + + /** + * Duration of the rewinding animation of the `slider` type in milliseconds. + * + * @type {Number} + */ + rewindDuration: 800, + + /** + * Easing function for the animation. + * + * @type {String} + */ + animationTimingFunc: 'cubic-bezier(.165, .840, .440, 1)', + + /** + * Throttle costly events at most once per every wait milliseconds. + * + * @type {Number} + */ + throttle: 10, + + /** + * Moving direction mode. + * + * Available inputs: + * - 'ltr' - left to right movement, + * - 'rtl' - right to left movement. + * + * @type {String} + */ + direction: 'ltr', + + /** + * The distance value of the next and previous viewports which + * have to peek in the current view. Accepts number and + * pixels as a string. Left and right peeking can be + * set up separately with a directions object. + * + * For example: + * `100` - Peek 100px on the both sides. + * { before: 100, after: 50 }` - Peek 100px on the left side and 50px on the right side. + * + * @type {Number|String|Object} + */ + peek: 0, + + /** + * Collection of options applied at specified media breakpoints. + * For example: display two slides per view under 800px. + * `{ + * '800px': { + * perView: 2 + * } + * }` + */ + breakpoints: {}, + + /** + * Collection of internally used HTML classes. + * + * @todo Refactor `slider` and `carousel` properties to single `type: { slider: '', carousel: '' }` object + * @type {Object} + */ + classes: { + direction: { + ltr: 'glide--ltr', + rtl: 'glide--rtl' + }, + slider: 'glide--slider', + carousel: 'glide--carousel', + swipeable: 'glide--swipeable', + dragging: 'glide--dragging', + cloneSlide: 'glide__slide--clone', + activeNav: 'glide__bullet--active', + activeSlide: 'glide__slide--active', + disabledArrow: 'glide__arrow--disabled' + } +}; + +/** + * Outputs warning message to the bowser console. + * + * @param {String} msg + * @return {Void} + */ +function warn(msg) { + console.error("[Glide warn]: " + msg); +} + +var _typeof = typeof Symbol === "function" && _typeof2(Symbol.iterator) === "symbol" ? function (obj) { + return typeof obj === 'undefined' ? 'undefined' : _typeof2(obj); +} : function (obj) { + return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj === 'undefined' ? 'undefined' : _typeof2(obj); +}; + +var classCallCheck = function classCallCheck(instance, Constructor) { + if (!(instance instanceof Constructor)) { + throw new TypeError("Cannot call a class as a function"); + } +}; + +var createClass = function () { + function defineProperties(target, props) { + for (var i = 0; i < props.length; i++) { + var descriptor = props[i]; + descriptor.enumerable = descriptor.enumerable || false; + descriptor.configurable = true; + if ("value" in descriptor) descriptor.writable = true; + Object.defineProperty(target, descriptor.key, descriptor); + } + } + + return function (Constructor, protoProps, staticProps) { + if (protoProps) defineProperties(Constructor.prototype, protoProps); + if (staticProps) defineProperties(Constructor, staticProps); + return Constructor; + }; +}(); + +var _extends = Object.assign || function (target) { + for (var i = 1; i < arguments.length; i++) { + var source = arguments[i]; + + for (var key in source) { + if (Object.prototype.hasOwnProperty.call(source, key)) { + target[key] = source[key]; + } + } + } + + return target; +}; + +var get = function get(object, property, receiver) { + if (object === null) object = Function.prototype; + var desc = Object.getOwnPropertyDescriptor(object, property); + + if (desc === undefined) { + var parent = Object.getPrototypeOf(object); + + if (parent === null) { + return undefined; + } else { + return get(parent, property, receiver); + } + } else if ("value" in desc) { + return desc.value; + } else { + var getter = desc.get; + + if (getter === undefined) { + return undefined; + } + + return getter.call(receiver); + } +}; + +var inherits = function inherits(subClass, superClass) { + if (typeof superClass !== "function" && superClass !== null) { + throw new TypeError("Super expression must either be null or a function, not " + (typeof superClass === 'undefined' ? 'undefined' : _typeof2(superClass))); + } + + subClass.prototype = Object.create(superClass && superClass.prototype, { + constructor: { + value: subClass, + enumerable: false, + writable: true, + configurable: true + } + }); + if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; +}; + +var possibleConstructorReturn = function possibleConstructorReturn(self, call) { + if (!self) { + throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); + } + + return call && ((typeof call === 'undefined' ? 'undefined' : _typeof2(call)) === "object" || typeof call === "function") ? call : self; +}; + +/** + * Converts value entered as number + * or string to integer value. + * + * @param {String} value + * @returns {Number} + */ +function toInt(value) { + return parseInt(value); +} + +/** + * Converts value entered as number + * or string to flat value. + * + * @param {String} value + * @returns {Number} + */ +function toFloat(value) { + return parseFloat(value); +} + +/** + * Indicates whether the specified value is a string. + * + * @param {*} value + * @return {Boolean} + */ +function isString(value) { + return typeof value === 'string'; +} + +/** + * Indicates whether the specified value is an object. + * + * @param {*} value + * @return {Boolean} + * + * @see https://github.com/jashkenas/underscore + */ +function isObject(value) { + var type = typeof value === 'undefined' ? 'undefined' : _typeof(value); + + return type === 'function' || type === 'object' && !!value; // eslint-disable-line no-mixed-operators +} + +/** + * Indicates whether the specified value is a number. + * + * @param {*} value + * @return {Boolean} + */ +function isNumber(value) { + return typeof value === 'number'; +} + +/** + * Indicates whether the specified value is a function. + * + * @param {*} value + * @return {Boolean} + */ +function isFunction(value) { + return typeof value === 'function'; +} + +/** + * Indicates whether the specified value is undefined. + * + * @param {*} value + * @return {Boolean} + */ +function isUndefined(value) { + return typeof value === 'undefined'; +} + +/** + * Indicates whether the specified value is an array. + * + * @param {*} value + * @return {Boolean} + */ +function isArray(value) { + return value.constructor === Array; +} + +/** + * Creates and initializes specified collection of extensions. + * Each extension receives access to instance of glide and rest of components. + * + * @param {Object} glide + * @param {Object} extensions + * + * @returns {Object} + */ +function mount(glide, extensions, events) { + var components = {}; + + for (var name in extensions) { + if (isFunction(extensions[name])) { + components[name] = extensions[name](glide, components, events); + } else { + warn('Extension must be a function'); + } + } + + for (var _name in components) { + if (isFunction(components[_name].mount)) { + components[_name].mount(); + } + } + + return components; +} + +/** + * Defines getter and setter property on the specified object. + * + * @param {Object} obj Object where property has to be defined. + * @param {String} prop Name of the defined property. + * @param {Object} definition Get and set definitions for the property. + * @return {Void} + */ +function define(obj, prop, definition) { + Object.defineProperty(obj, prop, definition); +} + +/** + * Sorts aphabetically object keys. + * + * @param {Object} obj + * @return {Object} + */ +function sortKeys(obj) { + return Object.keys(obj).sort().reduce(function (r, k) { + r[k] = obj[k]; + + return r[k], r; + }, {}); +} + +/** + * Merges passed settings object with default options. + * + * @param {Object} defaults + * @param {Object} settings + * @return {Object} + */ +function mergeOptions(defaults, settings) { + var options = _extends({}, defaults, settings); + + // `Object.assign` do not deeply merge objects, so we + // have to do it manually for every nested object + // in options. Although it does not look smart, + // it's smaller and faster than some fancy + // merging deep-merge algorithm script. + if (settings.hasOwnProperty('classes')) { + options.classes = _extends({}, defaults.classes, settings.classes); + + if (settings.classes.hasOwnProperty('direction')) { + options.classes.direction = _extends({}, defaults.classes.direction, settings.classes.direction); + } + } + + if (settings.hasOwnProperty('breakpoints')) { + options.breakpoints = _extends({}, defaults.breakpoints, settings.breakpoints); + } + + return options; +} + +var EventsBus = function () { + /** + * Construct a EventBus instance. + * + * @param {Object} events + */ + function EventsBus() { + var events = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; + classCallCheck(this, EventsBus); + + this.events = events; + this.hop = events.hasOwnProperty; + } + + /** + * Adds listener to the specifed event. + * + * @param {String|Array} event + * @param {Function} handler + */ + + createClass(EventsBus, [{ + key: 'on', + value: function on(event, handler) { + if (isArray(event)) { + for (var i = 0; i < event.length; i++) { + this.on(event[i], handler); + } + } + + // Create the event's object if not yet created + if (!this.hop.call(this.events, event)) { + this.events[event] = []; + } + + // Add the handler to queue + var index = this.events[event].push(handler) - 1; + + // Provide handle back for removal of event + return { + remove: function remove() { + delete this.events[event][index]; + } + }; + } + + /** + * Runs registered handlers for specified event. + * + * @param {String|Array} event + * @param {Object=} context + */ + + }, { + key: 'emit', + value: function emit(event, context) { + if (isArray(event)) { + for (var i = 0; i < event.length; i++) { + this.emit(event[i], context); + } + } + + // If the event doesn't exist, or there's no handlers in queue, just leave + if (!this.hop.call(this.events, event)) { + return; + } + + // Cycle through events queue, fire! + this.events[event].forEach(function (item) { + item(context || {}); + }); + } + }]); + return EventsBus; +}(); + +var Glide$1 = function () { + /** + * Construct glide. + * + * @param {String} selector + * @param {Object} options + */ + function Glide(selector) { + var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; + classCallCheck(this, Glide); + + this._c = {}; + this._t = []; + this._e = new EventsBus(); + + this.disabled = false; + this.selector = selector; + this.settings = mergeOptions(defaults, options); + this.index = this.settings.startAt; + } + + /** + * Initializes glide. + * + * @param {Object} extensions Collection of extensions to initialize. + * @return {Glide} + */ + + createClass(Glide, [{ + key: 'mount', + value: function mount$$1() { + var extensions = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; + + this._e.emit('mount.before'); + + if (isObject(extensions)) { + this._c = mount(this, extensions, this._e); + } else { + warn('You need to provide a object on `mount()`'); + } + + this._e.emit('mount.after'); + + return this; + } + + /** + * Collects an instance `translate` transformers. + * + * @param {Array} transformers Collection of transformers. + * @return {Void} + */ + + }, { + key: 'mutate', + value: function mutate() { + var transformers = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : []; + + if (isArray(transformers)) { + this._t = transformers; + } else { + warn('You need to provide a array on `mutate()`'); + } + + return this; + } + + /** + * Updates glide with specified settings. + * + * @param {Object} settings + * @return {Glide} + */ + + }, { + key: 'update', + value: function update() { + var settings = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; + + this.settings = mergeOptions(this.settings, settings); + + if (settings.hasOwnProperty('startAt')) { + this.index = settings.startAt; + } + + this._e.emit('update'); + + return this; + } + + /** + * Change slide with specified pattern. A pattern must be in the special format: + * `>` - Move one forward + * `<` - Move one backward + * `={i}` - Go to {i} zero-based slide (eq. '=1', will go to second slide) + * `>>` - Rewinds to end (last slide) + * `<<` - Rewinds to start (first slide) + * + * @param {String} pattern + * @return {Glide} + */ + + }, { + key: 'go', + value: function go(pattern) { + this._c.Run.make(pattern); + + return this; + } + + /** + * Move track by specified distance. + * + * @param {String} distance + * @return {Glide} + */ + + }, { + key: 'move', + value: function move(distance) { + this._c.Transition.disable(); + this._c.Move.make(distance); + + return this; + } + + /** + * Destroy instance and revert all changes done by this._c. + * + * @return {Glide} + */ + + }, { + key: 'destroy', + value: function destroy() { + this._e.emit('destroy'); + + return this; + } + + /** + * Start instance autoplaying. + * + * @param {Boolean|Number} interval Run autoplaying with passed interval regardless of `autoplay` settings + * @return {Glide} + */ + + }, { + key: 'play', + value: function play() { + var interval = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false; + + if (interval) { + this.settings.autoplay = interval; + } + + this._e.emit('play'); + + return this; + } + + /** + * Stop instance autoplaying. + * + * @return {Glide} + */ + + }, { + key: 'pause', + value: function pause() { + this._e.emit('pause'); + + return this; + } + + /** + * Sets glide into a idle status. + * + * @return {Glide} + */ + + }, { + key: 'disable', + value: function disable() { + this.disabled = true; + + return this; + } + + /** + * Sets glide into a active status. + * + * @return {Glide} + */ + + }, { + key: 'enable', + value: function enable() { + this.disabled = false; + + return this; + } + + /** + * Adds cuutom event listener with handler. + * + * @param {String|Array} event + * @param {Function} handler + * @return {Glide} + */ + + }, { + key: 'on', + value: function on(event, handler) { + this._e.on(event, handler); + + return this; + } + + /** + * Checks if glide is a precised type. + * + * @param {String} name + * @return {Boolean} + */ + + }, { + key: 'isType', + value: function isType(name) { + return this.settings.type === name; + } + + /** + * Gets value of the core options. + * + * @return {Object} + */ + + }, { + key: 'settings', + get: function get$$1() { + return this._o; + } + + /** + * Sets value of the core options. + * + * @param {Object} o + * @return {Void} + */ + + , set: function set$$1(o) { + if (isObject(o)) { + this._o = o; + } else { + warn('Options must be an `object` instance.'); + } + } + + /** + * Gets current index of the slider. + * + * @return {Object} + */ + + }, { + key: 'index', + get: function get$$1() { + return this._i; + } + + /** + * Sets current index a slider. + * + * @return {Object} + */ + + , set: function set$$1(i) { + this._i = toInt(i); + } + + /** + * Gets type name of the slider. + * + * @return {String} + */ + + }, { + key: 'type', + get: function get$$1() { + return this.settings.type; + } + + /** + * Gets value of the idle status. + * + * @return {Boolean} + */ + + }, { + key: 'disabled', + get: function get$$1() { + return this._d; + } + + /** + * Sets value of the idle status. + * + * @return {Boolean} + */ + + , set: function set$$1(status) { + this._d = !!status; + } + }]); + return Glide; +}(); + +function Run(Glide, Components, Events) { + var Run = { + /** + * Initializes autorunning of the glide. + * + * @return {Void} + */ + mount: function mount() { + this._o = false; + }, + + /** + * Makes glides running based on the passed moving schema. + * + * @param {String} move + */ + make: function make(move) { + var _this = this; + + if (!Glide.disabled) { + Glide.disable(); + + this.move = move; + + Events.emit('run.before', this.move); + + this.calculate(); + + Events.emit('run', this.move); + + Components.Transition.after(function () { + if (_this.isOffset('<') || _this.isOffset('>')) { + _this._o = false; + + Events.emit('run.offset', _this.move); + } + + Events.emit('run.after', _this.move); + + Glide.enable(); + }); + } + }, + + /** + * Calculates current index based on defined move. + * + * @return {Void} + */ + calculate: function calculate() { + var move = this.move, + length = this.length; + var steps = move.steps, + direction = move.direction; + + var countableSteps = isNumber(toInt(steps)) && toInt(steps) !== 0; + + switch (direction) { + case '>': + if (steps === '>') { + Glide.index = length; + } else if (this.isEnd()) { + if (!(Glide.isType('slider') && !Glide.settings.rewind)) { + this._o = true; + + Glide.index = 0; + } + + Events.emit('run.end', move); + } else if (countableSteps) { + Glide.index += Math.min(length - Glide.index, -toInt(steps)); + } else { + Glide.index++; + } + break; + + case '<': + if (steps === '<') { + Glide.index = 0; + } else if (this.isStart()) { + if (!(Glide.isType('slider') && !Glide.settings.rewind)) { + this._o = true; + + Glide.index = length; + } + + Events.emit('run.start', move); + } else if (countableSteps) { + Glide.index -= Math.min(Glide.index, toInt(steps)); + } else { + Glide.index--; + } + break; + + case '=': + Glide.index = steps; + break; + } + }, + + /** + * Checks if we are on the first slide. + * + * @return {Boolean} + */ + isStart: function isStart() { + return Glide.index === 0; + }, + + /** + * Checks if we are on the last slide. + * + * @return {Boolean} + */ + isEnd: function isEnd() { + return Glide.index === this.length; + }, + + /** + * Checks if we are making a offset run. + * + * @param {String} direction + * @return {Boolean} + */ + isOffset: function isOffset(direction) { + return this._o && this.move.direction === direction; + } + }; + + define(Run, 'move', { + /** + * Gets value of the move schema. + * + * @returns {Object} + */ + get: function get() { + return this._m; + }, + + /** + * Sets value of the move schema. + * + * @returns {Object} + */ + set: function set(value) { + this._m = { + direction: value.substr(0, 1), + steps: value.substr(1) ? value.substr(1) : 0 + }; + } + }); + + define(Run, 'length', { + /** + * Gets value of the running distance based + * on zero-indexing number of slides. + * + * @return {Number} + */ + get: function get() { + var settings = Glide.settings; + var length = Components.Html.slides.length; + + // If the `bound` option is acitve, a maximum running distance should be + // reduced by `perView` and `focusAt` settings. Running distance + // should end before creating an empty space after instance. + + if (Glide.isType('slider') && settings.focusAt !== 'center' && settings.bound) { + return length - 1 - (toInt(settings.perView) - 1) + toInt(settings.focusAt); + } + + return length - 1; + } + }); + + define(Run, 'offset', { + /** + * Gets status of the offsetting flag. + * + * @return {Boolean} + */ + get: function get() { + return this._o; + } + }); + + return Run; +} + +/** + * Returns a current time. + * + * @return {Number} + */ +function now() { + return new Date().getTime(); +} + +/** + * Returns a function, that, when invoked, will only be triggered + * at most once during a given window of time. + * + * @param {Function} func + * @param {Number} wait + * @param {Object=} options + * @return {Function} + * + * @see https://github.com/jashkenas/underscore + */ +function throttle(func, wait, options) { + var timeout = void 0, + context = void 0, + args = void 0, + result = void 0; + var previous = 0; + if (!options) options = {}; + + var later = function later() { + previous = options.leading === false ? 0 : now(); + timeout = null; + result = func.apply(context, args); + if (!timeout) context = args = null; + }; + + var throttled = function throttled() { + var at = now(); + if (!previous && options.leading === false) previous = at; + var remaining = wait - (at - previous); + context = this; + args = arguments; + if (remaining <= 0 || remaining > wait) { + if (timeout) { + clearTimeout(timeout); + timeout = null; + } + previous = at; + result = func.apply(context, args); + if (!timeout) context = args = null; + } else if (!timeout && options.trailing !== false) { + timeout = setTimeout(later, remaining); + } + return result; + }; + + throttled.cancel = function () { + clearTimeout(timeout); + previous = 0; + timeout = context = args = null; + }; + + return throttled; +} + +var MARGIN_TYPE = { + ltr: ['marginLeft', 'marginRight'], + rtl: ['marginRight', 'marginLeft'] +}; + +function Gaps(Glide, Components, Events) { + var Gaps = { + /** + * Applies gaps between slides. First and last + * slides do not receive it's edge margins. + * + * @param {HTMLCollection} slides + * @return {Void} + */ + apply: function apply(slides) { + for (var i = 0, len = slides.length; i < len; i++) { + var style = slides[i].style; + var direction = Components.Direction.value; + + if (i !== 0) { + style[MARGIN_TYPE[direction][0]] = this.value / 2 + 'px'; + } else { + style[MARGIN_TYPE[direction][0]] = ''; + } + + if (i !== slides.length - 1) { + style[MARGIN_TYPE[direction][1]] = this.value / 2 + 'px'; + } else { + style[MARGIN_TYPE[direction][1]] = ''; + } + } + }, + + /** + * Removes gaps from the slides. + * + * @param {HTMLCollection} slides + * @returns {Void} + */ + remove: function remove(slides) { + for (var i = 0, len = slides.length; i < len; i++) { + var style = slides[i].style; + + style.marginLeft = ''; + style.marginRight = ''; + } + } + }; + + define(Gaps, 'value', { + /** + * Gets value of the gap. + * + * @returns {Number} + */ + get: function get() { + return toInt(Glide.settings.gap); + } + }); + + define(Gaps, 'grow', { + /** + * Gets additional dimentions value caused by gaps. + * Used to increase width of the slides wrapper. + * + * @returns {Number} + */ + get: function get() { + return Gaps.value * (Components.Sizes.length - 1); + } + }); + + define(Gaps, 'reductor', { + /** + * Gets reduction value caused by gaps. + * Used to subtract width of the slides. + * + * @returns {Number} + */ + get: function get() { + var perView = Glide.settings.perView; + + return Gaps.value * (perView - 1) / perView; + } + }); + + /** + * Apply calculated gaps: + * - after building, so slides (including clones) will receive proper margins + * - on updating via API, to recalculate gaps with new options + */ + Events.on(['build.after', 'update'], throttle(function () { + Gaps.apply(Components.Html.wrapper.children); + }, 30)); + + /** + * Remove gaps: + * - on destroying to bring markup to its inital state + */ + Events.on('destroy', function () { + Gaps.remove(Components.Html.wrapper.children); + }); + + return Gaps; +} + +/** + * Finds siblings nodes of the passed node. + * + * @param {Element} node + * @return {Array} + */ +function siblings(node) { + if (node && node.parentNode) { + var n = node.parentNode.firstChild; + var matched = []; + + for (; n; n = n.nextSibling) { + if (n.nodeType === 1 && n !== node) { + matched.push(n); + } + } + + return matched; + } + + return []; +} + +/** + * Checks if passed node exist and is a valid element. + * + * @param {Element} node + * @return {Boolean} + */ +function exist(node) { + if (node && node instanceof window.HTMLElement) { + return true; + } + + return false; +} + +var TRACK_SELECTOR = '[data-glide-el="track"]'; + +function Html(Glide, Components) { + var Html = { + /** + * Setup slider HTML nodes. + * + * @param {Glide} glide + */ + mount: function mount() { + this.root = Glide.selector; + this.track = this.root.querySelector(TRACK_SELECTOR); + this.slides = Array.prototype.slice.call(this.wrapper.children).filter(function (slide) { + return !slide.classList.contains(Glide.settings.classes.cloneSlide); + }); + } + }; + + define(Html, 'root', { + /** + * Gets node of the glide main element. + * + * @return {Object} + */ + get: function get() { + return Html._r; + }, + + /** + * Sets node of the glide main element. + * + * @return {Object} + */ + set: function set(r) { + if (isString(r)) { + r = document.querySelector(r); + } + + if (exist(r)) { + Html._r = r; + } else { + warn('Root element must be a existing Html node'); + } + } + }); + + define(Html, 'track', { + /** + * Gets node of the glide track with slides. + * + * @return {Object} + */ + get: function get() { + return Html._t; + }, + + /** + * Sets node of the glide track with slides. + * + * @return {Object} + */ + set: function set(t) { + if (exist(t)) { + Html._t = t; + } else { + warn('Could not find track element. Please use ' + TRACK_SELECTOR + ' attribute.'); + } + } + }); + + define(Html, 'wrapper', { + /** + * Gets node of the slides wrapper. + * + * @return {Object} + */ + get: function get() { + return Html.track.children[0]; + } + }); + + return Html; +} + +function Peek(Glide, Components, Events) { + var Peek = { + /** + * Setups how much to peek based on settings. + * + * @return {Void} + */ + mount: function mount() { + this.value = Glide.settings.peek; + } + }; + + define(Peek, 'value', { + /** + * Gets value of the peek. + * + * @returns {Number|Object} + */ + get: function get() { + return Peek._v; + }, + + /** + * Sets value of the peek. + * + * @param {Number|Object} value + * @return {Void} + */ + set: function set(value) { + if (isObject(value)) { + value.before = toInt(value.before); + value.after = toInt(value.after); + } else { + value = toInt(value); + } + + Peek._v = value; + } + }); + + define(Peek, 'reductor', { + /** + * Gets reduction value caused by peek. + * + * @returns {Number} + */ + get: function get() { + var value = Peek.value; + var perView = Glide.settings.perView; + + if (isObject(value)) { + return value.before / perView + value.after / perView; + } + + return value * 2 / perView; + } + }); + + /** + * Recalculate peeking sizes on: + * - when resizing window to update to proper percents + */ + Events.on(['resize', 'update'], function () { + Peek.mount(); + }); + + return Peek; +} + +function Move(Glide, Components, Events) { + var Move = { + /** + * Constructs move component. + * + * @returns {Void} + */ + mount: function mount() { + this._o = 0; + }, + + /** + * Calculates a movement value based on passed offset and currently active index. + * + * @param {Number} offset + * @return {Void} + */ + make: function make() { + var _this = this; + + var offset = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 0; + + this.offset = offset; + + Events.emit('move', { + movement: this.value + }); + + Components.Transition.after(function () { + Events.emit('move.after', { + movement: _this.value + }); + }); + } + }; + + define(Move, 'offset', { + /** + * Gets an offset value used to modify current translate. + * + * @return {Object} + */ + get: function get() { + return Move._o; + }, + + /** + * Sets an offset value used to modify current translate. + * + * @return {Object} + */ + set: function set(value) { + Move._o = !isUndefined(value) ? toInt(value) : 0; + } + }); + + define(Move, 'translate', { + /** + * Gets a raw movement value. + * + * @return {Number} + */ + get: function get() { + return Components.Sizes.slideWidth * Glide.index; + } + }); + + define(Move, 'value', { + /** + * Gets an actual movement value corrected by offset. + * + * @return {Number} + */ + get: function get() { + var offset = this.offset; + var translate = this.translate; + + if (Components.Direction.is('rtl')) { + return translate + offset; + } + + return translate - offset; + } + }); + + /** + * Make movement to proper slide on: + * - before build, so glide will start at `startAt` index + * - on each standard run to move to newly calculated index + */ + Events.on(['build.before', 'run'], function () { + Move.make(); + }); + + return Move; +} + +function Sizes(Glide, Components, Events) { + var Sizes = { + /** + * Setups dimentions of slides. + * + * @return {Void} + */ + setupSlides: function setupSlides() { + var width = this.slideWidth + 'px'; + var slides = Components.Html.slides; + + for (var i = 0; i < slides.length; i++) { + slides[i].style.width = width; + } + }, + + /** + * Setups dimentions of slides wrapper. + * + * @return {Void} + */ + setupWrapper: function setupWrapper(dimention) { + Components.Html.wrapper.style.width = this.wrapperSize + 'px'; + }, + + /** + * Removes applied styles from HTML elements. + * + * @returns {Void} + */ + remove: function remove() { + var slides = Components.Html.slides; + + for (var i = 0; i < slides.length; i++) { + slides[i].style.width = ''; + } + + Components.Html.wrapper.style.width = ''; + } + }; + + define(Sizes, 'length', { + /** + * Gets count number of the slides. + * + * @return {Number} + */ + get: function get() { + return Components.Html.slides.length; + } + }); + + define(Sizes, 'width', { + /** + * Gets width value of the glide. + * + * @return {Number} + */ + get: function get() { + return Components.Html.root.offsetWidth; + } + }); + + define(Sizes, 'wrapperSize', { + /** + * Gets size of the slides wrapper. + * + * @return {Number} + */ + get: function get() { + return Sizes.slideWidth * Sizes.length + Components.Gaps.grow + Components.Clones.grow; + } + }); + + define(Sizes, 'slideWidth', { + /** + * Gets width value of the single slide. + * + * @return {Number} + */ + get: function get() { + return Sizes.width / Glide.settings.perView - Components.Peek.reductor - Components.Gaps.reductor; + } + }); + + /** + * Apply calculated glide's dimensions: + * - before building, so other dimentions (e.g. translate) will be calculated propertly + * - when resizing window to recalculate sildes dimensions + * - on updating via API, to calculate dimensions based on new options + */ + Events.on(['build.before', 'resize', 'update'], function () { + Sizes.setupSlides(); + Sizes.setupWrapper(); + }); + + /** + * Remove calculated glide's dimensions: + * - on destoting to bring markup to its inital state + */ + Events.on('destroy', function () { + Sizes.remove(); + }); + + return Sizes; +} + +function Build(Glide, Components, Events) { + var Build = { + /** + * Init glide building. Adds classes, sets + * dimensions and setups initial state. + * + * @return {Void} + */ + mount: function mount() { + Events.emit('build.before'); + + this.typeClass(); + this.activeClass(); + + Events.emit('build.after'); + }, + + /** + * Adds `type` class to the glide element. + * + * @return {Void} + */ + typeClass: function typeClass() { + Components.Html.root.classList.add(Glide.settings.classes[Glide.settings.type]); + }, + + /** + * Sets active class to current slide. + * + * @return {Void} + */ + activeClass: function activeClass() { + var classes = Glide.settings.classes; + var slide = Components.Html.slides[Glide.index]; + + if (slide) { + slide.classList.add(classes.activeSlide); + + siblings(slide).forEach(function (sibling) { + sibling.classList.remove(classes.activeSlide); + }); + } + }, + + /** + * Removes HTML classes applied at building. + * + * @return {Void} + */ + removeClasses: function removeClasses() { + var classes = Glide.settings.classes; + + Components.Html.root.classList.remove(classes[Glide.settings.type]); + + Components.Html.slides.forEach(function (sibling) { + sibling.classList.remove(classes.activeSlide); + }); + } + }; + + /** + * Clear building classes: + * - on destroying to bring HTML to its initial state + * - on updating to remove classes before remounting component + */ + Events.on(['destroy', 'update'], function () { + Build.removeClasses(); + }); + + /** + * Remount component: + * - on resizing of the window to calculate new dimentions + * - on updating settings via API + */ + Events.on(['resize', 'update'], function () { + Build.mount(); + }); + + /** + * Swap active class of current slide: + * - after each move to the new index + */ + Events.on('move.after', function () { + Build.activeClass(); + }); + + return Build; +} + +function Clones(Glide, Components, Events) { + var Clones = { + /** + * Create pattern map and collect slides to be cloned. + */ + mount: function mount() { + this.items = []; + + if (Glide.isType('carousel')) { + this.items = this.collect(); + } + }, + + /** + * Collect clones with pattern. + * + * @return {Void} + */ + collect: function collect() { + var items = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : []; + var slides = Components.Html.slides; + var _Glide$settings = Glide.settings, + perView = _Glide$settings.perView, + classes = _Glide$settings.classes; + + var peekIncrementer = +!!Glide.settings.peek; + var part = perView + peekIncrementer; + var start = slides.slice(0, part); + var end = slides.slice(-part); + + for (var r = 0; r < Math.max(1, Math.floor(perView / slides.length)); r++) { + for (var i = 0; i < start.length; i++) { + var clone = start[i].cloneNode(true); + + clone.classList.add(classes.cloneSlide); + + items.push(clone); + } + + for (var _i = 0; _i < end.length; _i++) { + var _clone = end[_i].cloneNode(true); + + _clone.classList.add(classes.cloneSlide); + + items.unshift(_clone); + } + } + + return items; + }, + + /** + * Append cloned slides with generated pattern. + * + * @return {Void} + */ + append: function append() { + var items = this.items; + var _Components$Html = Components.Html, + wrapper = _Components$Html.wrapper, + slides = _Components$Html.slides; + + var half = Math.floor(items.length / 2); + var prepend = items.slice(0, half).reverse(); + var append = items.slice(half, items.length); + var width = Components.Sizes.slideWidth + 'px'; + + for (var i = 0; i < append.length; i++) { + wrapper.appendChild(append[i]); + } + + for (var _i2 = 0; _i2 < prepend.length; _i2++) { + wrapper.insertBefore(prepend[_i2], slides[0]); + } + + for (var _i3 = 0; _i3 < items.length; _i3++) { + items[_i3].style.width = width; + } + }, + + /** + * Remove all cloned slides. + * + * @return {Void} + */ + remove: function remove() { + var items = this.items; + + for (var i = 0; i < items.length; i++) { + Components.Html.wrapper.removeChild(items[i]); + } + } + }; + + define(Clones, 'grow', { + /** + * Gets additional dimentions value caused by clones. + * + * @return {Number} + */ + get: function get() { + return (Components.Sizes.slideWidth + Components.Gaps.value) * Clones.items.length; + } + }); + + /** + * Append additional slide's clones: + * - while glide's type is `carousel` + */ + Events.on('update', function () { + Clones.remove(); + Clones.mount(); + Clones.append(); + }); + + /** + * Append additional slide's clones: + * - while glide's type is `carousel` + */ + Events.on('build.before', function () { + if (Glide.isType('carousel')) { + Clones.append(); + } + }); + + /** + * Remove clones HTMLElements: + * - on destroying, to bring HTML to its initial state + */ + Events.on('destroy', function () { + Clones.remove(); + }); + + return Clones; +} + +var EventsBinder = function () { + /** + * Construct a EventsBinder instance. + */ + function EventsBinder() { + var listeners = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; + classCallCheck(this, EventsBinder); + + this.listeners = listeners; + } + + /** + * Adds events listeners to arrows HTML elements. + * + * @param {String|Array} events + * @param {Element|Window|Document} el + * @param {Function} closure + * @param {Boolean|Object} capture + * @return {Void} + */ + + createClass(EventsBinder, [{ + key: 'on', + value: function on(events, el, closure) { + var capture = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : false; + + if (isString(events)) { + events = [events]; + } + + for (var i = 0; i < events.length; i++) { + this.listeners[events[i]] = closure; + + el.addEventListener(events[i], this.listeners[events[i]], capture); + } + } + + /** + * Removes event listeners from arrows HTML elements. + * + * @param {String|Array} events + * @param {Element|Window|Document} el + * @param {Boolean|Object} capture + * @return {Void} + */ + + }, { + key: 'off', + value: function off(events, el) { + var capture = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false; + + if (isString(events)) { + events = [events]; + } + + for (var i = 0; i < events.length; i++) { + el.removeEventListener(events[i], this.listeners[events[i]], capture); + } + } + + /** + * Destroy collected listeners. + * + * @returns {Void} + */ + + }, { + key: 'destroy', + value: function destroy() { + delete this.listeners; + } + }]); + return EventsBinder; +}(); + +function Resize(Glide, Components, Events) { + /** + * Instance of the binder for DOM Events. + * + * @type {EventsBinder} + */ + var Binder = new EventsBinder(); + + var Resize = { + /** + * Initializes window bindings. + */ + mount: function mount() { + this.bind(); + }, + + /** + * Binds `rezsize` listener to the window. + * It's a costly event, so we are debouncing it. + * + * @return {Void} + */ + bind: function bind() { + Binder.on('resize', window, throttle(function () { + Events.emit('resize'); + }, Glide.settings.throttle)); + }, + + /** + * Unbinds listeners from the window. + * + * @return {Void} + */ + unbind: function unbind() { + Binder.off('resize', window); + } + }; + + /** + * Remove bindings from window: + * - on destroying, to remove added EventListener + */ + Events.on('destroy', function () { + Resize.unbind(); + Binder.destroy(); + }); + + return Resize; +} + +var VALID_DIRECTIONS = ['ltr', 'rtl']; +var FLIPED_MOVEMENTS = { + '>': '<', + '<': '>', + '=': '=' +}; + +function Direction(Glide, Components, Events) { + var Direction = { + /** + * Setups gap value based on settings. + * + * @return {Void} + */ + mount: function mount() { + this.value = Glide.settings.direction; + }, + + /** + * Resolves pattern based on direction value + * + * @param {String} pattern + * @returns {String} + */ + resolve: function resolve(pattern) { + var token = pattern.slice(0, 1); + + if (this.is('rtl')) { + return pattern.split(token).join(FLIPED_MOVEMENTS[token]); + } + + return pattern; + }, + + /** + * Checks value of direction mode. + * + * @param {String} direction + * @returns {Boolean} + */ + is: function is(direction) { + return this.value === direction; + }, + + /** + * Applies direction class to the root HTML element. + * + * @return {Void} + */ + addClass: function addClass() { + Components.Html.root.classList.add(Glide.settings.classes.direction[this.value]); + }, + + /** + * Removes direction class from the root HTML element. + * + * @return {Void} + */ + removeClass: function removeClass() { + Components.Html.root.classList.remove(Glide.settings.classes.direction[this.value]); + } + }; + + define(Direction, 'value', { + /** + * Gets value of the direction. + * + * @returns {Number} + */ + get: function get() { + return Direction._v; + }, + + /** + * Sets value of the direction. + * + * @param {String} value + * @return {Void} + */ + set: function set(value) { + if (VALID_DIRECTIONS.indexOf(value) > -1) { + Direction._v = value; + } else { + warn('Direction value must be `ltr` or `rtl`'); + } + } + }); + + /** + * Clear direction class: + * - on destroy to bring HTML to its initial state + * - on update to remove class before reappling bellow + */ + Events.on(['destroy', 'update'], function () { + Direction.removeClass(); + }); + + /** + * Remount component: + * - on update to reflect changes in direction value + */ + Events.on('update', function () { + Direction.mount(); + }); + + /** + * Apply direction class: + * - before building to apply class for the first time + * - on updating to reapply direction class that may changed + */ + Events.on(['build.before', 'update'], function () { + Direction.addClass(); + }); + + return Direction; +} + +/** + * Reflects value of glide movement. + * + * @param {Object} Glide + * @param {Object} Components + * @return {Object} + */ +function Rtl(Glide, Components) { + return { + /** + * Negates the passed translate if glide is in RTL option. + * + * @param {Number} translate + * @return {Number} + */ + modify: function modify(translate) { + if (Components.Direction.is('rtl')) { + return -translate; + } + + return translate; + } + }; +} + +/** + * Updates glide movement with a `gap` settings. + * + * @param {Object} Glide + * @param {Object} Components + * @return {Object} + */ +function Gap(Glide, Components) { + return { + /** + * Modifies passed translate value with number in the `gap` settings. + * + * @param {Number} translate + * @return {Number} + */ + modify: function modify(translate) { + return translate + Components.Gaps.value * Glide.index; + } + }; +} + +/** + * Updates glide movement with width of additional clones width. + * + * @param {Object} Glide + * @param {Object} Components + * @return {Object} + */ +function Grow(Glide, Components) { + return { + /** + * Adds to the passed translate width of the half of clones. + * + * @param {Number} translate + * @return {Number} + */ + modify: function modify(translate) { + return translate + Components.Clones.grow / 2; + } + }; +} + +/** + * Updates glide movement with a `peek` settings. + * + * @param {Object} Glide + * @param {Object} Components + * @return {Object} + */ +function Peeking(Glide, Components) { + return { + /** + * Modifies passed translate value with a `peek` setting. + * + * @param {Number} translate + * @return {Number} + */ + modify: function modify(translate) { + if (Glide.settings.focusAt >= 0) { + var peek = Components.Peek.value; + + if (isObject(peek)) { + return translate - peek.before; + } + + return translate - peek; + } + + return translate; + } + }; +} + +/** + * Updates glide movement with a `focusAt` settings. + * + * @param {Object} Glide + * @param {Object} Components + * @return {Object} + */ +function Focusing(Glide, Components) { + return { + /** + * Modifies passed translate value with index in the `focusAt` setting. + * + * @param {Number} translate + * @return {Number} + */ + modify: function modify(translate) { + var gap = Components.Gaps.value; + var width = Components.Sizes.width; + var focusAt = Glide.settings.focusAt; + var slideWidth = Components.Sizes.slideWidth; + + if (focusAt === 'center') { + return translate - (width / 2 - slideWidth / 2); + } + + return translate - slideWidth * focusAt - gap * focusAt; + } + }; +} + +/** + * Applies diffrent transformers on translate value. + * + * @param {Object} Glide + * @param {Object} Components + * @return {Object} + */ +function mutator(Glide, Components, Events) { + /** + * Merge instance transformers with collection of default transformers. + * It's important that the Rtl component be last on the list, + * so it reflects all previous transformations. + * + * @type {Array} + */ + var TRANSFORMERS = [Gap, Grow, Peeking, Focusing].concat(Glide._t, [Rtl]); + + return { + /** + * Piplines translate value with registered transformers. + * + * @param {Number} translate + * @return {Number} + */ + mutate: function mutate(translate) { + for (var i = 0; i < TRANSFORMERS.length; i++) { + var transformer = TRANSFORMERS[i]; + + if (isFunction(transformer) && isFunction(transformer().modify)) { + translate = transformer(Glide, Components, Events).modify(translate); + } else { + warn('Transformer should be a function that returns an object with `modify()` method'); + } + } + + return translate; + } + }; +} + +function Translate(Glide, Components, Events) { + var Translate = { + /** + * Sets value of translate on HTML element. + * + * @param {Number} value + * @return {Void} + */ + set: function set(value) { + var transform = mutator(Glide, Components).mutate(value); + + Components.Html.wrapper.style.transform = 'translate3d(' + -1 * transform + 'px, 0px, 0px)'; + }, + + /** + * Removes value of translate from HTML element. + * + * @return {Void} + */ + remove: function remove() { + Components.Html.wrapper.style.transform = ''; + } + }; + + /** + * Set new translate value: + * - on move to reflect index change + * - on updating via API to reflect possible changes in options + */ + Events.on('move', function (context) { + var gap = Components.Gaps.value; + var length = Components.Sizes.length; + var width = Components.Sizes.slideWidth; + + if (Glide.isType('carousel') && Components.Run.isOffset('<')) { + Components.Transition.after(function () { + Events.emit('translate.jump'); + + Translate.set(width * (length - 1)); + }); + + return Translate.set(-width - gap * length); + } + + if (Glide.isType('carousel') && Components.Run.isOffset('>')) { + Components.Transition.after(function () { + Events.emit('translate.jump'); + + Translate.set(0); + }); + + return Translate.set(width * length + gap * length); + } + + return Translate.set(context.movement); + }); + + /** + * Remove translate: + * - on destroying to bring markup to its inital state + */ + Events.on('destroy', function () { + Translate.remove(); + }); + + return Translate; +} + +function Transition(Glide, Components, Events) { + /** + * Holds inactivity status of transition. + * When true transition is not applied. + * + * @type {Boolean} + */ + var disabled = false; + + var Transition = { + /** + * Composes string of the CSS transition. + * + * @param {String} property + * @return {String} + */ + compose: function compose(property) { + var settings = Glide.settings; + + if (!disabled) { + return property + ' ' + this.duration + 'ms ' + settings.animationTimingFunc; + } + + return property + ' 0ms ' + settings.animationTimingFunc; + }, + + /** + * Sets value of transition on HTML element. + * + * @param {String=} property + * @return {Void} + */ + set: function set() { + var property = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 'transform'; + + Components.Html.wrapper.style.transition = this.compose(property); + }, + + /** + * Removes value of transition from HTML element. + * + * @return {Void} + */ + remove: function remove() { + Components.Html.wrapper.style.transition = ''; + }, + + /** + * Runs callback after animation. + * + * @param {Function} callback + * @return {Void} + */ + after: function after(callback) { + setTimeout(function () { + callback(); + }, this.duration); + }, + + /** + * Enable transition. + * + * @return {Void} + */ + enable: function enable() { + disabled = false; + + this.set(); + }, + + /** + * Disable transition. + * + * @return {Void} + */ + disable: function disable() { + disabled = true; + + this.set(); + } + }; + + define(Transition, 'duration', { + /** + * Gets duration of the transition based + * on currently running animation type. + * + * @return {Number} + */ + get: function get() { + var settings = Glide.settings; + + if (Glide.isType('slider') && Components.Run.offset) { + return settings.rewindDuration; + } + + return settings.animationDuration; + } + }); + + /** + * Set transition `style` value: + * - on each moving, because it may be cleared by offset move + */ + Events.on('move', function () { + Transition.set(); + }); + + /** + * Disable transition: + * - before initial build to avoid transitioning from `0` to `startAt` index + * - while resizing window and recalculating dimentions + * - on jumping from offset transition at start and end edges in `carousel` type + */ + Events.on(['build.before', 'resize', 'translate.jump'], function () { + Transition.disable(); + }); + + /** + * Enable transition: + * - on each running, because it may be disabled by offset move + */ + Events.on('run', function () { + Transition.enable(); + }); + + /** + * Remove transition: + * - on destroying to bring markup to its inital state + */ + Events.on('destroy', function () { + Transition.remove(); + }); + + return Transition; +} + +/** + * Test via a getter in the options object to see + * if the passive property is accessed. + * + * @see https://github.com/WICG/EventListenerOptions/blob/gh-pages/explainer.md#feature-detection + */ + +var supportsPassive = false; + +try { + var opts = Object.defineProperty({}, 'passive', { + get: function get() { + supportsPassive = true; + } + }); + + window.addEventListener('testPassive', null, opts); + window.removeEventListener('testPassive', null, opts); +} catch (e) {} + +var supportsPassive$1 = supportsPassive; + +var START_EVENTS = ['touchstart', 'mousedown']; +var MOVE_EVENTS = ['touchmove', 'mousemove']; +var END_EVENTS = ['touchend', 'touchcancel', 'mouseup', 'mouseleave']; +var MOUSE_EVENTS = ['mousedown', 'mousemove', 'mouseup', 'mouseleave']; + +function Swipe(Glide, Components, Events) { + /** + * Instance of the binder for DOM Events. + * + * @type {EventsBinder} + */ + var Binder = new EventsBinder(); + + var swipeSin = 0; + var swipeStartX = 0; + var swipeStartY = 0; + var disabled = false; + var moveable = true; + var capture = supportsPassive$1 ? { passive: true } : false; + + var Swipe = { + /** + * Initializes swipe bindings. + * + * @return {Void} + */ + mount: function mount() { + this.bindSwipeStart(); + }, + + /** + * Handler for `swipestart` event. Calculates entry points of the user's tap. + * + * @param {Object} event + * @return {Void} + */ + start: function start(event) { + if (!disabled && !Glide.disabled) { + this.disable(); + + var swipe = this.touches(event); + + moveable = true; + swipeSin = null; + swipeStartX = toInt(swipe.pageX); + swipeStartY = toInt(swipe.pageY); + + this.bindSwipeMove(); + this.bindSwipeEnd(); + + Events.emit('swipe.start'); + } + }, + + /** + * Handler for `swipemove` event. Calculates user's tap angle and distance. + * + * @param {Object} event + */ + move: function move(event) { + if (!Glide.disabled) { + var _Glide$settings = Glide.settings, + touchAngle = _Glide$settings.touchAngle, + touchRatio = _Glide$settings.touchRatio, + classes = _Glide$settings.classes; + + var swipe = this.touches(event); + + var subExSx = toInt(swipe.pageX) - swipeStartX; + var subEySy = toInt(swipe.pageY) - swipeStartY; + var powEX = Math.abs(subExSx << 2); + var powEY = Math.abs(subEySy << 2); + var swipeHypotenuse = Math.sqrt(powEX + powEY); + var swipeCathetus = Math.sqrt(powEY); + + swipeSin = Math.asin(swipeCathetus / swipeHypotenuse); + + if (moveable && swipeSin * 180 / Math.PI < touchAngle) { + event.stopPropagation(); + + Components.Move.make(subExSx * toFloat(touchRatio)); + + Components.Html.root.classList.add(classes.dragging); + + Events.emit('swipe.move'); + } else { + moveable = false; + + return false; + } + } + }, + + /** + * Handler for `swipeend` event. Finitializes user's tap and decides about glide move. + * + * @param {Object} event + * @return {Void} + */ + end: function end(event) { + if (!Glide.disabled) { + var settings = Glide.settings; + + var swipe = this.touches(event); + var threshold = this.threshold(event); + + var swipeDistance = swipe.pageX - swipeStartX; + var swipeDeg = swipeSin * 180 / Math.PI; + var steps = Math.round(swipeDistance / Components.Sizes.slideWidth); + + this.enable(); + + if (moveable) { + if (swipeDistance > threshold && swipeDeg < settings.touchAngle) { + // While swipe is positive and greater than threshold move backward. + if (settings.perTouch) { + steps = Math.min(steps, toInt(settings.perTouch)); + } + + if (Components.Direction.is('rtl')) { + steps = -steps; + } + + Components.Run.make(Components.Direction.resolve('<' + steps)); + } else if (swipeDistance < -threshold && swipeDeg < settings.touchAngle) { + // While swipe is negative and lower than negative threshold move forward. + if (settings.perTouch) { + steps = Math.max(steps, -toInt(settings.perTouch)); + } + + if (Components.Direction.is('rtl')) { + steps = -steps; + } + + Components.Run.make(Components.Direction.resolve('>' + steps)); + } else { + // While swipe don't reach distance apply previous transform. + Components.Move.make(); + } + } + + Components.Html.root.classList.remove(settings.classes.dragging); + + this.unbindSwipeMove(); + this.unbindSwipeEnd(); + + Events.emit('swipe.end'); + } + }, + + /** + * Binds swipe's starting event. + * + * @return {Void} + */ + bindSwipeStart: function bindSwipeStart() { + var _this = this; + + var settings = Glide.settings; + + if (settings.swipeThreshold) { + Binder.on(START_EVENTS[0], Components.Html.wrapper, function (event) { + _this.start(event); + }, capture); + } + + if (settings.dragThreshold) { + Binder.on(START_EVENTS[1], Components.Html.wrapper, function (event) { + _this.start(event); + }, capture); + } + }, + + /** + * Unbinds swipe's starting event. + * + * @return {Void} + */ + unbindSwipeStart: function unbindSwipeStart() { + Binder.off(START_EVENTS[0], Components.Html.wrapper, capture); + Binder.off(START_EVENTS[1], Components.Html.wrapper, capture); + }, + + /** + * Binds swipe's moving event. + * + * @return {Void} + */ + bindSwipeMove: function bindSwipeMove() { + var _this2 = this; + + Binder.on(MOVE_EVENTS, Components.Html.wrapper, throttle(function (event) { + _this2.move(event); + }, Glide.settings.throttle), capture); + }, + + /** + * Unbinds swipe's moving event. + * + * @return {Void} + */ + unbindSwipeMove: function unbindSwipeMove() { + Binder.off(MOVE_EVENTS, Components.Html.wrapper, capture); + }, + + /** + * Binds swipe's ending event. + * + * @return {Void} + */ + bindSwipeEnd: function bindSwipeEnd() { + var _this3 = this; + + Binder.on(END_EVENTS, Components.Html.wrapper, function (event) { + _this3.end(event); + }); + }, + + /** + * Unbinds swipe's ending event. + * + * @return {Void} + */ + unbindSwipeEnd: function unbindSwipeEnd() { + Binder.off(END_EVENTS, Components.Html.wrapper); + }, + + /** + * Normalizes event touches points accorting to different types. + * + * @param {Object} event + */ + touches: function touches(event) { + if (MOUSE_EVENTS.indexOf(event.type) > -1) { + return event; + } + + return event.touches[0] || event.changedTouches[0]; + }, + + /** + * Gets value of minimum swipe distance settings based on event type. + * + * @return {Number} + */ + threshold: function threshold(event) { + var settings = Glide.settings; + + if (MOUSE_EVENTS.indexOf(event.type) > -1) { + return settings.dragThreshold; + } + + return settings.swipeThreshold; + }, + + /** + * Enables swipe event. + * + * @return {self} + */ + enable: function enable() { + disabled = false; + + Components.Transition.enable(); + + return this; + }, + + /** + * Disables swipe event. + * + * @return {self} + */ + disable: function disable() { + disabled = true; + + Components.Transition.disable(); + + return this; + } + }; + + /** + * Add component class: + * - after initial building + */ + Events.on('build.after', function () { + Components.Html.root.classList.add(Glide.settings.classes.swipeable); + }); + + /** + * Remove swiping bindings: + * - on destroying, to remove added EventListeners + */ + Events.on('destroy', function () { + Swipe.unbindSwipeStart(); + Swipe.unbindSwipeMove(); + Swipe.unbindSwipeEnd(); + Binder.destroy(); + }); + + return Swipe; +} + +function Images(Glide, Components, Events) { + /** + * Instance of the binder for DOM Events. + * + * @type {EventsBinder} + */ + var Binder = new EventsBinder(); + + var Images = { + /** + * Binds listener to glide wrapper. + * + * @return {Void} + */ + mount: function mount() { + this.bind(); + }, + + /** + * Binds `dragstart` event on wrapper to prevent dragging images. + * + * @return {Void} + */ + bind: function bind() { + Binder.on('dragstart', Components.Html.wrapper, this.dragstart); + }, + + /** + * Unbinds `dragstart` event on wrapper. + * + * @return {Void} + */ + unbind: function unbind() { + Binder.off('dragstart', Components.Html.wrapper); + }, + + /** + * Event handler. Prevents dragging. + * + * @return {Void} + */ + dragstart: function dragstart(event) { + event.preventDefault(); + } + }; + + /** + * Remove bindings from images: + * - on destroying, to remove added EventListeners + */ + Events.on('destroy', function () { + Images.unbind(); + Binder.destroy(); + }); + + return Images; +} + +function Anchors(Glide, Components, Events) { + /** + * Instance of the binder for DOM Events. + * + * @type {EventsBinder} + */ + var Binder = new EventsBinder(); + + /** + * Holds detaching status of anchors. + * Prevents detaching of already detached anchors. + * + * @private + * @type {Boolean} + */ + var detached = false; + + /** + * Holds preventing status of anchors. + * If `true` redirection after click will be disabled. + * + * @private + * @type {Boolean} + */ + var prevented = false; + + var Anchors = { + /** + * Setups a initial state of anchors component. + * + * @returns {Void} + */ + mount: function mount() { + /** + * Holds collection of anchors elements. + * + * @private + * @type {HTMLCollection} + */ + this._a = Components.Html.wrapper.querySelectorAll('a'); + + this.bind(); + }, + + /** + * Binds events to anchors inside a track. + * + * @return {Void} + */ + bind: function bind() { + Binder.on('click', Components.Html.wrapper, this.click); + }, + + /** + * Unbinds events attached to anchors inside a track. + * + * @return {Void} + */ + unbind: function unbind() { + Binder.off('click', Components.Html.wrapper); + }, + + /** + * Handler for click event. Prevents clicks when glide is in `prevent` status. + * + * @param {Object} event + * @return {Void} + */ + click: function click(event) { + if (prevented) { + event.stopPropagation(); + event.preventDefault(); + } + }, + + /** + * Detaches anchors click event inside glide. + * + * @return {self} + */ + detach: function detach() { + prevented = true; + + if (!detached) { + for (var i = 0; i < this.items.length; i++) { + this.items[i].draggable = false; + + this.items[i].setAttribute('data-href', this.items[i].getAttribute('href')); + + this.items[i].removeAttribute('href'); + } + + detached = true; + } + + return this; + }, + + /** + * Attaches anchors click events inside glide. + * + * @return {self} + */ + attach: function attach() { + prevented = false; + + if (detached) { + for (var i = 0; i < this.items.length; i++) { + this.items[i].draggable = true; + + this.items[i].setAttribute('href', this.items[i].getAttribute('data-href')); + } + + detached = false; + } + + return this; + } + }; + + define(Anchors, 'items', { + /** + * Gets collection of the arrows HTML elements. + * + * @return {HTMLElement[]} + */ + get: function get() { + return Anchors._a; + } + }); + + /** + * Detach anchors inside slides: + * - on swiping, so they won't redirect to its `href` attributes + */ + Events.on('swipe.move', function () { + Anchors.detach(); + }); + + /** + * Attach anchors inside slides: + * - after swiping and transitions ends, so they can redirect after click again + */ + Events.on('swipe.end', function () { + Components.Transition.after(function () { + Anchors.attach(); + }); + }); + + /** + * Unbind anchors inside slides: + * - on destroying, to bring anchors to its initial state + */ + Events.on('destroy', function () { + Anchors.attach(); + Anchors.unbind(); + Binder.destroy(); + }); + + return Anchors; +} + +var NAV_SELECTOR = '[data-glide-el="controls[nav]"]'; +var CONTROLS_SELECTOR = '[data-glide-el^="controls"]'; + +function Controls(Glide, Components, Events) { + /** + * Instance of the binder for DOM Events. + * + * @type {EventsBinder} + */ + var Binder = new EventsBinder(); + + var Controls = { + /** + * Inits arrows. Binds events listeners + * to the arrows HTML elements. + * + * @return {Void} + */ + mount: function mount() { + /** + * Collection of navigation HTML elements. + * + * @private + * @type {HTMLCollection} + */ + this._n = Components.Html.root.querySelectorAll(NAV_SELECTOR); + + /** + * Collection of controls HTML elements. + * + * @private + * @type {HTMLCollection} + */ + this._c = Components.Html.root.querySelectorAll(CONTROLS_SELECTOR); + + this.addBindings(); + }, + + /** + * Sets active class to current slide. + * + * @return {Void} + */ + setActive: function setActive() { + for (var i = 0; i < this._n.length; i++) { + this.addClass(this._n[i].children); + } + }, + + /** + * Removes active class to current slide. + * + * @return {Void} + */ + removeActive: function removeActive() { + for (var i = 0; i < this._n.length; i++) { + this.removeClass(this._n[i].children); + } + }, + + /** + * Toggles active class on items inside navigation. + * + * @param {HTMLElement} controls + * @return {Void} + */ + addClass: function addClass(controls) { + var settings = Glide.settings; + var item = controls[Glide.index]; + + item.classList.add(settings.classes.activeNav); + + siblings(item).forEach(function (sibling) { + sibling.classList.remove(settings.classes.activeNav); + }); + }, + + /** + * Removes active class from active control. + * + * @param {HTMLElement} controls + * @return {Void} + */ + removeClass: function removeClass(controls) { + controls[Glide.index].classList.remove(Glide.settings.classes.activeNav); + }, + + /** + * Adds handles to the each group of controls. + * + * @return {Void} + */ + addBindings: function addBindings() { + for (var i = 0; i < this._c.length; i++) { + this.bind(this._c[i].children); + } + }, + + /** + * Removes handles from the each group of controls. + * + * @return {Void} + */ + removeBindings: function removeBindings() { + for (var i = 0; i < this._c.length; i++) { + this.unbind(this._c[i].children); + } + }, + + /** + * Binds events to arrows HTML elements. + * + * @param {HTMLCollection} elements + * @return {Void} + */ + bind: function bind(elements) { + for (var i = 0; i < elements.length; i++) { + Binder.on(['click', 'touchstart'], elements[i], this.click); + } + }, + + /** + * Unbinds events binded to the arrows HTML elements. + * + * @param {HTMLCollection} elements + * @return {Void} + */ + unbind: function unbind(elements) { + for (var i = 0; i < elements.length; i++) { + Binder.off(['click', 'touchstart'], elements[i]); + } + }, + + /** + * Handles `click` event on the arrows HTML elements. + * Moves slider in driection precised in + * `data-glide-dir` attribute. + * + * @param {Object} event + * @return {Void} + */ + click: function click(event) { + event.preventDefault(); + + Components.Run.make(Components.Direction.resolve(event.currentTarget.getAttribute('data-glide-dir'))); + } + }; + + define(Controls, 'items', { + /** + * Gets collection of the controls HTML elements. + * + * @return {HTMLElement[]} + */ + get: function get() { + return Controls._c; + } + }); + + /** + * Swap active class of current navigation item: + * - after mounting to set it to initial index + * - after each move to the new index + */ + Events.on(['mount.after', 'move.after'], function () { + Controls.setActive(); + }); + + /** + * Remove bindings and HTML Classes: + * - on destroying, to bring markup to its initial state + */ + Events.on('destroy', function () { + Controls.removeBindings(); + Controls.removeActive(); + Binder.destroy(); + }); + + return Controls; +} + +function Keyboard(Glide, Components, Events) { + /** + * Instance of the binder for DOM Events. + * + * @type {EventsBinder} + */ + var Binder = new EventsBinder(); + + var Keyboard = { + /** + * Binds keyboard events on component mount. + * + * @return {Void} + */ + mount: function mount() { + if (Glide.settings.keyboard) { + this.bind(); + } + }, + + /** + * Adds keyboard press events. + * + * @return {Void} + */ + bind: function bind() { + Binder.on('keyup', document, this.press); + }, + + /** + * Removes keyboard press events. + * + * @return {Void} + */ + unbind: function unbind() { + Binder.off('keyup', document); + }, + + /** + * Handles keyboard's arrows press and moving glide foward and backward. + * + * @param {Object} event + * @return {Void} + */ + press: function press(event) { + if (event.keyCode === 39) { + Components.Run.make(Components.Direction.resolve('>')); + } + + if (event.keyCode === 37) { + Components.Run.make(Components.Direction.resolve('<')); + } + } + }; + + /** + * Remove bindings from keyboard: + * - on destroying to remove added events + * - on updating to remove events before remounting + */ + Events.on(['destroy', 'update'], function () { + Keyboard.unbind(); + }); + + /** + * Remount component + * - on updating to reflect potential changes in settings + */ + Events.on('update', function () { + Keyboard.mount(); + }); + + /** + * Destroy binder: + * - on destroying to remove listeners + */ + Events.on('destroy', function () { + Binder.destroy(); + }); + + return Keyboard; +} + +function Autoplay(Glide, Components, Events) { + /** + * Instance of the binder for DOM Events. + * + * @type {EventsBinder} + */ + var Binder = new EventsBinder(); + + var Autoplay = { + /** + * Initializes autoplaying and events. + * + * @return {Void} + */ + mount: function mount() { + this.start(); + + if (Glide.settings.hoverpause) { + this.bind(); + } + }, + + /** + * Starts autoplaying in configured interval. + * + * @param {Boolean|Number} force Run autoplaying with passed interval regardless of `autoplay` settings + * @return {Void} + */ + start: function start() { + var _this = this; + + if (Glide.settings.autoplay) { + if (isUndefined(this._i)) { + this._i = setInterval(function () { + _this.stop(); + + Components.Run.make('>'); + + _this.start(); + }, this.time); + } + } + }, + + /** + * Stops autorunning of the glide. + * + * @return {Void} + */ + stop: function stop() { + this._i = clearInterval(this._i); + }, + + /** + * Stops autoplaying while mouse is over glide's area. + * + * @return {Void} + */ + bind: function bind() { + var _this2 = this; + + Binder.on('mouseover', Components.Html.root, function () { + _this2.stop(); + }); + + Binder.on('mouseout', Components.Html.root, function () { + _this2.start(); + }); + }, + + /** + * Unbind mouseover events. + * + * @returns {Void} + */ + unbind: function unbind() { + Binder.off(['mouseover', 'mouseout'], Components.Html.root); + } + }; + + define(Autoplay, 'time', { + /** + * Gets time period value for the autoplay interval. Prioritizes + * times in `data-glide-autoplay` attrubutes over options. + * + * @return {Number} + */ + get: function get() { + var autoplay = Components.Html.slides[Glide.index].getAttribute('data-glide-autoplay'); + + if (autoplay) { + return toInt(autoplay); + } + + return toInt(Glide.settings.autoplay); + } + }); + + /** + * Stop autoplaying and unbind events: + * - on destroying, to clear defined interval + * - on updating via API to reset interval that may changed + */ + Events.on(['destroy', 'update'], function () { + Autoplay.unbind(); + }); + + /** + * Stop autoplaying: + * - before each run, to restart autoplaying + * - on pausing via API + * - on destroying, to clear defined interval + * - while starting a swipe + * - on updating via API to reset interval that may changed + */ + Events.on(['run.before', 'pause', 'destroy', 'swipe.start', 'update'], function () { + Autoplay.stop(); + }); + + /** + * Start autoplaying: + * - after each run, to restart autoplaying + * - on playing via API + * - while ending a swipe + */ + Events.on(['run.after', 'play', 'swipe.end'], function () { + Autoplay.start(); + }); + + /** + * Remount autoplaying: + * - on updating via API to reset interval that may changed + */ + Events.on('update', function () { + Autoplay.mount(); + }); + + /** + * Destroy a binder: + * - on destroying glide instance to clearup listeners + */ + Events.on('destroy', function () { + Binder.destroy(); + }); + + return Autoplay; +} + +/** + * Sorts keys of breakpoint object so they will be ordered from lower to bigger. + * + * @param {Object} points + * @returns {Object} + */ +function sortBreakpoints(points) { + if (isObject(points)) { + return sortKeys(points); + } else { + warn('Breakpoints option must be an object'); + } + + return {}; +} + +function Breakpoints(Glide, Components, Events) { + /** + * Instance of the binder for DOM Events. + * + * @type {EventsBinder} + */ + var Binder = new EventsBinder(); + + /** + * Holds reference to settings. + * + * @type {Object} + */ + var settings = Glide.settings; + + /** + * Holds reference to breakpoints object in settings. Sorts breakpoints + * from smaller to larger. It is required in order to proper + * matching currently active breakpoint settings. + * + * @type {Object} + */ + var points = sortBreakpoints(settings.breakpoints); + + /** + * Cache initial settings before overwritting. + * + * @type {Object} + */ + var defaults = _extends({}, settings); + + var Breakpoints = { + /** + * Matches settings for currectly matching media breakpoint. + * + * @param {Object} points + * @returns {Object} + */ + match: function match(points) { + if (typeof window.matchMedia !== 'undefined') { + for (var point in points) { + if (points.hasOwnProperty(point)) { + if (window.matchMedia('(max-width: ' + point + 'px)').matches) { + return points[point]; + } + } + } + } + + return defaults; + } + }; + + /** + * Overwrite instance settings with currently matching breakpoint settings. + * This happens right after component initialization. + */ + _extends(settings, Breakpoints.match(points)); + + /** + * Update glide with settings of matched brekpoint: + * - window resize to update slider + */ + Binder.on('resize', window, throttle(function () { + Glide.settings = mergeOptions(settings, Breakpoints.match(points)); + }, Glide.settings.throttle)); + + /** + * Resort and update default settings: + * - on reinit via API, so breakpoint matching will be performed with options + */ + Events.on('update', function () { + points = sortBreakpoints(points); + + defaults = _extends({}, settings); + }); + + /** + * Unbind resize listener: + * - on destroying, to bring markup to its initial state + */ + Events.on('destroy', function () { + Binder.off('resize', window); + }); + + return Breakpoints; +} + +var COMPONENTS = { + // Required + Html: Html, + Translate: Translate, + Transition: Transition, + Direction: Direction, + Peek: Peek, + Sizes: Sizes, + Gaps: Gaps, + Move: Move, + Clones: Clones, + Resize: Resize, + Build: Build, + Run: Run, + + // Optional + Swipe: Swipe, + Images: Images, + Anchors: Anchors, + Controls: Controls, + Keyboard: Keyboard, + Autoplay: Autoplay, + Breakpoints: Breakpoints +}; + +var Glide$1$1 = function (_Core) { + inherits(Glide$$1, _Core); + + function Glide$$1() { + classCallCheck(this, Glide$$1); + return possibleConstructorReturn(this, (Glide$$1.__proto__ || Object.getPrototypeOf(Glide$$1)).apply(this, arguments)); + } + + createClass(Glide$$1, [{ + key: 'mount', + value: function mount() { + var extensions = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; + + return get(Glide$$1.prototype.__proto__ || Object.getPrototypeOf(Glide$$1.prototype), 'mount', this).call(this, _extends({}, COMPONENTS, extensions)); + } + }]); + return Glide$$1; +}(Glide$1); + +var Events = ['mount.before', 'mount.after', 'update', 'play', 'pause', 'build.before', 'build.after', 'run.before', 'run', 'run.after', 'run.offset', 'run.start', 'run.end', 'move', 'move.after', 'resize', 'swipe.start', 'swipe.move', 'swipe.end', 'translate.jump']; + +(function () { + if (typeof document !== 'undefined') { + var head = document.head || document.getElementsByTagName('head')[0], + style = document.createElement('style'), + css = "";style.type = 'text/css';if (style.styleSheet) { + style.styleSheet.cssText = css; + } else { + style.appendChild(document.createTextNode(css)); + }head.appendChild(style); + } +})(); + +var Glide = { render: function render() { + var _vm = this;var _h = _vm.$createElement;var _c = _vm._self._c || _h;return _c('div', { staticClass: "glide" }, [_vm._t("default")], 2); + }, staticRenderFns: [], + name: 'Glide', + + props: { + settings: { + type: Object, + default: function _default() {} + }, + delayInitialization: { + type: Boolean, + default: false + } + }, + + methods: { + go: function go(pattern) { + this.glide.go(pattern); + }, + init: function init() { + this.glide.mount(); + } + }, + + data: function data() { + return { + glide: undefined + }; + }, + mounted: function mounted() { + var _this = this; + + this.glide = new Glide$1$1(this.$el, this.settings); + + Events.forEach(function (event) { + _this.glide.on(event, function () { + for (var _len = arguments.length, parameters = Array(_len), _key = 0; _key < _len; _key++) { + parameters[_key] = arguments[_key]; + } + + var emmiter = event.replace(/\.([a-z])/g, function (m, w) { + return w.toUpperCase(); + }); + + _this.$emit.apply(_this, [emmiter].concat(parameters)); + }); + }); + + if (!this.delayInitialization) { + this.init(); + } + } +}; + +(function () { + if (typeof document !== 'undefined') { + var head = document.head || document.getElementsByTagName('head')[0], + style = document.createElement('style'), + css = "";style.type = 'text/css';if (style.styleSheet) { + style.styleSheet.cssText = css; + } else { + style.appendChild(document.createTextNode(css)); + }head.appendChild(style); + } +})(); + +var Track = { render: function render() { + var _vm = this;var _h = _vm.$createElement;var _c = _vm._self._c || _h;return _c('div', { staticClass: "glide__track", attrs: { "data-glide-el": "track" } }, [_c('ul', { staticClass: "glide__slides" }, [_vm._t("default")], 2)]); + }, staticRenderFns: [], + name: 'GlideTrack' +}; + +(function () { + if (typeof document !== 'undefined') { + var head = document.head || document.getElementsByTagName('head')[0], + style = document.createElement('style'), + css = "";style.type = 'text/css';if (style.styleSheet) { + style.styleSheet.cssText = css; + } else { + style.appendChild(document.createTextNode(css)); + }head.appendChild(style); + } +})(); + +var Slide = { render: function render() { + var _vm = this;var _h = _vm.$createElement;var _c = _vm._self._c || _h;return _c('li', { staticClass: "glide__slide" }, [_vm._t("default")], 2); + }, staticRenderFns: [], + name: 'GlideSlide' +}; + +var index = { + install: function install(Vue, options) { + Vue.component(Glide.name, Glide); + Vue.component(Track.name, Track); + Vue.component(Slide.name, Slide); + } +}; + +export default index; diff --git a/dist/vue-glide.js b/dist/vue-glide.js new file mode 100644 index 0000000..225b677 --- /dev/null +++ b/dist/vue-glide.js @@ -0,0 +1,3706 @@ +/*! + * VueGlide.js v3.1.0 + * (c) 2017-2019 jedrzejchalubek (https://jedrzejchalubek.com) + * Released under the MIT License. + */ + +(function (global, factory) { + typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : + typeof define === 'function' && define.amd ? define(factory) : + (global.VueGlide = factory()); +}(this, (function () { 'use strict'; + +var _typeof2 = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; + +/*! + * Glide.js v3.2.6 + * (c) 2013-2019 Jędrzej Chałubek (http://jedrzejchalubek.com/) + * Released under the MIT License. + */ + +var defaults = { + /** + * Type of the movement. + * + * Available types: + * `slider` - Rewinds slider to the start/end when it reaches the first or last slide. + * `carousel` - Changes slides without starting over when it reaches the first or last slide. + * + * @type {String} + */ + type: 'slider', + + /** + * Start at specific slide number defined with zero-based index. + * + * @type {Number} + */ + startAt: 0, + + /** + * A number of slides visible on the single viewport. + * + * @type {Number} + */ + perView: 1, + + /** + * Focus currently active slide at a specified position in the track. + * + * Available inputs: + * `center` - Current slide will be always focused at the center of a track. + * `0,1,2,3...` - Current slide will be focused on the specified zero-based index. + * + * @type {String|Number} + */ + focusAt: 0, + + /** + * A size of the gap added between slides. + * + * @type {Number} + */ + gap: 10, + + /** + * Change slides after a specified interval. Use `false` for turning off autoplay. + * + * @type {Number|Boolean} + */ + autoplay: false, + + /** + * Stop autoplay on mouseover event. + * + * @type {Boolean} + */ + hoverpause: true, + + /** + * Allow for changing slides with left and right keyboard arrows. + * + * @type {Boolean} + */ + keyboard: true, + + /** + * Stop running `perView` number of slides from the end. Use this + * option if you don't want to have an empty space after + * a slider. Works only with `slider` type and a + * non-centered `focusAt` setting. + * + * @type {Boolean} + */ + bound: false, + + /** + * Minimal swipe distance needed to change the slide. Use `false` for turning off a swiping. + * + * @type {Number|Boolean} + */ + swipeThreshold: 80, + + /** + * Minimal mouse drag distance needed to change the slide. Use `false` for turning off a dragging. + * + * @type {Number|Boolean} + */ + dragThreshold: 120, + + /** + * A maximum number of slides to which movement will be made on swiping or dragging. Use `false` for unlimited. + * + * @type {Number|Boolean} + */ + perTouch: false, + + /** + * Moving distance ratio of the slides on a swiping and dragging. + * + * @type {Number} + */ + touchRatio: 0.5, + + /** + * Angle required to activate slides moving on swiping or dragging. + * + * @type {Number} + */ + touchAngle: 45, + + /** + * Duration of the animation in milliseconds. + * + * @type {Number} + */ + animationDuration: 400, + + /** + * Allows looping the `slider` type. Slider will rewind to the first/last slide when it's at the start/end. + * + * @type {Boolean} + */ + rewind: true, + + /** + * Duration of the rewinding animation of the `slider` type in milliseconds. + * + * @type {Number} + */ + rewindDuration: 800, + + /** + * Easing function for the animation. + * + * @type {String} + */ + animationTimingFunc: 'cubic-bezier(.165, .840, .440, 1)', + + /** + * Throttle costly events at most once per every wait milliseconds. + * + * @type {Number} + */ + throttle: 10, + + /** + * Moving direction mode. + * + * Available inputs: + * - 'ltr' - left to right movement, + * - 'rtl' - right to left movement. + * + * @type {String} + */ + direction: 'ltr', + + /** + * The distance value of the next and previous viewports which + * have to peek in the current view. Accepts number and + * pixels as a string. Left and right peeking can be + * set up separately with a directions object. + * + * For example: + * `100` - Peek 100px on the both sides. + * { before: 100, after: 50 }` - Peek 100px on the left side and 50px on the right side. + * + * @type {Number|String|Object} + */ + peek: 0, + + /** + * Collection of options applied at specified media breakpoints. + * For example: display two slides per view under 800px. + * `{ + * '800px': { + * perView: 2 + * } + * }` + */ + breakpoints: {}, + + /** + * Collection of internally used HTML classes. + * + * @todo Refactor `slider` and `carousel` properties to single `type: { slider: '', carousel: '' }` object + * @type {Object} + */ + classes: { + direction: { + ltr: 'glide--ltr', + rtl: 'glide--rtl' + }, + slider: 'glide--slider', + carousel: 'glide--carousel', + swipeable: 'glide--swipeable', + dragging: 'glide--dragging', + cloneSlide: 'glide__slide--clone', + activeNav: 'glide__bullet--active', + activeSlide: 'glide__slide--active', + disabledArrow: 'glide__arrow--disabled' + } +}; + +/** + * Outputs warning message to the bowser console. + * + * @param {String} msg + * @return {Void} + */ +function warn(msg) { + console.error("[Glide warn]: " + msg); +} + +var _typeof = typeof Symbol === "function" && _typeof2(Symbol.iterator) === "symbol" ? function (obj) { + return typeof obj === 'undefined' ? 'undefined' : _typeof2(obj); +} : function (obj) { + return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj === 'undefined' ? 'undefined' : _typeof2(obj); +}; + +var classCallCheck = function classCallCheck(instance, Constructor) { + if (!(instance instanceof Constructor)) { + throw new TypeError("Cannot call a class as a function"); + } +}; + +var createClass = function () { + function defineProperties(target, props) { + for (var i = 0; i < props.length; i++) { + var descriptor = props[i]; + descriptor.enumerable = descriptor.enumerable || false; + descriptor.configurable = true; + if ("value" in descriptor) descriptor.writable = true; + Object.defineProperty(target, descriptor.key, descriptor); + } + } + + return function (Constructor, protoProps, staticProps) { + if (protoProps) defineProperties(Constructor.prototype, protoProps); + if (staticProps) defineProperties(Constructor, staticProps); + return Constructor; + }; +}(); + +var _extends = Object.assign || function (target) { + for (var i = 1; i < arguments.length; i++) { + var source = arguments[i]; + + for (var key in source) { + if (Object.prototype.hasOwnProperty.call(source, key)) { + target[key] = source[key]; + } + } + } + + return target; +}; + +var get = function get(object, property, receiver) { + if (object === null) object = Function.prototype; + var desc = Object.getOwnPropertyDescriptor(object, property); + + if (desc === undefined) { + var parent = Object.getPrototypeOf(object); + + if (parent === null) { + return undefined; + } else { + return get(parent, property, receiver); + } + } else if ("value" in desc) { + return desc.value; + } else { + var getter = desc.get; + + if (getter === undefined) { + return undefined; + } + + return getter.call(receiver); + } +}; + +var inherits = function inherits(subClass, superClass) { + if (typeof superClass !== "function" && superClass !== null) { + throw new TypeError("Super expression must either be null or a function, not " + (typeof superClass === 'undefined' ? 'undefined' : _typeof2(superClass))); + } + + subClass.prototype = Object.create(superClass && superClass.prototype, { + constructor: { + value: subClass, + enumerable: false, + writable: true, + configurable: true + } + }); + if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; +}; + +var possibleConstructorReturn = function possibleConstructorReturn(self, call) { + if (!self) { + throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); + } + + return call && ((typeof call === 'undefined' ? 'undefined' : _typeof2(call)) === "object" || typeof call === "function") ? call : self; +}; + +/** + * Converts value entered as number + * or string to integer value. + * + * @param {String} value + * @returns {Number} + */ +function toInt(value) { + return parseInt(value); +} + +/** + * Converts value entered as number + * or string to flat value. + * + * @param {String} value + * @returns {Number} + */ +function toFloat(value) { + return parseFloat(value); +} + +/** + * Indicates whether the specified value is a string. + * + * @param {*} value + * @return {Boolean} + */ +function isString(value) { + return typeof value === 'string'; +} + +/** + * Indicates whether the specified value is an object. + * + * @param {*} value + * @return {Boolean} + * + * @see https://github.com/jashkenas/underscore + */ +function isObject(value) { + var type = typeof value === 'undefined' ? 'undefined' : _typeof(value); + + return type === 'function' || type === 'object' && !!value; // eslint-disable-line no-mixed-operators +} + +/** + * Indicates whether the specified value is a number. + * + * @param {*} value + * @return {Boolean} + */ +function isNumber(value) { + return typeof value === 'number'; +} + +/** + * Indicates whether the specified value is a function. + * + * @param {*} value + * @return {Boolean} + */ +function isFunction(value) { + return typeof value === 'function'; +} + +/** + * Indicates whether the specified value is undefined. + * + * @param {*} value + * @return {Boolean} + */ +function isUndefined(value) { + return typeof value === 'undefined'; +} + +/** + * Indicates whether the specified value is an array. + * + * @param {*} value + * @return {Boolean} + */ +function isArray(value) { + return value.constructor === Array; +} + +/** + * Creates and initializes specified collection of extensions. + * Each extension receives access to instance of glide and rest of components. + * + * @param {Object} glide + * @param {Object} extensions + * + * @returns {Object} + */ +function mount(glide, extensions, events) { + var components = {}; + + for (var name in extensions) { + if (isFunction(extensions[name])) { + components[name] = extensions[name](glide, components, events); + } else { + warn('Extension must be a function'); + } + } + + for (var _name in components) { + if (isFunction(components[_name].mount)) { + components[_name].mount(); + } + } + + return components; +} + +/** + * Defines getter and setter property on the specified object. + * + * @param {Object} obj Object where property has to be defined. + * @param {String} prop Name of the defined property. + * @param {Object} definition Get and set definitions for the property. + * @return {Void} + */ +function define(obj, prop, definition) { + Object.defineProperty(obj, prop, definition); +} + +/** + * Sorts aphabetically object keys. + * + * @param {Object} obj + * @return {Object} + */ +function sortKeys(obj) { + return Object.keys(obj).sort().reduce(function (r, k) { + r[k] = obj[k]; + + return r[k], r; + }, {}); +} + +/** + * Merges passed settings object with default options. + * + * @param {Object} defaults + * @param {Object} settings + * @return {Object} + */ +function mergeOptions(defaults, settings) { + var options = _extends({}, defaults, settings); + + // `Object.assign` do not deeply merge objects, so we + // have to do it manually for every nested object + // in options. Although it does not look smart, + // it's smaller and faster than some fancy + // merging deep-merge algorithm script. + if (settings.hasOwnProperty('classes')) { + options.classes = _extends({}, defaults.classes, settings.classes); + + if (settings.classes.hasOwnProperty('direction')) { + options.classes.direction = _extends({}, defaults.classes.direction, settings.classes.direction); + } + } + + if (settings.hasOwnProperty('breakpoints')) { + options.breakpoints = _extends({}, defaults.breakpoints, settings.breakpoints); + } + + return options; +} + +var EventsBus = function () { + /** + * Construct a EventBus instance. + * + * @param {Object} events + */ + function EventsBus() { + var events = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; + classCallCheck(this, EventsBus); + + this.events = events; + this.hop = events.hasOwnProperty; + } + + /** + * Adds listener to the specifed event. + * + * @param {String|Array} event + * @param {Function} handler + */ + + createClass(EventsBus, [{ + key: 'on', + value: function on(event, handler) { + if (isArray(event)) { + for (var i = 0; i < event.length; i++) { + this.on(event[i], handler); + } + } + + // Create the event's object if not yet created + if (!this.hop.call(this.events, event)) { + this.events[event] = []; + } + + // Add the handler to queue + var index = this.events[event].push(handler) - 1; + + // Provide handle back for removal of event + return { + remove: function remove() { + delete this.events[event][index]; + } + }; + } + + /** + * Runs registered handlers for specified event. + * + * @param {String|Array} event + * @param {Object=} context + */ + + }, { + key: 'emit', + value: function emit(event, context) { + if (isArray(event)) { + for (var i = 0; i < event.length; i++) { + this.emit(event[i], context); + } + } + + // If the event doesn't exist, or there's no handlers in queue, just leave + if (!this.hop.call(this.events, event)) { + return; + } + + // Cycle through events queue, fire! + this.events[event].forEach(function (item) { + item(context || {}); + }); + } + }]); + return EventsBus; +}(); + +var Glide$1 = function () { + /** + * Construct glide. + * + * @param {String} selector + * @param {Object} options + */ + function Glide(selector) { + var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; + classCallCheck(this, Glide); + + this._c = {}; + this._t = []; + this._e = new EventsBus(); + + this.disabled = false; + this.selector = selector; + this.settings = mergeOptions(defaults, options); + this.index = this.settings.startAt; + } + + /** + * Initializes glide. + * + * @param {Object} extensions Collection of extensions to initialize. + * @return {Glide} + */ + + createClass(Glide, [{ + key: 'mount', + value: function mount$$1() { + var extensions = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; + + this._e.emit('mount.before'); + + if (isObject(extensions)) { + this._c = mount(this, extensions, this._e); + } else { + warn('You need to provide a object on `mount()`'); + } + + this._e.emit('mount.after'); + + return this; + } + + /** + * Collects an instance `translate` transformers. + * + * @param {Array} transformers Collection of transformers. + * @return {Void} + */ + + }, { + key: 'mutate', + value: function mutate() { + var transformers = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : []; + + if (isArray(transformers)) { + this._t = transformers; + } else { + warn('You need to provide a array on `mutate()`'); + } + + return this; + } + + /** + * Updates glide with specified settings. + * + * @param {Object} settings + * @return {Glide} + */ + + }, { + key: 'update', + value: function update() { + var settings = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; + + this.settings = mergeOptions(this.settings, settings); + + if (settings.hasOwnProperty('startAt')) { + this.index = settings.startAt; + } + + this._e.emit('update'); + + return this; + } + + /** + * Change slide with specified pattern. A pattern must be in the special format: + * `>` - Move one forward + * `<` - Move one backward + * `={i}` - Go to {i} zero-based slide (eq. '=1', will go to second slide) + * `>>` - Rewinds to end (last slide) + * `<<` - Rewinds to start (first slide) + * + * @param {String} pattern + * @return {Glide} + */ + + }, { + key: 'go', + value: function go(pattern) { + this._c.Run.make(pattern); + + return this; + } + + /** + * Move track by specified distance. + * + * @param {String} distance + * @return {Glide} + */ + + }, { + key: 'move', + value: function move(distance) { + this._c.Transition.disable(); + this._c.Move.make(distance); + + return this; + } + + /** + * Destroy instance and revert all changes done by this._c. + * + * @return {Glide} + */ + + }, { + key: 'destroy', + value: function destroy() { + this._e.emit('destroy'); + + return this; + } + + /** + * Start instance autoplaying. + * + * @param {Boolean|Number} interval Run autoplaying with passed interval regardless of `autoplay` settings + * @return {Glide} + */ + + }, { + key: 'play', + value: function play() { + var interval = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false; + + if (interval) { + this.settings.autoplay = interval; + } + + this._e.emit('play'); + + return this; + } + + /** + * Stop instance autoplaying. + * + * @return {Glide} + */ + + }, { + key: 'pause', + value: function pause() { + this._e.emit('pause'); + + return this; + } + + /** + * Sets glide into a idle status. + * + * @return {Glide} + */ + + }, { + key: 'disable', + value: function disable() { + this.disabled = true; + + return this; + } + + /** + * Sets glide into a active status. + * + * @return {Glide} + */ + + }, { + key: 'enable', + value: function enable() { + this.disabled = false; + + return this; + } + + /** + * Adds cuutom event listener with handler. + * + * @param {String|Array} event + * @param {Function} handler + * @return {Glide} + */ + + }, { + key: 'on', + value: function on(event, handler) { + this._e.on(event, handler); + + return this; + } + + /** + * Checks if glide is a precised type. + * + * @param {String} name + * @return {Boolean} + */ + + }, { + key: 'isType', + value: function isType(name) { + return this.settings.type === name; + } + + /** + * Gets value of the core options. + * + * @return {Object} + */ + + }, { + key: 'settings', + get: function get$$1() { + return this._o; + } + + /** + * Sets value of the core options. + * + * @param {Object} o + * @return {Void} + */ + + , set: function set$$1(o) { + if (isObject(o)) { + this._o = o; + } else { + warn('Options must be an `object` instance.'); + } + } + + /** + * Gets current index of the slider. + * + * @return {Object} + */ + + }, { + key: 'index', + get: function get$$1() { + return this._i; + } + + /** + * Sets current index a slider. + * + * @return {Object} + */ + + , set: function set$$1(i) { + this._i = toInt(i); + } + + /** + * Gets type name of the slider. + * + * @return {String} + */ + + }, { + key: 'type', + get: function get$$1() { + return this.settings.type; + } + + /** + * Gets value of the idle status. + * + * @return {Boolean} + */ + + }, { + key: 'disabled', + get: function get$$1() { + return this._d; + } + + /** + * Sets value of the idle status. + * + * @return {Boolean} + */ + + , set: function set$$1(status) { + this._d = !!status; + } + }]); + return Glide; +}(); + +function Run(Glide, Components, Events) { + var Run = { + /** + * Initializes autorunning of the glide. + * + * @return {Void} + */ + mount: function mount() { + this._o = false; + }, + + /** + * Makes glides running based on the passed moving schema. + * + * @param {String} move + */ + make: function make(move) { + var _this = this; + + if (!Glide.disabled) { + Glide.disable(); + + this.move = move; + + Events.emit('run.before', this.move); + + this.calculate(); + + Events.emit('run', this.move); + + Components.Transition.after(function () { + if (_this.isOffset('<') || _this.isOffset('>')) { + _this._o = false; + + Events.emit('run.offset', _this.move); + } + + Events.emit('run.after', _this.move); + + Glide.enable(); + }); + } + }, + + /** + * Calculates current index based on defined move. + * + * @return {Void} + */ + calculate: function calculate() { + var move = this.move, + length = this.length; + var steps = move.steps, + direction = move.direction; + + var countableSteps = isNumber(toInt(steps)) && toInt(steps) !== 0; + + switch (direction) { + case '>': + if (steps === '>') { + Glide.index = length; + } else if (this.isEnd()) { + if (!(Glide.isType('slider') && !Glide.settings.rewind)) { + this._o = true; + + Glide.index = 0; + } + + Events.emit('run.end', move); + } else if (countableSteps) { + Glide.index += Math.min(length - Glide.index, -toInt(steps)); + } else { + Glide.index++; + } + break; + + case '<': + if (steps === '<') { + Glide.index = 0; + } else if (this.isStart()) { + if (!(Glide.isType('slider') && !Glide.settings.rewind)) { + this._o = true; + + Glide.index = length; + } + + Events.emit('run.start', move); + } else if (countableSteps) { + Glide.index -= Math.min(Glide.index, toInt(steps)); + } else { + Glide.index--; + } + break; + + case '=': + Glide.index = steps; + break; + } + }, + + /** + * Checks if we are on the first slide. + * + * @return {Boolean} + */ + isStart: function isStart() { + return Glide.index === 0; + }, + + /** + * Checks if we are on the last slide. + * + * @return {Boolean} + */ + isEnd: function isEnd() { + return Glide.index === this.length; + }, + + /** + * Checks if we are making a offset run. + * + * @param {String} direction + * @return {Boolean} + */ + isOffset: function isOffset(direction) { + return this._o && this.move.direction === direction; + } + }; + + define(Run, 'move', { + /** + * Gets value of the move schema. + * + * @returns {Object} + */ + get: function get() { + return this._m; + }, + + /** + * Sets value of the move schema. + * + * @returns {Object} + */ + set: function set(value) { + this._m = { + direction: value.substr(0, 1), + steps: value.substr(1) ? value.substr(1) : 0 + }; + } + }); + + define(Run, 'length', { + /** + * Gets value of the running distance based + * on zero-indexing number of slides. + * + * @return {Number} + */ + get: function get() { + var settings = Glide.settings; + var length = Components.Html.slides.length; + + // If the `bound` option is acitve, a maximum running distance should be + // reduced by `perView` and `focusAt` settings. Running distance + // should end before creating an empty space after instance. + + if (Glide.isType('slider') && settings.focusAt !== 'center' && settings.bound) { + return length - 1 - (toInt(settings.perView) - 1) + toInt(settings.focusAt); + } + + return length - 1; + } + }); + + define(Run, 'offset', { + /** + * Gets status of the offsetting flag. + * + * @return {Boolean} + */ + get: function get() { + return this._o; + } + }); + + return Run; +} + +/** + * Returns a current time. + * + * @return {Number} + */ +function now() { + return new Date().getTime(); +} + +/** + * Returns a function, that, when invoked, will only be triggered + * at most once during a given window of time. + * + * @param {Function} func + * @param {Number} wait + * @param {Object=} options + * @return {Function} + * + * @see https://github.com/jashkenas/underscore + */ +function throttle(func, wait, options) { + var timeout = void 0, + context = void 0, + args = void 0, + result = void 0; + var previous = 0; + if (!options) options = {}; + + var later = function later() { + previous = options.leading === false ? 0 : now(); + timeout = null; + result = func.apply(context, args); + if (!timeout) context = args = null; + }; + + var throttled = function throttled() { + var at = now(); + if (!previous && options.leading === false) previous = at; + var remaining = wait - (at - previous); + context = this; + args = arguments; + if (remaining <= 0 || remaining > wait) { + if (timeout) { + clearTimeout(timeout); + timeout = null; + } + previous = at; + result = func.apply(context, args); + if (!timeout) context = args = null; + } else if (!timeout && options.trailing !== false) { + timeout = setTimeout(later, remaining); + } + return result; + }; + + throttled.cancel = function () { + clearTimeout(timeout); + previous = 0; + timeout = context = args = null; + }; + + return throttled; +} + +var MARGIN_TYPE = { + ltr: ['marginLeft', 'marginRight'], + rtl: ['marginRight', 'marginLeft'] +}; + +function Gaps(Glide, Components, Events) { + var Gaps = { + /** + * Applies gaps between slides. First and last + * slides do not receive it's edge margins. + * + * @param {HTMLCollection} slides + * @return {Void} + */ + apply: function apply(slides) { + for (var i = 0, len = slides.length; i < len; i++) { + var style = slides[i].style; + var direction = Components.Direction.value; + + if (i !== 0) { + style[MARGIN_TYPE[direction][0]] = this.value / 2 + 'px'; + } else { + style[MARGIN_TYPE[direction][0]] = ''; + } + + if (i !== slides.length - 1) { + style[MARGIN_TYPE[direction][1]] = this.value / 2 + 'px'; + } else { + style[MARGIN_TYPE[direction][1]] = ''; + } + } + }, + + /** + * Removes gaps from the slides. + * + * @param {HTMLCollection} slides + * @returns {Void} + */ + remove: function remove(slides) { + for (var i = 0, len = slides.length; i < len; i++) { + var style = slides[i].style; + + style.marginLeft = ''; + style.marginRight = ''; + } + } + }; + + define(Gaps, 'value', { + /** + * Gets value of the gap. + * + * @returns {Number} + */ + get: function get() { + return toInt(Glide.settings.gap); + } + }); + + define(Gaps, 'grow', { + /** + * Gets additional dimentions value caused by gaps. + * Used to increase width of the slides wrapper. + * + * @returns {Number} + */ + get: function get() { + return Gaps.value * (Components.Sizes.length - 1); + } + }); + + define(Gaps, 'reductor', { + /** + * Gets reduction value caused by gaps. + * Used to subtract width of the slides. + * + * @returns {Number} + */ + get: function get() { + var perView = Glide.settings.perView; + + return Gaps.value * (perView - 1) / perView; + } + }); + + /** + * Apply calculated gaps: + * - after building, so slides (including clones) will receive proper margins + * - on updating via API, to recalculate gaps with new options + */ + Events.on(['build.after', 'update'], throttle(function () { + Gaps.apply(Components.Html.wrapper.children); + }, 30)); + + /** + * Remove gaps: + * - on destroying to bring markup to its inital state + */ + Events.on('destroy', function () { + Gaps.remove(Components.Html.wrapper.children); + }); + + return Gaps; +} + +/** + * Finds siblings nodes of the passed node. + * + * @param {Element} node + * @return {Array} + */ +function siblings(node) { + if (node && node.parentNode) { + var n = node.parentNode.firstChild; + var matched = []; + + for (; n; n = n.nextSibling) { + if (n.nodeType === 1 && n !== node) { + matched.push(n); + } + } + + return matched; + } + + return []; +} + +/** + * Checks if passed node exist and is a valid element. + * + * @param {Element} node + * @return {Boolean} + */ +function exist(node) { + if (node && node instanceof window.HTMLElement) { + return true; + } + + return false; +} + +var TRACK_SELECTOR = '[data-glide-el="track"]'; + +function Html(Glide, Components) { + var Html = { + /** + * Setup slider HTML nodes. + * + * @param {Glide} glide + */ + mount: function mount() { + this.root = Glide.selector; + this.track = this.root.querySelector(TRACK_SELECTOR); + this.slides = Array.prototype.slice.call(this.wrapper.children).filter(function (slide) { + return !slide.classList.contains(Glide.settings.classes.cloneSlide); + }); + } + }; + + define(Html, 'root', { + /** + * Gets node of the glide main element. + * + * @return {Object} + */ + get: function get() { + return Html._r; + }, + + /** + * Sets node of the glide main element. + * + * @return {Object} + */ + set: function set(r) { + if (isString(r)) { + r = document.querySelector(r); + } + + if (exist(r)) { + Html._r = r; + } else { + warn('Root element must be a existing Html node'); + } + } + }); + + define(Html, 'track', { + /** + * Gets node of the glide track with slides. + * + * @return {Object} + */ + get: function get() { + return Html._t; + }, + + /** + * Sets node of the glide track with slides. + * + * @return {Object} + */ + set: function set(t) { + if (exist(t)) { + Html._t = t; + } else { + warn('Could not find track element. Please use ' + TRACK_SELECTOR + ' attribute.'); + } + } + }); + + define(Html, 'wrapper', { + /** + * Gets node of the slides wrapper. + * + * @return {Object} + */ + get: function get() { + return Html.track.children[0]; + } + }); + + return Html; +} + +function Peek(Glide, Components, Events) { + var Peek = { + /** + * Setups how much to peek based on settings. + * + * @return {Void} + */ + mount: function mount() { + this.value = Glide.settings.peek; + } + }; + + define(Peek, 'value', { + /** + * Gets value of the peek. + * + * @returns {Number|Object} + */ + get: function get() { + return Peek._v; + }, + + /** + * Sets value of the peek. + * + * @param {Number|Object} value + * @return {Void} + */ + set: function set(value) { + if (isObject(value)) { + value.before = toInt(value.before); + value.after = toInt(value.after); + } else { + value = toInt(value); + } + + Peek._v = value; + } + }); + + define(Peek, 'reductor', { + /** + * Gets reduction value caused by peek. + * + * @returns {Number} + */ + get: function get() { + var value = Peek.value; + var perView = Glide.settings.perView; + + if (isObject(value)) { + return value.before / perView + value.after / perView; + } + + return value * 2 / perView; + } + }); + + /** + * Recalculate peeking sizes on: + * - when resizing window to update to proper percents + */ + Events.on(['resize', 'update'], function () { + Peek.mount(); + }); + + return Peek; +} + +function Move(Glide, Components, Events) { + var Move = { + /** + * Constructs move component. + * + * @returns {Void} + */ + mount: function mount() { + this._o = 0; + }, + + /** + * Calculates a movement value based on passed offset and currently active index. + * + * @param {Number} offset + * @return {Void} + */ + make: function make() { + var _this = this; + + var offset = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 0; + + this.offset = offset; + + Events.emit('move', { + movement: this.value + }); + + Components.Transition.after(function () { + Events.emit('move.after', { + movement: _this.value + }); + }); + } + }; + + define(Move, 'offset', { + /** + * Gets an offset value used to modify current translate. + * + * @return {Object} + */ + get: function get() { + return Move._o; + }, + + /** + * Sets an offset value used to modify current translate. + * + * @return {Object} + */ + set: function set(value) { + Move._o = !isUndefined(value) ? toInt(value) : 0; + } + }); + + define(Move, 'translate', { + /** + * Gets a raw movement value. + * + * @return {Number} + */ + get: function get() { + return Components.Sizes.slideWidth * Glide.index; + } + }); + + define(Move, 'value', { + /** + * Gets an actual movement value corrected by offset. + * + * @return {Number} + */ + get: function get() { + var offset = this.offset; + var translate = this.translate; + + if (Components.Direction.is('rtl')) { + return translate + offset; + } + + return translate - offset; + } + }); + + /** + * Make movement to proper slide on: + * - before build, so glide will start at `startAt` index + * - on each standard run to move to newly calculated index + */ + Events.on(['build.before', 'run'], function () { + Move.make(); + }); + + return Move; +} + +function Sizes(Glide, Components, Events) { + var Sizes = { + /** + * Setups dimentions of slides. + * + * @return {Void} + */ + setupSlides: function setupSlides() { + var width = this.slideWidth + 'px'; + var slides = Components.Html.slides; + + for (var i = 0; i < slides.length; i++) { + slides[i].style.width = width; + } + }, + + /** + * Setups dimentions of slides wrapper. + * + * @return {Void} + */ + setupWrapper: function setupWrapper(dimention) { + Components.Html.wrapper.style.width = this.wrapperSize + 'px'; + }, + + /** + * Removes applied styles from HTML elements. + * + * @returns {Void} + */ + remove: function remove() { + var slides = Components.Html.slides; + + for (var i = 0; i < slides.length; i++) { + slides[i].style.width = ''; + } + + Components.Html.wrapper.style.width = ''; + } + }; + + define(Sizes, 'length', { + /** + * Gets count number of the slides. + * + * @return {Number} + */ + get: function get() { + return Components.Html.slides.length; + } + }); + + define(Sizes, 'width', { + /** + * Gets width value of the glide. + * + * @return {Number} + */ + get: function get() { + return Components.Html.root.offsetWidth; + } + }); + + define(Sizes, 'wrapperSize', { + /** + * Gets size of the slides wrapper. + * + * @return {Number} + */ + get: function get() { + return Sizes.slideWidth * Sizes.length + Components.Gaps.grow + Components.Clones.grow; + } + }); + + define(Sizes, 'slideWidth', { + /** + * Gets width value of the single slide. + * + * @return {Number} + */ + get: function get() { + return Sizes.width / Glide.settings.perView - Components.Peek.reductor - Components.Gaps.reductor; + } + }); + + /** + * Apply calculated glide's dimensions: + * - before building, so other dimentions (e.g. translate) will be calculated propertly + * - when resizing window to recalculate sildes dimensions + * - on updating via API, to calculate dimensions based on new options + */ + Events.on(['build.before', 'resize', 'update'], function () { + Sizes.setupSlides(); + Sizes.setupWrapper(); + }); + + /** + * Remove calculated glide's dimensions: + * - on destoting to bring markup to its inital state + */ + Events.on('destroy', function () { + Sizes.remove(); + }); + + return Sizes; +} + +function Build(Glide, Components, Events) { + var Build = { + /** + * Init glide building. Adds classes, sets + * dimensions and setups initial state. + * + * @return {Void} + */ + mount: function mount() { + Events.emit('build.before'); + + this.typeClass(); + this.activeClass(); + + Events.emit('build.after'); + }, + + /** + * Adds `type` class to the glide element. + * + * @return {Void} + */ + typeClass: function typeClass() { + Components.Html.root.classList.add(Glide.settings.classes[Glide.settings.type]); + }, + + /** + * Sets active class to current slide. + * + * @return {Void} + */ + activeClass: function activeClass() { + var classes = Glide.settings.classes; + var slide = Components.Html.slides[Glide.index]; + + if (slide) { + slide.classList.add(classes.activeSlide); + + siblings(slide).forEach(function (sibling) { + sibling.classList.remove(classes.activeSlide); + }); + } + }, + + /** + * Removes HTML classes applied at building. + * + * @return {Void} + */ + removeClasses: function removeClasses() { + var classes = Glide.settings.classes; + + Components.Html.root.classList.remove(classes[Glide.settings.type]); + + Components.Html.slides.forEach(function (sibling) { + sibling.classList.remove(classes.activeSlide); + }); + } + }; + + /** + * Clear building classes: + * - on destroying to bring HTML to its initial state + * - on updating to remove classes before remounting component + */ + Events.on(['destroy', 'update'], function () { + Build.removeClasses(); + }); + + /** + * Remount component: + * - on resizing of the window to calculate new dimentions + * - on updating settings via API + */ + Events.on(['resize', 'update'], function () { + Build.mount(); + }); + + /** + * Swap active class of current slide: + * - after each move to the new index + */ + Events.on('move.after', function () { + Build.activeClass(); + }); + + return Build; +} + +function Clones(Glide, Components, Events) { + var Clones = { + /** + * Create pattern map and collect slides to be cloned. + */ + mount: function mount() { + this.items = []; + + if (Glide.isType('carousel')) { + this.items = this.collect(); + } + }, + + /** + * Collect clones with pattern. + * + * @return {Void} + */ + collect: function collect() { + var items = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : []; + var slides = Components.Html.slides; + var _Glide$settings = Glide.settings, + perView = _Glide$settings.perView, + classes = _Glide$settings.classes; + + var peekIncrementer = +!!Glide.settings.peek; + var part = perView + peekIncrementer; + var start = slides.slice(0, part); + var end = slides.slice(-part); + + for (var r = 0; r < Math.max(1, Math.floor(perView / slides.length)); r++) { + for (var i = 0; i < start.length; i++) { + var clone = start[i].cloneNode(true); + + clone.classList.add(classes.cloneSlide); + + items.push(clone); + } + + for (var _i = 0; _i < end.length; _i++) { + var _clone = end[_i].cloneNode(true); + + _clone.classList.add(classes.cloneSlide); + + items.unshift(_clone); + } + } + + return items; + }, + + /** + * Append cloned slides with generated pattern. + * + * @return {Void} + */ + append: function append() { + var items = this.items; + var _Components$Html = Components.Html, + wrapper = _Components$Html.wrapper, + slides = _Components$Html.slides; + + var half = Math.floor(items.length / 2); + var prepend = items.slice(0, half).reverse(); + var append = items.slice(half, items.length); + var width = Components.Sizes.slideWidth + 'px'; + + for (var i = 0; i < append.length; i++) { + wrapper.appendChild(append[i]); + } + + for (var _i2 = 0; _i2 < prepend.length; _i2++) { + wrapper.insertBefore(prepend[_i2], slides[0]); + } + + for (var _i3 = 0; _i3 < items.length; _i3++) { + items[_i3].style.width = width; + } + }, + + /** + * Remove all cloned slides. + * + * @return {Void} + */ + remove: function remove() { + var items = this.items; + + for (var i = 0; i < items.length; i++) { + Components.Html.wrapper.removeChild(items[i]); + } + } + }; + + define(Clones, 'grow', { + /** + * Gets additional dimentions value caused by clones. + * + * @return {Number} + */ + get: function get() { + return (Components.Sizes.slideWidth + Components.Gaps.value) * Clones.items.length; + } + }); + + /** + * Append additional slide's clones: + * - while glide's type is `carousel` + */ + Events.on('update', function () { + Clones.remove(); + Clones.mount(); + Clones.append(); + }); + + /** + * Append additional slide's clones: + * - while glide's type is `carousel` + */ + Events.on('build.before', function () { + if (Glide.isType('carousel')) { + Clones.append(); + } + }); + + /** + * Remove clones HTMLElements: + * - on destroying, to bring HTML to its initial state + */ + Events.on('destroy', function () { + Clones.remove(); + }); + + return Clones; +} + +var EventsBinder = function () { + /** + * Construct a EventsBinder instance. + */ + function EventsBinder() { + var listeners = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; + classCallCheck(this, EventsBinder); + + this.listeners = listeners; + } + + /** + * Adds events listeners to arrows HTML elements. + * + * @param {String|Array} events + * @param {Element|Window|Document} el + * @param {Function} closure + * @param {Boolean|Object} capture + * @return {Void} + */ + + createClass(EventsBinder, [{ + key: 'on', + value: function on(events, el, closure) { + var capture = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : false; + + if (isString(events)) { + events = [events]; + } + + for (var i = 0; i < events.length; i++) { + this.listeners[events[i]] = closure; + + el.addEventListener(events[i], this.listeners[events[i]], capture); + } + } + + /** + * Removes event listeners from arrows HTML elements. + * + * @param {String|Array} events + * @param {Element|Window|Document} el + * @param {Boolean|Object} capture + * @return {Void} + */ + + }, { + key: 'off', + value: function off(events, el) { + var capture = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false; + + if (isString(events)) { + events = [events]; + } + + for (var i = 0; i < events.length; i++) { + el.removeEventListener(events[i], this.listeners[events[i]], capture); + } + } + + /** + * Destroy collected listeners. + * + * @returns {Void} + */ + + }, { + key: 'destroy', + value: function destroy() { + delete this.listeners; + } + }]); + return EventsBinder; +}(); + +function Resize(Glide, Components, Events) { + /** + * Instance of the binder for DOM Events. + * + * @type {EventsBinder} + */ + var Binder = new EventsBinder(); + + var Resize = { + /** + * Initializes window bindings. + */ + mount: function mount() { + this.bind(); + }, + + /** + * Binds `rezsize` listener to the window. + * It's a costly event, so we are debouncing it. + * + * @return {Void} + */ + bind: function bind() { + Binder.on('resize', window, throttle(function () { + Events.emit('resize'); + }, Glide.settings.throttle)); + }, + + /** + * Unbinds listeners from the window. + * + * @return {Void} + */ + unbind: function unbind() { + Binder.off('resize', window); + } + }; + + /** + * Remove bindings from window: + * - on destroying, to remove added EventListener + */ + Events.on('destroy', function () { + Resize.unbind(); + Binder.destroy(); + }); + + return Resize; +} + +var VALID_DIRECTIONS = ['ltr', 'rtl']; +var FLIPED_MOVEMENTS = { + '>': '<', + '<': '>', + '=': '=' +}; + +function Direction(Glide, Components, Events) { + var Direction = { + /** + * Setups gap value based on settings. + * + * @return {Void} + */ + mount: function mount() { + this.value = Glide.settings.direction; + }, + + /** + * Resolves pattern based on direction value + * + * @param {String} pattern + * @returns {String} + */ + resolve: function resolve(pattern) { + var token = pattern.slice(0, 1); + + if (this.is('rtl')) { + return pattern.split(token).join(FLIPED_MOVEMENTS[token]); + } + + return pattern; + }, + + /** + * Checks value of direction mode. + * + * @param {String} direction + * @returns {Boolean} + */ + is: function is(direction) { + return this.value === direction; + }, + + /** + * Applies direction class to the root HTML element. + * + * @return {Void} + */ + addClass: function addClass() { + Components.Html.root.classList.add(Glide.settings.classes.direction[this.value]); + }, + + /** + * Removes direction class from the root HTML element. + * + * @return {Void} + */ + removeClass: function removeClass() { + Components.Html.root.classList.remove(Glide.settings.classes.direction[this.value]); + } + }; + + define(Direction, 'value', { + /** + * Gets value of the direction. + * + * @returns {Number} + */ + get: function get() { + return Direction._v; + }, + + /** + * Sets value of the direction. + * + * @param {String} value + * @return {Void} + */ + set: function set(value) { + if (VALID_DIRECTIONS.indexOf(value) > -1) { + Direction._v = value; + } else { + warn('Direction value must be `ltr` or `rtl`'); + } + } + }); + + /** + * Clear direction class: + * - on destroy to bring HTML to its initial state + * - on update to remove class before reappling bellow + */ + Events.on(['destroy', 'update'], function () { + Direction.removeClass(); + }); + + /** + * Remount component: + * - on update to reflect changes in direction value + */ + Events.on('update', function () { + Direction.mount(); + }); + + /** + * Apply direction class: + * - before building to apply class for the first time + * - on updating to reapply direction class that may changed + */ + Events.on(['build.before', 'update'], function () { + Direction.addClass(); + }); + + return Direction; +} + +/** + * Reflects value of glide movement. + * + * @param {Object} Glide + * @param {Object} Components + * @return {Object} + */ +function Rtl(Glide, Components) { + return { + /** + * Negates the passed translate if glide is in RTL option. + * + * @param {Number} translate + * @return {Number} + */ + modify: function modify(translate) { + if (Components.Direction.is('rtl')) { + return -translate; + } + + return translate; + } + }; +} + +/** + * Updates glide movement with a `gap` settings. + * + * @param {Object} Glide + * @param {Object} Components + * @return {Object} + */ +function Gap(Glide, Components) { + return { + /** + * Modifies passed translate value with number in the `gap` settings. + * + * @param {Number} translate + * @return {Number} + */ + modify: function modify(translate) { + return translate + Components.Gaps.value * Glide.index; + } + }; +} + +/** + * Updates glide movement with width of additional clones width. + * + * @param {Object} Glide + * @param {Object} Components + * @return {Object} + */ +function Grow(Glide, Components) { + return { + /** + * Adds to the passed translate width of the half of clones. + * + * @param {Number} translate + * @return {Number} + */ + modify: function modify(translate) { + return translate + Components.Clones.grow / 2; + } + }; +} + +/** + * Updates glide movement with a `peek` settings. + * + * @param {Object} Glide + * @param {Object} Components + * @return {Object} + */ +function Peeking(Glide, Components) { + return { + /** + * Modifies passed translate value with a `peek` setting. + * + * @param {Number} translate + * @return {Number} + */ + modify: function modify(translate) { + if (Glide.settings.focusAt >= 0) { + var peek = Components.Peek.value; + + if (isObject(peek)) { + return translate - peek.before; + } + + return translate - peek; + } + + return translate; + } + }; +} + +/** + * Updates glide movement with a `focusAt` settings. + * + * @param {Object} Glide + * @param {Object} Components + * @return {Object} + */ +function Focusing(Glide, Components) { + return { + /** + * Modifies passed translate value with index in the `focusAt` setting. + * + * @param {Number} translate + * @return {Number} + */ + modify: function modify(translate) { + var gap = Components.Gaps.value; + var width = Components.Sizes.width; + var focusAt = Glide.settings.focusAt; + var slideWidth = Components.Sizes.slideWidth; + + if (focusAt === 'center') { + return translate - (width / 2 - slideWidth / 2); + } + + return translate - slideWidth * focusAt - gap * focusAt; + } + }; +} + +/** + * Applies diffrent transformers on translate value. + * + * @param {Object} Glide + * @param {Object} Components + * @return {Object} + */ +function mutator(Glide, Components, Events) { + /** + * Merge instance transformers with collection of default transformers. + * It's important that the Rtl component be last on the list, + * so it reflects all previous transformations. + * + * @type {Array} + */ + var TRANSFORMERS = [Gap, Grow, Peeking, Focusing].concat(Glide._t, [Rtl]); + + return { + /** + * Piplines translate value with registered transformers. + * + * @param {Number} translate + * @return {Number} + */ + mutate: function mutate(translate) { + for (var i = 0; i < TRANSFORMERS.length; i++) { + var transformer = TRANSFORMERS[i]; + + if (isFunction(transformer) && isFunction(transformer().modify)) { + translate = transformer(Glide, Components, Events).modify(translate); + } else { + warn('Transformer should be a function that returns an object with `modify()` method'); + } + } + + return translate; + } + }; +} + +function Translate(Glide, Components, Events) { + var Translate = { + /** + * Sets value of translate on HTML element. + * + * @param {Number} value + * @return {Void} + */ + set: function set(value) { + var transform = mutator(Glide, Components).mutate(value); + + Components.Html.wrapper.style.transform = 'translate3d(' + -1 * transform + 'px, 0px, 0px)'; + }, + + /** + * Removes value of translate from HTML element. + * + * @return {Void} + */ + remove: function remove() { + Components.Html.wrapper.style.transform = ''; + } + }; + + /** + * Set new translate value: + * - on move to reflect index change + * - on updating via API to reflect possible changes in options + */ + Events.on('move', function (context) { + var gap = Components.Gaps.value; + var length = Components.Sizes.length; + var width = Components.Sizes.slideWidth; + + if (Glide.isType('carousel') && Components.Run.isOffset('<')) { + Components.Transition.after(function () { + Events.emit('translate.jump'); + + Translate.set(width * (length - 1)); + }); + + return Translate.set(-width - gap * length); + } + + if (Glide.isType('carousel') && Components.Run.isOffset('>')) { + Components.Transition.after(function () { + Events.emit('translate.jump'); + + Translate.set(0); + }); + + return Translate.set(width * length + gap * length); + } + + return Translate.set(context.movement); + }); + + /** + * Remove translate: + * - on destroying to bring markup to its inital state + */ + Events.on('destroy', function () { + Translate.remove(); + }); + + return Translate; +} + +function Transition(Glide, Components, Events) { + /** + * Holds inactivity status of transition. + * When true transition is not applied. + * + * @type {Boolean} + */ + var disabled = false; + + var Transition = { + /** + * Composes string of the CSS transition. + * + * @param {String} property + * @return {String} + */ + compose: function compose(property) { + var settings = Glide.settings; + + if (!disabled) { + return property + ' ' + this.duration + 'ms ' + settings.animationTimingFunc; + } + + return property + ' 0ms ' + settings.animationTimingFunc; + }, + + /** + * Sets value of transition on HTML element. + * + * @param {String=} property + * @return {Void} + */ + set: function set() { + var property = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 'transform'; + + Components.Html.wrapper.style.transition = this.compose(property); + }, + + /** + * Removes value of transition from HTML element. + * + * @return {Void} + */ + remove: function remove() { + Components.Html.wrapper.style.transition = ''; + }, + + /** + * Runs callback after animation. + * + * @param {Function} callback + * @return {Void} + */ + after: function after(callback) { + setTimeout(function () { + callback(); + }, this.duration); + }, + + /** + * Enable transition. + * + * @return {Void} + */ + enable: function enable() { + disabled = false; + + this.set(); + }, + + /** + * Disable transition. + * + * @return {Void} + */ + disable: function disable() { + disabled = true; + + this.set(); + } + }; + + define(Transition, 'duration', { + /** + * Gets duration of the transition based + * on currently running animation type. + * + * @return {Number} + */ + get: function get() { + var settings = Glide.settings; + + if (Glide.isType('slider') && Components.Run.offset) { + return settings.rewindDuration; + } + + return settings.animationDuration; + } + }); + + /** + * Set transition `style` value: + * - on each moving, because it may be cleared by offset move + */ + Events.on('move', function () { + Transition.set(); + }); + + /** + * Disable transition: + * - before initial build to avoid transitioning from `0` to `startAt` index + * - while resizing window and recalculating dimentions + * - on jumping from offset transition at start and end edges in `carousel` type + */ + Events.on(['build.before', 'resize', 'translate.jump'], function () { + Transition.disable(); + }); + + /** + * Enable transition: + * - on each running, because it may be disabled by offset move + */ + Events.on('run', function () { + Transition.enable(); + }); + + /** + * Remove transition: + * - on destroying to bring markup to its inital state + */ + Events.on('destroy', function () { + Transition.remove(); + }); + + return Transition; +} + +/** + * Test via a getter in the options object to see + * if the passive property is accessed. + * + * @see https://github.com/WICG/EventListenerOptions/blob/gh-pages/explainer.md#feature-detection + */ + +var supportsPassive = false; + +try { + var opts = Object.defineProperty({}, 'passive', { + get: function get() { + supportsPassive = true; + } + }); + + window.addEventListener('testPassive', null, opts); + window.removeEventListener('testPassive', null, opts); +} catch (e) {} + +var supportsPassive$1 = supportsPassive; + +var START_EVENTS = ['touchstart', 'mousedown']; +var MOVE_EVENTS = ['touchmove', 'mousemove']; +var END_EVENTS = ['touchend', 'touchcancel', 'mouseup', 'mouseleave']; +var MOUSE_EVENTS = ['mousedown', 'mousemove', 'mouseup', 'mouseleave']; + +function Swipe(Glide, Components, Events) { + /** + * Instance of the binder for DOM Events. + * + * @type {EventsBinder} + */ + var Binder = new EventsBinder(); + + var swipeSin = 0; + var swipeStartX = 0; + var swipeStartY = 0; + var disabled = false; + var moveable = true; + var capture = supportsPassive$1 ? { passive: true } : false; + + var Swipe = { + /** + * Initializes swipe bindings. + * + * @return {Void} + */ + mount: function mount() { + this.bindSwipeStart(); + }, + + /** + * Handler for `swipestart` event. Calculates entry points of the user's tap. + * + * @param {Object} event + * @return {Void} + */ + start: function start(event) { + if (!disabled && !Glide.disabled) { + this.disable(); + + var swipe = this.touches(event); + + moveable = true; + swipeSin = null; + swipeStartX = toInt(swipe.pageX); + swipeStartY = toInt(swipe.pageY); + + this.bindSwipeMove(); + this.bindSwipeEnd(); + + Events.emit('swipe.start'); + } + }, + + /** + * Handler for `swipemove` event. Calculates user's tap angle and distance. + * + * @param {Object} event + */ + move: function move(event) { + if (!Glide.disabled) { + var _Glide$settings = Glide.settings, + touchAngle = _Glide$settings.touchAngle, + touchRatio = _Glide$settings.touchRatio, + classes = _Glide$settings.classes; + + var swipe = this.touches(event); + + var subExSx = toInt(swipe.pageX) - swipeStartX; + var subEySy = toInt(swipe.pageY) - swipeStartY; + var powEX = Math.abs(subExSx << 2); + var powEY = Math.abs(subEySy << 2); + var swipeHypotenuse = Math.sqrt(powEX + powEY); + var swipeCathetus = Math.sqrt(powEY); + + swipeSin = Math.asin(swipeCathetus / swipeHypotenuse); + + if (moveable && swipeSin * 180 / Math.PI < touchAngle) { + event.stopPropagation(); + + Components.Move.make(subExSx * toFloat(touchRatio)); + + Components.Html.root.classList.add(classes.dragging); + + Events.emit('swipe.move'); + } else { + moveable = false; + + return false; + } + } + }, + + /** + * Handler for `swipeend` event. Finitializes user's tap and decides about glide move. + * + * @param {Object} event + * @return {Void} + */ + end: function end(event) { + if (!Glide.disabled) { + var settings = Glide.settings; + + var swipe = this.touches(event); + var threshold = this.threshold(event); + + var swipeDistance = swipe.pageX - swipeStartX; + var swipeDeg = swipeSin * 180 / Math.PI; + var steps = Math.round(swipeDistance / Components.Sizes.slideWidth); + + this.enable(); + + if (moveable) { + if (swipeDistance > threshold && swipeDeg < settings.touchAngle) { + // While swipe is positive and greater than threshold move backward. + if (settings.perTouch) { + steps = Math.min(steps, toInt(settings.perTouch)); + } + + if (Components.Direction.is('rtl')) { + steps = -steps; + } + + Components.Run.make(Components.Direction.resolve('<' + steps)); + } else if (swipeDistance < -threshold && swipeDeg < settings.touchAngle) { + // While swipe is negative and lower than negative threshold move forward. + if (settings.perTouch) { + steps = Math.max(steps, -toInt(settings.perTouch)); + } + + if (Components.Direction.is('rtl')) { + steps = -steps; + } + + Components.Run.make(Components.Direction.resolve('>' + steps)); + } else { + // While swipe don't reach distance apply previous transform. + Components.Move.make(); + } + } + + Components.Html.root.classList.remove(settings.classes.dragging); + + this.unbindSwipeMove(); + this.unbindSwipeEnd(); + + Events.emit('swipe.end'); + } + }, + + /** + * Binds swipe's starting event. + * + * @return {Void} + */ + bindSwipeStart: function bindSwipeStart() { + var _this = this; + + var settings = Glide.settings; + + if (settings.swipeThreshold) { + Binder.on(START_EVENTS[0], Components.Html.wrapper, function (event) { + _this.start(event); + }, capture); + } + + if (settings.dragThreshold) { + Binder.on(START_EVENTS[1], Components.Html.wrapper, function (event) { + _this.start(event); + }, capture); + } + }, + + /** + * Unbinds swipe's starting event. + * + * @return {Void} + */ + unbindSwipeStart: function unbindSwipeStart() { + Binder.off(START_EVENTS[0], Components.Html.wrapper, capture); + Binder.off(START_EVENTS[1], Components.Html.wrapper, capture); + }, + + /** + * Binds swipe's moving event. + * + * @return {Void} + */ + bindSwipeMove: function bindSwipeMove() { + var _this2 = this; + + Binder.on(MOVE_EVENTS, Components.Html.wrapper, throttle(function (event) { + _this2.move(event); + }, Glide.settings.throttle), capture); + }, + + /** + * Unbinds swipe's moving event. + * + * @return {Void} + */ + unbindSwipeMove: function unbindSwipeMove() { + Binder.off(MOVE_EVENTS, Components.Html.wrapper, capture); + }, + + /** + * Binds swipe's ending event. + * + * @return {Void} + */ + bindSwipeEnd: function bindSwipeEnd() { + var _this3 = this; + + Binder.on(END_EVENTS, Components.Html.wrapper, function (event) { + _this3.end(event); + }); + }, + + /** + * Unbinds swipe's ending event. + * + * @return {Void} + */ + unbindSwipeEnd: function unbindSwipeEnd() { + Binder.off(END_EVENTS, Components.Html.wrapper); + }, + + /** + * Normalizes event touches points accorting to different types. + * + * @param {Object} event + */ + touches: function touches(event) { + if (MOUSE_EVENTS.indexOf(event.type) > -1) { + return event; + } + + return event.touches[0] || event.changedTouches[0]; + }, + + /** + * Gets value of minimum swipe distance settings based on event type. + * + * @return {Number} + */ + threshold: function threshold(event) { + var settings = Glide.settings; + + if (MOUSE_EVENTS.indexOf(event.type) > -1) { + return settings.dragThreshold; + } + + return settings.swipeThreshold; + }, + + /** + * Enables swipe event. + * + * @return {self} + */ + enable: function enable() { + disabled = false; + + Components.Transition.enable(); + + return this; + }, + + /** + * Disables swipe event. + * + * @return {self} + */ + disable: function disable() { + disabled = true; + + Components.Transition.disable(); + + return this; + } + }; + + /** + * Add component class: + * - after initial building + */ + Events.on('build.after', function () { + Components.Html.root.classList.add(Glide.settings.classes.swipeable); + }); + + /** + * Remove swiping bindings: + * - on destroying, to remove added EventListeners + */ + Events.on('destroy', function () { + Swipe.unbindSwipeStart(); + Swipe.unbindSwipeMove(); + Swipe.unbindSwipeEnd(); + Binder.destroy(); + }); + + return Swipe; +} + +function Images(Glide, Components, Events) { + /** + * Instance of the binder for DOM Events. + * + * @type {EventsBinder} + */ + var Binder = new EventsBinder(); + + var Images = { + /** + * Binds listener to glide wrapper. + * + * @return {Void} + */ + mount: function mount() { + this.bind(); + }, + + /** + * Binds `dragstart` event on wrapper to prevent dragging images. + * + * @return {Void} + */ + bind: function bind() { + Binder.on('dragstart', Components.Html.wrapper, this.dragstart); + }, + + /** + * Unbinds `dragstart` event on wrapper. + * + * @return {Void} + */ + unbind: function unbind() { + Binder.off('dragstart', Components.Html.wrapper); + }, + + /** + * Event handler. Prevents dragging. + * + * @return {Void} + */ + dragstart: function dragstart(event) { + event.preventDefault(); + } + }; + + /** + * Remove bindings from images: + * - on destroying, to remove added EventListeners + */ + Events.on('destroy', function () { + Images.unbind(); + Binder.destroy(); + }); + + return Images; +} + +function Anchors(Glide, Components, Events) { + /** + * Instance of the binder for DOM Events. + * + * @type {EventsBinder} + */ + var Binder = new EventsBinder(); + + /** + * Holds detaching status of anchors. + * Prevents detaching of already detached anchors. + * + * @private + * @type {Boolean} + */ + var detached = false; + + /** + * Holds preventing status of anchors. + * If `true` redirection after click will be disabled. + * + * @private + * @type {Boolean} + */ + var prevented = false; + + var Anchors = { + /** + * Setups a initial state of anchors component. + * + * @returns {Void} + */ + mount: function mount() { + /** + * Holds collection of anchors elements. + * + * @private + * @type {HTMLCollection} + */ + this._a = Components.Html.wrapper.querySelectorAll('a'); + + this.bind(); + }, + + /** + * Binds events to anchors inside a track. + * + * @return {Void} + */ + bind: function bind() { + Binder.on('click', Components.Html.wrapper, this.click); + }, + + /** + * Unbinds events attached to anchors inside a track. + * + * @return {Void} + */ + unbind: function unbind() { + Binder.off('click', Components.Html.wrapper); + }, + + /** + * Handler for click event. Prevents clicks when glide is in `prevent` status. + * + * @param {Object} event + * @return {Void} + */ + click: function click(event) { + if (prevented) { + event.stopPropagation(); + event.preventDefault(); + } + }, + + /** + * Detaches anchors click event inside glide. + * + * @return {self} + */ + detach: function detach() { + prevented = true; + + if (!detached) { + for (var i = 0; i < this.items.length; i++) { + this.items[i].draggable = false; + + this.items[i].setAttribute('data-href', this.items[i].getAttribute('href')); + + this.items[i].removeAttribute('href'); + } + + detached = true; + } + + return this; + }, + + /** + * Attaches anchors click events inside glide. + * + * @return {self} + */ + attach: function attach() { + prevented = false; + + if (detached) { + for (var i = 0; i < this.items.length; i++) { + this.items[i].draggable = true; + + this.items[i].setAttribute('href', this.items[i].getAttribute('data-href')); + } + + detached = false; + } + + return this; + } + }; + + define(Anchors, 'items', { + /** + * Gets collection of the arrows HTML elements. + * + * @return {HTMLElement[]} + */ + get: function get() { + return Anchors._a; + } + }); + + /** + * Detach anchors inside slides: + * - on swiping, so they won't redirect to its `href` attributes + */ + Events.on('swipe.move', function () { + Anchors.detach(); + }); + + /** + * Attach anchors inside slides: + * - after swiping and transitions ends, so they can redirect after click again + */ + Events.on('swipe.end', function () { + Components.Transition.after(function () { + Anchors.attach(); + }); + }); + + /** + * Unbind anchors inside slides: + * - on destroying, to bring anchors to its initial state + */ + Events.on('destroy', function () { + Anchors.attach(); + Anchors.unbind(); + Binder.destroy(); + }); + + return Anchors; +} + +var NAV_SELECTOR = '[data-glide-el="controls[nav]"]'; +var CONTROLS_SELECTOR = '[data-glide-el^="controls"]'; + +function Controls(Glide, Components, Events) { + /** + * Instance of the binder for DOM Events. + * + * @type {EventsBinder} + */ + var Binder = new EventsBinder(); + + var Controls = { + /** + * Inits arrows. Binds events listeners + * to the arrows HTML elements. + * + * @return {Void} + */ + mount: function mount() { + /** + * Collection of navigation HTML elements. + * + * @private + * @type {HTMLCollection} + */ + this._n = Components.Html.root.querySelectorAll(NAV_SELECTOR); + + /** + * Collection of controls HTML elements. + * + * @private + * @type {HTMLCollection} + */ + this._c = Components.Html.root.querySelectorAll(CONTROLS_SELECTOR); + + this.addBindings(); + }, + + /** + * Sets active class to current slide. + * + * @return {Void} + */ + setActive: function setActive() { + for (var i = 0; i < this._n.length; i++) { + this.addClass(this._n[i].children); + } + }, + + /** + * Removes active class to current slide. + * + * @return {Void} + */ + removeActive: function removeActive() { + for (var i = 0; i < this._n.length; i++) { + this.removeClass(this._n[i].children); + } + }, + + /** + * Toggles active class on items inside navigation. + * + * @param {HTMLElement} controls + * @return {Void} + */ + addClass: function addClass(controls) { + var settings = Glide.settings; + var item = controls[Glide.index]; + + item.classList.add(settings.classes.activeNav); + + siblings(item).forEach(function (sibling) { + sibling.classList.remove(settings.classes.activeNav); + }); + }, + + /** + * Removes active class from active control. + * + * @param {HTMLElement} controls + * @return {Void} + */ + removeClass: function removeClass(controls) { + controls[Glide.index].classList.remove(Glide.settings.classes.activeNav); + }, + + /** + * Adds handles to the each group of controls. + * + * @return {Void} + */ + addBindings: function addBindings() { + for (var i = 0; i < this._c.length; i++) { + this.bind(this._c[i].children); + } + }, + + /** + * Removes handles from the each group of controls. + * + * @return {Void} + */ + removeBindings: function removeBindings() { + for (var i = 0; i < this._c.length; i++) { + this.unbind(this._c[i].children); + } + }, + + /** + * Binds events to arrows HTML elements. + * + * @param {HTMLCollection} elements + * @return {Void} + */ + bind: function bind(elements) { + for (var i = 0; i < elements.length; i++) { + Binder.on(['click', 'touchstart'], elements[i], this.click); + } + }, + + /** + * Unbinds events binded to the arrows HTML elements. + * + * @param {HTMLCollection} elements + * @return {Void} + */ + unbind: function unbind(elements) { + for (var i = 0; i < elements.length; i++) { + Binder.off(['click', 'touchstart'], elements[i]); + } + }, + + /** + * Handles `click` event on the arrows HTML elements. + * Moves slider in driection precised in + * `data-glide-dir` attribute. + * + * @param {Object} event + * @return {Void} + */ + click: function click(event) { + event.preventDefault(); + + Components.Run.make(Components.Direction.resolve(event.currentTarget.getAttribute('data-glide-dir'))); + } + }; + + define(Controls, 'items', { + /** + * Gets collection of the controls HTML elements. + * + * @return {HTMLElement[]} + */ + get: function get() { + return Controls._c; + } + }); + + /** + * Swap active class of current navigation item: + * - after mounting to set it to initial index + * - after each move to the new index + */ + Events.on(['mount.after', 'move.after'], function () { + Controls.setActive(); + }); + + /** + * Remove bindings and HTML Classes: + * - on destroying, to bring markup to its initial state + */ + Events.on('destroy', function () { + Controls.removeBindings(); + Controls.removeActive(); + Binder.destroy(); + }); + + return Controls; +} + +function Keyboard(Glide, Components, Events) { + /** + * Instance of the binder for DOM Events. + * + * @type {EventsBinder} + */ + var Binder = new EventsBinder(); + + var Keyboard = { + /** + * Binds keyboard events on component mount. + * + * @return {Void} + */ + mount: function mount() { + if (Glide.settings.keyboard) { + this.bind(); + } + }, + + /** + * Adds keyboard press events. + * + * @return {Void} + */ + bind: function bind() { + Binder.on('keyup', document, this.press); + }, + + /** + * Removes keyboard press events. + * + * @return {Void} + */ + unbind: function unbind() { + Binder.off('keyup', document); + }, + + /** + * Handles keyboard's arrows press and moving glide foward and backward. + * + * @param {Object} event + * @return {Void} + */ + press: function press(event) { + if (event.keyCode === 39) { + Components.Run.make(Components.Direction.resolve('>')); + } + + if (event.keyCode === 37) { + Components.Run.make(Components.Direction.resolve('<')); + } + } + }; + + /** + * Remove bindings from keyboard: + * - on destroying to remove added events + * - on updating to remove events before remounting + */ + Events.on(['destroy', 'update'], function () { + Keyboard.unbind(); + }); + + /** + * Remount component + * - on updating to reflect potential changes in settings + */ + Events.on('update', function () { + Keyboard.mount(); + }); + + /** + * Destroy binder: + * - on destroying to remove listeners + */ + Events.on('destroy', function () { + Binder.destroy(); + }); + + return Keyboard; +} + +function Autoplay(Glide, Components, Events) { + /** + * Instance of the binder for DOM Events. + * + * @type {EventsBinder} + */ + var Binder = new EventsBinder(); + + var Autoplay = { + /** + * Initializes autoplaying and events. + * + * @return {Void} + */ + mount: function mount() { + this.start(); + + if (Glide.settings.hoverpause) { + this.bind(); + } + }, + + /** + * Starts autoplaying in configured interval. + * + * @param {Boolean|Number} force Run autoplaying with passed interval regardless of `autoplay` settings + * @return {Void} + */ + start: function start() { + var _this = this; + + if (Glide.settings.autoplay) { + if (isUndefined(this._i)) { + this._i = setInterval(function () { + _this.stop(); + + Components.Run.make('>'); + + _this.start(); + }, this.time); + } + } + }, + + /** + * Stops autorunning of the glide. + * + * @return {Void} + */ + stop: function stop() { + this._i = clearInterval(this._i); + }, + + /** + * Stops autoplaying while mouse is over glide's area. + * + * @return {Void} + */ + bind: function bind() { + var _this2 = this; + + Binder.on('mouseover', Components.Html.root, function () { + _this2.stop(); + }); + + Binder.on('mouseout', Components.Html.root, function () { + _this2.start(); + }); + }, + + /** + * Unbind mouseover events. + * + * @returns {Void} + */ + unbind: function unbind() { + Binder.off(['mouseover', 'mouseout'], Components.Html.root); + } + }; + + define(Autoplay, 'time', { + /** + * Gets time period value for the autoplay interval. Prioritizes + * times in `data-glide-autoplay` attrubutes over options. + * + * @return {Number} + */ + get: function get() { + var autoplay = Components.Html.slides[Glide.index].getAttribute('data-glide-autoplay'); + + if (autoplay) { + return toInt(autoplay); + } + + return toInt(Glide.settings.autoplay); + } + }); + + /** + * Stop autoplaying and unbind events: + * - on destroying, to clear defined interval + * - on updating via API to reset interval that may changed + */ + Events.on(['destroy', 'update'], function () { + Autoplay.unbind(); + }); + + /** + * Stop autoplaying: + * - before each run, to restart autoplaying + * - on pausing via API + * - on destroying, to clear defined interval + * - while starting a swipe + * - on updating via API to reset interval that may changed + */ + Events.on(['run.before', 'pause', 'destroy', 'swipe.start', 'update'], function () { + Autoplay.stop(); + }); + + /** + * Start autoplaying: + * - after each run, to restart autoplaying + * - on playing via API + * - while ending a swipe + */ + Events.on(['run.after', 'play', 'swipe.end'], function () { + Autoplay.start(); + }); + + /** + * Remount autoplaying: + * - on updating via API to reset interval that may changed + */ + Events.on('update', function () { + Autoplay.mount(); + }); + + /** + * Destroy a binder: + * - on destroying glide instance to clearup listeners + */ + Events.on('destroy', function () { + Binder.destroy(); + }); + + return Autoplay; +} + +/** + * Sorts keys of breakpoint object so they will be ordered from lower to bigger. + * + * @param {Object} points + * @returns {Object} + */ +function sortBreakpoints(points) { + if (isObject(points)) { + return sortKeys(points); + } else { + warn('Breakpoints option must be an object'); + } + + return {}; +} + +function Breakpoints(Glide, Components, Events) { + /** + * Instance of the binder for DOM Events. + * + * @type {EventsBinder} + */ + var Binder = new EventsBinder(); + + /** + * Holds reference to settings. + * + * @type {Object} + */ + var settings = Glide.settings; + + /** + * Holds reference to breakpoints object in settings. Sorts breakpoints + * from smaller to larger. It is required in order to proper + * matching currently active breakpoint settings. + * + * @type {Object} + */ + var points = sortBreakpoints(settings.breakpoints); + + /** + * Cache initial settings before overwritting. + * + * @type {Object} + */ + var defaults = _extends({}, settings); + + var Breakpoints = { + /** + * Matches settings for currectly matching media breakpoint. + * + * @param {Object} points + * @returns {Object} + */ + match: function match(points) { + if (typeof window.matchMedia !== 'undefined') { + for (var point in points) { + if (points.hasOwnProperty(point)) { + if (window.matchMedia('(max-width: ' + point + 'px)').matches) { + return points[point]; + } + } + } + } + + return defaults; + } + }; + + /** + * Overwrite instance settings with currently matching breakpoint settings. + * This happens right after component initialization. + */ + _extends(settings, Breakpoints.match(points)); + + /** + * Update glide with settings of matched brekpoint: + * - window resize to update slider + */ + Binder.on('resize', window, throttle(function () { + Glide.settings = mergeOptions(settings, Breakpoints.match(points)); + }, Glide.settings.throttle)); + + /** + * Resort and update default settings: + * - on reinit via API, so breakpoint matching will be performed with options + */ + Events.on('update', function () { + points = sortBreakpoints(points); + + defaults = _extends({}, settings); + }); + + /** + * Unbind resize listener: + * - on destroying, to bring markup to its initial state + */ + Events.on('destroy', function () { + Binder.off('resize', window); + }); + + return Breakpoints; +} + +var COMPONENTS = { + // Required + Html: Html, + Translate: Translate, + Transition: Transition, + Direction: Direction, + Peek: Peek, + Sizes: Sizes, + Gaps: Gaps, + Move: Move, + Clones: Clones, + Resize: Resize, + Build: Build, + Run: Run, + + // Optional + Swipe: Swipe, + Images: Images, + Anchors: Anchors, + Controls: Controls, + Keyboard: Keyboard, + Autoplay: Autoplay, + Breakpoints: Breakpoints +}; + +var Glide$1$1 = function (_Core) { + inherits(Glide$$1, _Core); + + function Glide$$1() { + classCallCheck(this, Glide$$1); + return possibleConstructorReturn(this, (Glide$$1.__proto__ || Object.getPrototypeOf(Glide$$1)).apply(this, arguments)); + } + + createClass(Glide$$1, [{ + key: 'mount', + value: function mount() { + var extensions = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; + + return get(Glide$$1.prototype.__proto__ || Object.getPrototypeOf(Glide$$1.prototype), 'mount', this).call(this, _extends({}, COMPONENTS, extensions)); + } + }]); + return Glide$$1; +}(Glide$1); + +var Events = ['mount.before', 'mount.after', 'update', 'play', 'pause', 'build.before', 'build.after', 'run.before', 'run', 'run.after', 'run.offset', 'run.start', 'run.end', 'move', 'move.after', 'resize', 'swipe.start', 'swipe.move', 'swipe.end', 'translate.jump']; + +(function () { + if (typeof document !== 'undefined') { + var head = document.head || document.getElementsByTagName('head')[0], + style = document.createElement('style'), + css = "";style.type = 'text/css';if (style.styleSheet) { + style.styleSheet.cssText = css; + } else { + style.appendChild(document.createTextNode(css)); + }head.appendChild(style); + } +})(); + +var Glide = { render: function render() { + var _vm = this;var _h = _vm.$createElement;var _c = _vm._self._c || _h;return _c('div', { staticClass: "glide" }, [_vm._t("default")], 2); + }, staticRenderFns: [], + name: 'Glide', + + props: { + settings: { + type: Object, + default: function _default() {} + }, + delayInitialization: { + type: Boolean, + default: false + } + }, + + methods: { + go: function go(pattern) { + this.glide.go(pattern); + }, + init: function init() { + this.glide.mount(); + } + }, + + data: function data() { + return { + glide: undefined + }; + }, + mounted: function mounted() { + var _this = this; + + this.glide = new Glide$1$1(this.$el, this.settings); + + Events.forEach(function (event) { + _this.glide.on(event, function () { + for (var _len = arguments.length, parameters = Array(_len), _key = 0; _key < _len; _key++) { + parameters[_key] = arguments[_key]; + } + + var emmiter = event.replace(/\.([a-z])/g, function (m, w) { + return w.toUpperCase(); + }); + + _this.$emit.apply(_this, [emmiter].concat(parameters)); + }); + }); + + if (!this.delayInitialization) { + this.init(); + } + } +}; + +(function () { + if (typeof document !== 'undefined') { + var head = document.head || document.getElementsByTagName('head')[0], + style = document.createElement('style'), + css = "";style.type = 'text/css';if (style.styleSheet) { + style.styleSheet.cssText = css; + } else { + style.appendChild(document.createTextNode(css)); + }head.appendChild(style); + } +})(); + +var Track = { render: function render() { + var _vm = this;var _h = _vm.$createElement;var _c = _vm._self._c || _h;return _c('div', { staticClass: "glide__track", attrs: { "data-glide-el": "track" } }, [_c('ul', { staticClass: "glide__slides" }, [_vm._t("default")], 2)]); + }, staticRenderFns: [], + name: 'GlideTrack' +}; + +(function () { + if (typeof document !== 'undefined') { + var head = document.head || document.getElementsByTagName('head')[0], + style = document.createElement('style'), + css = "";style.type = 'text/css';if (style.styleSheet) { + style.styleSheet.cssText = css; + } else { + style.appendChild(document.createTextNode(css)); + }head.appendChild(style); + } +})(); + +var Slide = { render: function render() { + var _vm = this;var _h = _vm.$createElement;var _c = _vm._self._c || _h;return _c('li', { staticClass: "glide__slide" }, [_vm._t("default")], 2); + }, staticRenderFns: [], + name: 'GlideSlide' +}; + +var index = { + install: function install(Vue, options) { + Vue.component(Glide.name, Glide); + Vue.component(Track.name, Track); + Vue.component(Slide.name, Slide); + } +}; + +return index; + +}))); diff --git a/dist/vue-glide.min.js b/dist/vue-glide.min.js new file mode 100644 index 0000000..4cff3cb --- /dev/null +++ b/dist/vue-glide.min.js @@ -0,0 +1,11 @@ +/*! + * VueGlide.js v3.1.0 + * (c) 2017-2019 jedrzejchalubek (https://jedrzejchalubek.com) + * Released under the MIT License. + */ +!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):t.VueGlide=e()}(this,function(){"use strict";var n="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t},i={type:"slider",startAt:0,perView:1,focusAt:0,gap:10,autoplay:!1,hoverpause:!0,keyboard:!0,bound:!1,swipeThreshold:80,dragThreshold:120,perTouch:!1,touchRatio:.5,touchAngle:45,animationDuration:400,rewind:!0,rewindDuration:800,animationTimingFunc:"cubic-bezier(.165, .840, .440, 1)",throttle:10,direction:"ltr",peek:0,breakpoints:{},classes:{direction:{ltr:"glide--ltr",rtl:"glide--rtl"},slider:"glide--slider",carousel:"glide--carousel",swipeable:"glide--swipeable",dragging:"glide--dragging",cloneSlide:"glide__slide--clone",activeNav:"glide__bullet--active",activeSlide:"glide__slide--active",disabledArrow:"glide__arrow--disabled"}}; +/*! + * Glide.js v3.2.6 + * (c) 2013-2019 Jędrzej Chałubek (http://jedrzejchalubek.com/) + * Released under the MIT License. + */var r="function"==typeof Symbol&&"symbol"===n(Symbol.iterator)?function(t){return void 0===t?"undefined":n(t)}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":void 0===t?"undefined":n(t)},o=function(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")},s=function(){function i(t,e){for(var n=0;n":"<","<":">","=":"="};function t(t,e){return{modify:function(t){return e.Direction.is("rtl")?-t:t}}}function O(i,r,o){var s=[function(e,n){return{modify:function(t){return t+n.Gaps.value*e.index}}},function(t,e){return{modify:function(t){return t+e.Clones.grow/2}}},function(n,i){return{modify:function(t){if(0<=n.settings.focusAt){var e=i.Peek.value;return c(e)?t-e.before:t-e}return t}}},function(o,s){return{modify:function(t){var e=s.Gaps.value,n=s.Sizes.width,i=o.settings.focusAt,r=s.Sizes.slideWidth;return"center"===i?t-(n/2-r/2):t-r*i-e*i}}}].concat(i._t,[t]);return{mutate:function(t){for(var e=0;e")?(o.Transition.after(function(){s.emit("translate.jump"),u.set(0)}),u.set(i*n+e*n)):u.set(t.movement)}),s.on("destroy",function(){u.remove()}),u},Transition:function(n,e,t){var i=!1,r={compose:function(t){var e=n.settings;return i?t+" 0ms "+e.animationTimingFunc:t+" "+this.duration+"ms "+e.animationTimingFunc},set:function(){var t=0"))&&(e._o=!1,s.emit("run.offset",e.move)),s.emit("run.after",e.move),o.enable()}))},calculate:function(){var t=this.move,e=this.length,n=t.steps,i=t.direction,r="number"==typeof b(n)&&0!==b(n);switch(i){case">":">"===n?o.index=e:this.isEnd()?(o.isType("slider")&&!o.settings.rewind||(this._o=!0,o.index=0),s.emit("run.end",t)):r?o.index+=Math.min(e-o.index,-b(n)):o.index++;break;case"<":"<"===n?o.index=0:this.isStart()?(o.isType("slider")&&!o.settings.rewind||(this._o=!0,o.index=e),s.emit("run.start",t)):r?o.index-=Math.min(o.index,b(n)):o.index--;break;case"=":o.index=n}},isStart:function(){return 0===o.index},isEnd:function(){return o.index===this.length},isOffset:function(t){return this._o&&this.move.direction===t}};return v(t,"move",{get:function(){return this._m},set:function(t){this._m={direction:t.substr(0,1),steps:t.substr(1)?t.substr(1):0}}}),v(t,"length",{get:function(){var t=o.settings,e=n.Html.slides.length;return o.isType("slider")&&"center"!==t.focusAt&&t.bound?e-1-(b(t.perView)-1)+b(t.focusAt):e-1}}),v(t,"offset",{get:function(){return this._o}}),t},Swipe:function(d,h,p){var n=new x,v=0,m=0,g=0,i=!1,y=!0,r=!!z&&{passive:!0},t={mount:function(){this.bindSwipeStart()},start:function(t){if(!i&&!d.disabled){this.disable();var e=this.touches(t);y=!0,v=null,m=b(e.pageX),g=b(e.pageY),this.bindSwipeMove(),this.bindSwipeEnd(),p.emit("swipe.start")}},move:function(t){if(!d.disabled){var e=d.settings,n=e.touchAngle,i=e.touchRatio,r=e.classes,o=this.touches(t),s=b(o.pageX)-m,u=b(o.pageY)-g,a=Math.abs(s<<2),c=Math.abs(u<<2),l=Math.sqrt(a+c),f=Math.sqrt(c);if(v=Math.asin(f/l),!(y&&180*v/Math.PI"+s))):h.Move.make()),h.Html.root.classList.remove(e.classes.dragging),this.unbindSwipeMove(),this.unbindSwipeEnd(),p.emit("swipe.end")}},bindSwipeStart:function(){var e=this,t=d.settings;t.swipeThreshold&&n.on(M[0],h.Html.wrapper,function(t){e.start(t)},r),t.dragThreshold&&n.on(M[1],h.Html.wrapper,function(t){e.start(t)},r)},unbindSwipeStart:function(){n.off(M[0],h.Html.wrapper,r),n.off(M[1],h.Html.wrapper,r)},bindSwipeMove:function(){var e=this;n.on(P,h.Html.wrapper,_(function(t){e.move(t)},d.settings.throttle),r)},unbindSwipeMove:function(){n.off(P,h.Html.wrapper,r)},bindSwipeEnd:function(){var e=this;n.on(L,h.Html.wrapper,function(t){e.end(t)})},unbindSwipeEnd:function(){n.off(L,h.Html.wrapper)},touches:function(t){return-1")),37===t.keyCode&&e.Run.make(e.Direction.resolve("<"))}};return n.on(["destroy","update"],function(){r.unbind()}),n.on("update",function(){r.mount()}),n.on("destroy",function(){i.destroy()}),r},Autoplay:function(e,n,t){var i=new x,r={mount:function(){this.start(),e.settings.hoverpause&&this.bind()},start:function(){var t=this;e.settings.autoplay&&f(this._i)&&(this._i=setInterval(function(){t.stop(),n.Run.make(">"),t.start()},this.time))},stop:function(){this._i=clearInterval(this._i)},bind:function(){var t=this;i.on("mouseover",n.Html.root,function(){t.stop()}),i.on("mouseout",n.Html.root,function(){t.start()})},unbind:function(){i.off(["mouseover","mouseout"],n.Html.root)}};return v(r,"time",{get:function(){var t=n.Html.slides[e.index].getAttribute("data-glide-autoplay");return b(t||e.settings.autoplay)}}),t.on(["destroy","update"],function(){r.unbind()}),t.on(["run.before","pause","destroy","swipe.start","update"],function(){r.stop()}),t.on(["run.after","play","swipe.end"],function(){r.start()}),t.on("update",function(){r.mount()}),t.on("destroy",function(){i.destroy()}),r},Breakpoints:function(t,e,n){var i=new x,r=t.settings,o=R(r.breakpoints),s=a({},r),u={match:function(t){if(void 0!==window.matchMedia)for(var e in t)if(t.hasOwnProperty(e)&&window.matchMedia("(max-width: "+e+"px)").matches)return t[e];return s}};return a(r,u.match(o)),i.on("resize",window,_(function(){t.settings=h(r,u.match(o))},t.settings.throttle)),n.on("update",function(){o=R(o),s=a({},r)}),n.on("destroy",function(){i.off("resize",window)}),u}},N=function(t){function e(){return o(this,e),function(t,e){if(!t)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!e||"object"!==(void 0===e?"undefined":n(e))&&"function"!=typeof e?t:e}(this,(e.__proto__||Object.getPrototypeOf(e)).apply(this,arguments))}return function(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("Super expression must either be null or a function, not "+(void 0===e?"undefined":n(e)));t.prototype=Object.create(e&&e.prototype,{constructor:{value:t,enumerable:!1,writable:!0,configurable:!0}}),e&&(Object.setPrototypeOf?Object.setPrototypeOf(t,e):t.__proto__=e)}(e,m),s(e,[{key:"mount",value:function(){var t=0 (https://jedrzejchalubek.com)", "license": "MIT", "dependencies": { - "@glidejs/glide": "^3.0.1", + "@glidejs/glide": "^3.2.6", "vue": "^2.0.0" }, "devDependencies": { diff --git a/src/components/Glide.vue b/src/components/Glide.vue index fc75be6..9f6066e 100644 --- a/src/components/Glide.vue +++ b/src/components/Glide.vue @@ -15,12 +15,19 @@ export default { settings: { type: Object, default: () => {} + }, + delayInitialization: { + type: Boolean, + default: false } }, methods: { go (pattern) { this.glide.go(pattern) + }, + init () { + this.glide.mount() } }, @@ -41,7 +48,9 @@ export default { }) }) - this.glide.mount() + if (!this.delayInitialization) { + this.init() + } } }