UNPKG

svelte-glidejs

Version:
1,868 lines (1,632 loc) 124 kB
(function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : typeof define === 'function' && define.amd ? define(factory) : (global = global || self, global.Glidejs = factory()); }(this, (function () { 'use strict'; function noop() { } function assign(tar, src) { // @ts-ignore for (const k in src) tar[k] = src[k]; return tar; } function run(fn) { return fn(); } function blank_object() { return Object.create(null); } function run_all(fns) { fns.forEach(run); } function is_function(thing) { return typeof thing === 'function'; } function safe_not_equal(a, b) { return a != a ? b == b : a !== b || ((a && typeof a === 'object') || typeof a === 'function'); } function is_empty(obj) { return Object.keys(obj).length === 0; } function create_slot(definition, ctx, $$scope, fn) { if (definition) { const slot_ctx = get_slot_context(definition, ctx, $$scope, fn); return definition[0](slot_ctx); } } function get_slot_context(definition, ctx, $$scope, fn) { return definition[1] && fn ? assign($$scope.ctx.slice(), definition[1](fn(ctx))) : $$scope.ctx; } function get_slot_changes(definition, $$scope, dirty, fn) { if (definition[2] && fn) { const lets = definition[2](fn(dirty)); if ($$scope.dirty === undefined) { return lets; } if (typeof lets === 'object') { const merged = []; const len = Math.max($$scope.dirty.length, lets.length); for (let i = 0; i < len; i += 1) { merged[i] = $$scope.dirty[i] | lets[i]; } return merged; } return $$scope.dirty | lets; } return $$scope.dirty; } function update_slot(slot, slot_definition, ctx, $$scope, dirty, get_slot_changes_fn, get_slot_context_fn) { const slot_changes = get_slot_changes(slot_definition, $$scope, dirty, get_slot_changes_fn); if (slot_changes) { const slot_context = get_slot_context(slot_definition, ctx, $$scope, get_slot_context_fn); slot.p(slot_context, slot_changes); } } function action_destroyer(action_result) { return action_result && is_function(action_result.destroy) ? action_result.destroy : noop; } function append(target, node) { target.appendChild(node); } function insert(target, node, anchor) { target.insertBefore(node, anchor || null); } function detach(node) { node.parentNode.removeChild(node); } function destroy_each(iterations, detaching) { for (let i = 0; i < iterations.length; i += 1) { if (iterations[i]) iterations[i].d(detaching); } } function element(name) { return document.createElement(name); } function text(data) { return document.createTextNode(data); } function space() { return text(' '); } function listen(node, event, handler, options) { node.addEventListener(event, handler, options); return () => node.removeEventListener(event, handler, options); } function attr(node, attribute, value) { if (value == null) node.removeAttribute(attribute); else if (node.getAttribute(attribute) !== value) node.setAttribute(attribute, value); } function children(element) { return Array.from(element.childNodes); } function toggle_class(element, name, toggle) { element.classList[toggle ? 'add' : 'remove'](name); } function custom_event(type, detail) { const e = document.createEvent('CustomEvent'); e.initCustomEvent(type, false, false, detail); return e; } let current_component; function set_current_component(component) { current_component = component; } function get_current_component() { if (!current_component) throw new Error(`Function called outside component initialization`); return current_component; } function createEventDispatcher() { const component = get_current_component(); return (type, detail) => { const callbacks = component.$$.callbacks[type]; if (callbacks) { // TODO are there situations where events could be dispatched // in a server (non-DOM) environment? const event = custom_event(type, detail); callbacks.slice().forEach(fn => { fn.call(component, event); }); } }; } const dirty_components = []; const binding_callbacks = []; const render_callbacks = []; const flush_callbacks = []; const resolved_promise = Promise.resolve(); let update_scheduled = false; function schedule_update() { if (!update_scheduled) { update_scheduled = true; resolved_promise.then(flush); } } function add_render_callback(fn) { render_callbacks.push(fn); } let flushing = false; const seen_callbacks = new Set(); function flush() { if (flushing) return; flushing = true; do { // first, call beforeUpdate functions // and update components for (let i = 0; i < dirty_components.length; i += 1) { const component = dirty_components[i]; set_current_component(component); update(component.$$); } dirty_components.length = 0; while (binding_callbacks.length) binding_callbacks.pop()(); // then, once components are updated, call // afterUpdate functions. This may cause // subsequent updates... for (let i = 0; i < render_callbacks.length; i += 1) { const callback = render_callbacks[i]; if (!seen_callbacks.has(callback)) { // ...so guard against infinite loops seen_callbacks.add(callback); callback(); } } render_callbacks.length = 0; } while (dirty_components.length); while (flush_callbacks.length) { flush_callbacks.pop()(); } update_scheduled = false; flushing = false; seen_callbacks.clear(); } function update($$) { if ($$.fragment !== null) { $$.update(); run_all($$.before_update); const dirty = $$.dirty; $$.dirty = [-1]; $$.fragment && $$.fragment.p($$.ctx, dirty); $$.after_update.forEach(add_render_callback); } } const outroing = new Set(); let outros; function group_outros() { outros = { r: 0, c: [], p: outros // parent group }; } function check_outros() { if (!outros.r) { run_all(outros.c); } outros = outros.p; } function transition_in(block, local) { if (block && block.i) { outroing.delete(block); block.i(local); } } function transition_out(block, local, detach, callback) { if (block && block.o) { if (outroing.has(block)) return; outroing.add(block); outros.c.push(() => { outroing.delete(block); if (callback) { if (detach) block.d(1); callback(); } }); block.o(local); } } function mount_component(component, target, anchor) { const { fragment, on_mount, on_destroy, after_update } = component.$$; fragment && fragment.m(target, anchor); // onMount happens before the initial afterUpdate add_render_callback(() => { const new_on_destroy = on_mount.map(run).filter(is_function); if (on_destroy) { on_destroy.push(...new_on_destroy); } else { // Edge case - component was destroyed immediately, // most likely as a result of a binding initialising run_all(new_on_destroy); } component.$$.on_mount = []; }); after_update.forEach(add_render_callback); } function destroy_component(component, detaching) { const $$ = component.$$; if ($$.fragment !== null) { run_all($$.on_destroy); $$.fragment && $$.fragment.d(detaching); // TODO null out other refs, including component.$$ (but need to // preserve final state?) $$.on_destroy = $$.fragment = null; $$.ctx = []; } } function make_dirty(component, i) { if (component.$$.dirty[0] === -1) { dirty_components.push(component); schedule_update(); component.$$.dirty.fill(0); } component.$$.dirty[(i / 31) | 0] |= (1 << (i % 31)); } function init(component, options, instance, create_fragment, not_equal, props, dirty = [-1]) { const parent_component = current_component; set_current_component(component); const prop_values = options.props || {}; const $$ = component.$$ = { fragment: null, ctx: null, // state props, update: noop, not_equal, bound: blank_object(), // lifecycle on_mount: [], on_destroy: [], before_update: [], after_update: [], context: new Map(parent_component ? parent_component.$$.context : []), // everything else callbacks: blank_object(), dirty, skip_bound: false }; let ready = false; $$.ctx = instance ? instance(component, prop_values, (i, ret, ...rest) => { const value = rest.length ? rest[0] : ret; if ($$.ctx && not_equal($$.ctx[i], $$.ctx[i] = value)) { if (!$$.skip_bound && $$.bound[i]) $$.bound[i](value); if (ready) make_dirty(component, i); } return ret; }) : []; $$.update(); ready = true; run_all($$.before_update); // `false` as a special case of no DOM component $$.fragment = create_fragment ? create_fragment($$.ctx) : false; if (options.target) { if (options.hydrate) { const nodes = children(options.target); // eslint-disable-next-line @typescript-eslint/no-non-null-assertion $$.fragment && $$.fragment.l(nodes); nodes.forEach(detach); } else { // eslint-disable-next-line @typescript-eslint/no-non-null-assertion $$.fragment && $$.fragment.c(); } if (options.intro) transition_in(component.$$.fragment); mount_component(component, options.target, options.anchor); flush(); } set_current_component(parent_component); } class SvelteComponent { $destroy() { destroy_component(this, 1); this.$destroy = noop; } $on(type, callback) { const callbacks = (this.$$.callbacks[type] || (this.$$.callbacks[type] = [])); callbacks.push(callback); return () => { const index = callbacks.indexOf(callback); if (index !== -1) callbacks.splice(index, 1); }; } $set($$props) { if (this.$$set && !is_empty($$props)) { this.$$.skip_bound = true; this.$$set($$props); this.$$.skip_bound = false; } } } /*! * Glide.js v3.4.1 * (c) 2013-2019 Jędrzej Chałubek <jedrzej.chalubek@gmail.com> (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" && 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; }; var classCallCheck = function (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 (subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof 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 (self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof 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 = 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.isStart()) { Events.emit('run.start', _this.move); } if (_this.isEnd()) { Events.emit('run.end', _this.move); } 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; } } 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; } } else if (countableSteps) { Glide.index -= Math.min(Glide.index, toInt(steps)); } else { Glide.index--; } break; case '=': Glide.index = steps; break; default: warn('Invalid direction pattern [' + direction + steps + '] has been used'); 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) { var step = value.substr(1); this._m = { direction: value.substr(0, 1), steps: step ? toInt(step) ? toInt(step) : step : 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', {