UNPKG

tbg-foundation-sites

Version:

TBG fork of the most advanced responsive front-end framework in the world.

1,787 lines (1,536 loc) 122 kB
/*! * Flickity PACKAGED v2.0.5 * Touch, responsive, flickable carousels * * Licensed GPLv3 for open source use * or Flickity Commercial License for commercial use * * http://flickity.metafizzy.co * Copyright 2016 Metafizzy */ /** * Bridget makes jQuery widgets * v2.0.1 * MIT license */ /* jshint browser: true, strict: true, undef: true, unused: true */ (function (window, factory) { // universal module definition /*jshint strict: false */ /* globals define, module, require */ if (typeof define == 'function' && define.amd) { // AMD define('jquery-bridget/jquery-bridget', ['jquery'], function (jQuery) { return factory(window, jQuery); }); } else if (typeof module == 'object' && module.exports) { // CommonJS module.exports = factory(window, require('jquery')); } else { // browser global window.jQueryBridget = factory(window, window.jQuery); } })(window, function factory(window, jQuery) { 'use strict'; // ----- utils ----- // var arraySlice = Array.prototype.slice; // helper function for logging errors // $.error breaks jQuery chaining var console = window.console; var logError = typeof console == 'undefined' ? function () {} : function (message) { console.error(message); }; // ----- jQueryBridget ----- // function jQueryBridget(namespace, PluginClass, $) { $ = $ || jQuery || window.jQuery; if (!$) { return; } // add option method -> $().plugin('option', {...}) if (!PluginClass.prototype.option) { // option setter PluginClass.prototype.option = function (opts) { // bail out if not an object if (!$.isPlainObject(opts)) { return; } this.options = $.extend(true, this.options, opts); }; } // make jQuery plugin $.fn[namespace] = function (arg0 /*, arg1 */) { if (typeof arg0 == 'string') { // method call $().plugin( 'methodName', { options } ) // shift arguments by 1 var args = arraySlice.call(arguments, 1); return methodCall(this, arg0, args); } // just $().plugin({ options }) plainCall(this, arg0); return this; }; // $().plugin('methodName') function methodCall($elems, methodName, args) { var returnValue; var pluginMethodStr = '$().' + namespace + '("' + methodName + '")'; $elems.each(function (i, elem) { // get instance var instance = $.data(elem, namespace); if (!instance) { logError(namespace + ' not initialized. Cannot call methods, i.e. ' + pluginMethodStr); return; } var method = instance[methodName]; if (!method || methodName.charAt(0) == '_') { logError(pluginMethodStr + ' is not a valid method'); return; } // apply method, get return value var value = method.apply(instance, args); // set return value if value is returned, use only first value returnValue = returnValue === undefined ? value : returnValue; }); return returnValue !== undefined ? returnValue : $elems; } function plainCall($elems, options) { $elems.each(function (i, elem) { var instance = $.data(elem, namespace); if (instance) { // set options & init instance.option(options); instance._init(); } else { // initialize new instance instance = new PluginClass(elem, options); $.data(elem, namespace, instance); } }); } updateJQuery($); } // ----- updateJQuery ----- // // set $.bridget for v1 backwards compatibility function updateJQuery($) { if (!$ || $ && $.bridget) { return; } $.bridget = jQueryBridget; } updateJQuery(jQuery || window.jQuery); // ----- ----- // return jQueryBridget; }); /** * EvEmitter v1.0.3 * Lil' event emitter * MIT License */ /* jshint unused: true, undef: true, strict: true */ (function (global, factory) { // universal module definition /* jshint strict: false */ /* globals define, module, window */ if (typeof define == 'function' && define.amd) { // AMD - RequireJS define('ev-emitter/ev-emitter', factory); } else if (typeof module == 'object' && module.exports) { // CommonJS - Browserify, Webpack module.exports = factory(); } else { // Browser globals global.EvEmitter = factory(); } })(typeof window != 'undefined' ? window : this, function () { function EvEmitter() {} var proto = EvEmitter.prototype; proto.on = function (eventName, listener) { if (!eventName || !listener) { return; } // set events hash var events = this._events = this._events || {}; // set listeners array var listeners = events[eventName] = events[eventName] || []; // only add once if (listeners.indexOf(listener) == -1) { listeners.push(listener); } return this; }; proto.once = function (eventName, listener) { if (!eventName || !listener) { return; } // add event this.on(eventName, listener); // set once flag // set onceEvents hash var onceEvents = this._onceEvents = this._onceEvents || {}; // set onceListeners object var onceListeners = onceEvents[eventName] = onceEvents[eventName] || {}; // set flag onceListeners[listener] = true; return this; }; proto.off = function (eventName, listener) { var listeners = this._events && this._events[eventName]; if (!listeners || !listeners.length) { return; } var index = listeners.indexOf(listener); if (index != -1) { listeners.splice(index, 1); } return this; }; proto.emitEvent = function (eventName, args) { var listeners = this._events && this._events[eventName]; if (!listeners || !listeners.length) { return; } var i = 0; var listener = listeners[i]; args = args || []; // once stuff var onceListeners = this._onceEvents && this._onceEvents[eventName]; while (listener) { var isOnce = onceListeners && onceListeners[listener]; if (isOnce) { // remove listener // remove before trigger to prevent recursion this.off(eventName, listener); // unset once flag delete onceListeners[listener]; } // trigger listener listener.apply(this, args); // get next listener i += isOnce ? 0 : 1; listener = listeners[i]; } return this; }; return EvEmitter; }); /*! * getSize v2.0.2 * measure size of elements * MIT license */ /*jshint browser: true, strict: true, undef: true, unused: true */ /*global define: false, module: false, console: false */ (function (window, factory) { 'use strict'; if (typeof define == 'function' && define.amd) { // AMD define('get-size/get-size', [], function () { return factory(); }); } else if (typeof module == 'object' && module.exports) { // CommonJS module.exports = factory(); } else { // browser global window.getSize = factory(); } })(window, function factory() { 'use strict'; // -------------------------- helpers -------------------------- // // get a number from a string, not a percentage function getStyleSize(value) { var num = parseFloat(value); // not a percent like '100%', and a number var isValid = value.indexOf('%') == -1 && !isNaN(num); return isValid && num; } function noop() {} var logError = typeof console == 'undefined' ? noop : function (message) { console.error(message); }; // -------------------------- measurements -------------------------- // var measurements = ['paddingLeft', 'paddingRight', 'paddingTop', 'paddingBottom', 'marginLeft', 'marginRight', 'marginTop', 'marginBottom', 'borderLeftWidth', 'borderRightWidth', 'borderTopWidth', 'borderBottomWidth']; var measurementsLength = measurements.length; function getZeroSize() { var size = { width: 0, height: 0, innerWidth: 0, innerHeight: 0, outerWidth: 0, outerHeight: 0 }; for (var i = 0; i < measurementsLength; i++) { var measurement = measurements[i]; size[measurement] = 0; } return size; } // -------------------------- getStyle -------------------------- // /** * getStyle, get style of element, check for Firefox bug * https://bugzilla.mozilla.org/show_bug.cgi?id=548397 */ function getStyle(elem) { var style = getComputedStyle(elem); if (!style) { logError('Style returned ' + style + '. Are you running this code in a hidden iframe on Firefox? ' + 'See http://bit.ly/getsizebug1'); } return style; } // -------------------------- setup -------------------------- // var isSetup = false; var isBoxSizeOuter; /** * setup * check isBoxSizerOuter * do on first getSize() rather than on page load for Firefox bug */ function setup() { // setup once if (isSetup) { return; } isSetup = true; // -------------------------- box sizing -------------------------- // /** * WebKit measures the outer-width on style.width on border-box elems * IE & Firefox<29 measures the inner-width */ var div = document.createElement('div'); div.style.width = '200px'; div.style.padding = '1px 2px 3px 4px'; div.style.borderStyle = 'solid'; div.style.borderWidth = '1px 2px 3px 4px'; div.style.boxSizing = 'border-box'; var body = document.body || document.documentElement; body.appendChild(div); var style = getStyle(div); getSize.isBoxSizeOuter = isBoxSizeOuter = getStyleSize(style.width) == 200; body.removeChild(div); } // -------------------------- getSize -------------------------- // function getSize(elem) { setup(); // use querySeletor if elem is string if (typeof elem == 'string') { elem = document.querySelector(elem); } // do not proceed on non-objects if (!elem || typeof elem != 'object' || !elem.nodeType) { return; } var style = getStyle(elem); // if hidden, everything is 0 if (style.display == 'none') { return getZeroSize(); } var size = {}; size.width = elem.offsetWidth; size.height = elem.offsetHeight; var isBorderBox = size.isBorderBox = style.boxSizing == 'border-box'; // get all measurements for (var i = 0; i < measurementsLength; i++) { var measurement = measurements[i]; var value = style[measurement]; var num = parseFloat(value); // any 'auto', 'medium' value will be 0 size[measurement] = !isNaN(num) ? num : 0; } var paddingWidth = size.paddingLeft + size.paddingRight; var paddingHeight = size.paddingTop + size.paddingBottom; var marginWidth = size.marginLeft + size.marginRight; var marginHeight = size.marginTop + size.marginBottom; var borderWidth = size.borderLeftWidth + size.borderRightWidth; var borderHeight = size.borderTopWidth + size.borderBottomWidth; var isBorderBoxSizeOuter = isBorderBox && isBoxSizeOuter; // overwrite width and height if we can get it from style var styleWidth = getStyleSize(style.width); if (styleWidth !== false) { size.width = styleWidth + ( // add padding and border unless it's already including it isBorderBoxSizeOuter ? 0 : paddingWidth + borderWidth); } var styleHeight = getStyleSize(style.height); if (styleHeight !== false) { size.height = styleHeight + ( // add padding and border unless it's already including it isBorderBoxSizeOuter ? 0 : paddingHeight + borderHeight); } size.innerWidth = size.width - (paddingWidth + borderWidth); size.innerHeight = size.height - (paddingHeight + borderHeight); size.outerWidth = size.width + marginWidth; size.outerHeight = size.height + marginHeight; return size; } return getSize; }); /** * matchesSelector v2.0.1 * matchesSelector( element, '.selector' ) * MIT license */ /*jshint browser: true, strict: true, undef: true, unused: true */ (function (window, factory) { /*global define: false, module: false */ 'use strict'; // universal module definition if (typeof define == 'function' && define.amd) { // AMD define('desandro-matches-selector/matches-selector', factory); } else if (typeof module == 'object' && module.exports) { // CommonJS module.exports = factory(); } else { // browser global window.matchesSelector = factory(); } })(window, function factory() { 'use strict'; var matchesMethod = function () { var ElemProto = Element.prototype; // check for the standard method name first if (ElemProto.matches) { return 'matches'; } // check un-prefixed if (ElemProto.matchesSelector) { return 'matchesSelector'; } // check vendor prefixes var prefixes = ['webkit', 'moz', 'ms', 'o']; for (var i = 0; i < prefixes.length; i++) { var prefix = prefixes[i]; var method = prefix + 'MatchesSelector'; if (ElemProto[method]) { return method; } } }(); return function matchesSelector(elem, selector) { return elem[matchesMethod](selector); }; }); /** * Fizzy UI utils v2.0.3 * MIT license */ /*jshint browser: true, undef: true, unused: true, strict: true */ (function (window, factory) { // universal module definition /*jshint strict: false */ /*globals define, module, require */ if (typeof define == 'function' && define.amd) { // AMD define('fizzy-ui-utils/utils', ['desandro-matches-selector/matches-selector'], function (matchesSelector) { return factory(window, matchesSelector); }); } else if (typeof module == 'object' && module.exports) { // CommonJS module.exports = factory(window, require('desandro-matches-selector')); } else { // browser global window.fizzyUIUtils = factory(window, window.matchesSelector); } })(window, function factory(window, matchesSelector) { var utils = {}; // ----- extend ----- // // extends objects utils.extend = function (a, b) { for (var prop in b) { a[prop] = b[prop]; } return a; }; // ----- modulo ----- // utils.modulo = function (num, div) { return (num % div + div) % div; }; // ----- makeArray ----- // // turn element or nodeList into an array utils.makeArray = function (obj) { var ary = []; if (Array.isArray(obj)) { // use object if already an array ary = obj; } else if (obj && typeof obj.length == 'number') { // convert nodeList to array for (var i = 0; i < obj.length; i++) { ary.push(obj[i]); } } else { // array of single index ary.push(obj); } return ary; }; // ----- removeFrom ----- // utils.removeFrom = function (ary, obj) { var index = ary.indexOf(obj); if (index != -1) { ary.splice(index, 1); } }; // ----- getParent ----- // utils.getParent = function (elem, selector) { while (elem != document.body) { elem = elem.parentNode; if (matchesSelector(elem, selector)) { return elem; } } }; // ----- getQueryElement ----- // // use element as selector string utils.getQueryElement = function (elem) { if (typeof elem == 'string') { return document.querySelector(elem); } return elem; }; // ----- handleEvent ----- // // enable .ontype to trigger from .addEventListener( elem, 'type' ) utils.handleEvent = function (event) { var method = 'on' + event.type; if (this[method]) { this[method](event); } }; // ----- filterFindElements ----- // utils.filterFindElements = function (elems, selector) { // make array of elems elems = utils.makeArray(elems); var ffElems = []; elems.forEach(function (elem) { // check that elem is an actual element if (!(elem instanceof HTMLElement)) { return; } // add elem if no selector if (!selector) { ffElems.push(elem); return; } // filter & find items if we have a selector // filter if (matchesSelector(elem, selector)) { ffElems.push(elem); } // find children var childElems = elem.querySelectorAll(selector); // concat childElems to filterFound array for (var i = 0; i < childElems.length; i++) { ffElems.push(childElems[i]); } }); return ffElems; }; // ----- debounceMethod ----- // utils.debounceMethod = function (_class, methodName, threshold) { // original method var method = _class.prototype[methodName]; var timeoutName = methodName + 'Timeout'; _class.prototype[methodName] = function () { var timeout = this[timeoutName]; if (timeout) { clearTimeout(timeout); } var args = arguments; var _this = this; this[timeoutName] = setTimeout(function () { method.apply(_this, args); delete _this[timeoutName]; }, threshold || 100); }; }; // ----- docReady ----- // utils.docReady = function (callback) { var readyState = document.readyState; if (readyState == 'complete' || readyState == 'interactive') { // do async to allow for other scripts to run. metafizzy/flickity#441 setTimeout(callback); } else { document.addEventListener('DOMContentLoaded', callback); } }; // ----- htmlInit ----- // // http://jamesroberts.name/blog/2010/02/22/string-functions-for-javascript-trim-to-camel-case-to-dashed-and-to-underscore/ utils.toDashed = function (str) { return str.replace(/(.)([A-Z])/g, function (match, $1, $2) { return $1 + '-' + $2; }).toLowerCase(); }; var console = window.console; /** * allow user to initialize classes via [data-namespace] or .js-namespace class * htmlInit( Widget, 'widgetName' ) * options are parsed from data-namespace-options */ utils.htmlInit = function (WidgetClass, namespace) { utils.docReady(function () { var dashedNamespace = utils.toDashed(namespace); var dataAttr = 'data-' + dashedNamespace; var dataAttrElems = document.querySelectorAll('[' + dataAttr + ']'); var jsDashElems = document.querySelectorAll('.js-' + dashedNamespace); var elems = utils.makeArray(dataAttrElems).concat(utils.makeArray(jsDashElems)); var dataOptionsAttr = dataAttr + '-options'; var jQuery = window.jQuery; elems.forEach(function (elem) { var attr = elem.getAttribute(dataAttr) || elem.getAttribute(dataOptionsAttr); var options; try { options = attr && JSON.parse(attr); } catch (error) { // log error, do not initialize if (console) { console.error('Error parsing ' + dataAttr + ' on ' + elem.className + ': ' + error); } return; } // initialize var instance = new WidgetClass(elem, options); // make available via $().data('namespace') if (jQuery) { jQuery.data(elem, namespace, instance); } }); }); }; // ----- ----- // return utils; }); // Flickity.Cell (function (window, factory) { // universal module definition /* jshint strict: false */ if (typeof define == 'function' && define.amd) { // AMD define('flickity/js/cell', ['get-size/get-size'], function (getSize) { return factory(window, getSize); }); } else if (typeof module == 'object' && module.exports) { // CommonJS module.exports = factory(window, require('get-size')); } else { // browser global window.Flickity = window.Flickity || {}; window.Flickity.Cell = factory(window, window.getSize); } })(window, function factory(window, getSize) { function Cell(elem, parent) { this.element = elem; this.parent = parent; this.create(); } var proto = Cell.prototype; proto.create = function () { this.element.style.position = 'absolute'; this.x = 0; this.shift = 0; }; proto.destroy = function () { // reset style this.element.style.position = ''; var side = this.parent.originSide; this.element.style[side] = ''; }; proto.getSize = function () { this.size = getSize(this.element); }; proto.setPosition = function (x) { this.x = x; this.updateTarget(); this.renderPosition(x); }; // setDefaultTarget v1 method, backwards compatibility, remove in v3 proto.updateTarget = proto.setDefaultTarget = function () { var marginProperty = this.parent.originSide == 'left' ? 'marginLeft' : 'marginRight'; this.target = this.x + this.size[marginProperty] + this.size.width * this.parent.cellAlign; }; proto.renderPosition = function (x) { // render position of cell with in slider var side = this.parent.originSide; this.element.style[side] = this.parent.getPositionValue(x); }; /** * @param {Integer} factor - 0, 1, or -1 **/ proto.wrapShift = function (shift) { this.shift = shift; this.renderPosition(this.x + this.parent.slideableWidth * shift); }; proto.remove = function () { this.element.parentNode.removeChild(this.element); }; return Cell; }); // slide (function (window, factory) { // universal module definition /* jshint strict: false */ if (typeof define == 'function' && define.amd) { // AMD define('flickity/js/slide', factory); } else if (typeof module == 'object' && module.exports) { // CommonJS module.exports = factory(); } else { // browser global window.Flickity = window.Flickity || {}; window.Flickity.Slide = factory(); } })(window, function factory() { 'use strict'; function Slide(parent) { this.parent = parent; this.isOriginLeft = parent.originSide == 'left'; this.cells = []; this.outerWidth = 0; this.height = 0; } var proto = Slide.prototype; proto.addCell = function (cell) { this.cells.push(cell); this.outerWidth += cell.size.outerWidth; this.height = Math.max(cell.size.outerHeight, this.height); // first cell stuff if (this.cells.length == 1) { this.x = cell.x; // x comes from first cell var beginMargin = this.isOriginLeft ? 'marginLeft' : 'marginRight'; this.firstMargin = cell.size[beginMargin]; } }; proto.updateTarget = function () { var endMargin = this.isOriginLeft ? 'marginRight' : 'marginLeft'; var lastCell = this.getLastCell(); var lastMargin = lastCell ? lastCell.size[endMargin] : 0; var slideWidth = this.outerWidth - (this.firstMargin + lastMargin); this.target = this.x + this.firstMargin + slideWidth * this.parent.cellAlign; }; proto.getLastCell = function () { return this.cells[this.cells.length - 1]; }; proto.select = function () { this.changeSelectedClass('add'); }; proto.unselect = function () { this.changeSelectedClass('remove'); }; proto.changeSelectedClass = function (method) { this.cells.forEach(function (cell) { cell.element.classList[method]('is-selected'); }); }; proto.getCellElements = function () { return this.cells.map(function (cell) { return cell.element; }); }; return Slide; }); // animate (function (window, factory) { // universal module definition /* jshint strict: false */ if (typeof define == 'function' && define.amd) { // AMD define('flickity/js/animate', ['fizzy-ui-utils/utils'], function (utils) { return factory(window, utils); }); } else if (typeof module == 'object' && module.exports) { // CommonJS module.exports = factory(window, require('fizzy-ui-utils')); } else { // browser global window.Flickity = window.Flickity || {}; window.Flickity.animatePrototype = factory(window, window.fizzyUIUtils); } })(window, function factory(window, utils) { // -------------------------- requestAnimationFrame -------------------------- // // get rAF, prefixed, if present var requestAnimationFrame = window.requestAnimationFrame || window.webkitRequestAnimationFrame; // fallback to setTimeout var lastTime = 0; if (!requestAnimationFrame) { requestAnimationFrame = function (callback) { var currTime = new Date().getTime(); var timeToCall = Math.max(0, 16 - (currTime - lastTime)); var id = setTimeout(callback, timeToCall); lastTime = currTime + timeToCall; return id; }; } // -------------------------- animate -------------------------- // var proto = {}; proto.startAnimation = function () { if (this.isAnimating) { return; } this.isAnimating = true; this.restingFrames = 0; this.animate(); }; proto.animate = function () { this.applyDragForce(); this.applySelectedAttraction(); var previousX = this.x; this.integratePhysics(); this.positionSlider(); this.settle(previousX); // animate next frame if (this.isAnimating) { var _this = this; requestAnimationFrame(function animateFrame() { _this.animate(); }); } }; var transformProperty = function () { var style = document.documentElement.style; if (typeof style.transform == 'string') { return 'transform'; } return 'WebkitTransform'; }(); proto.positionSlider = function () { var x = this.x; // wrap position around if (this.options.wrapAround && this.cells.length > 1) { x = utils.modulo(x, this.slideableWidth); x = x - this.slideableWidth; this.shiftWrapCells(x); } x = x + this.cursorPosition; // reverse if right-to-left and using transform x = this.options.rightToLeft && transformProperty ? -x : x; var value = this.getPositionValue(x); // use 3D tranforms for hardware acceleration on iOS // but use 2D when settled, for better font-rendering this.slider.style[transformProperty] = this.isAnimating ? 'translate3d(' + value + ',0,0)' : 'translateX(' + value + ')'; // scroll event var firstSlide = this.slides[0]; if (firstSlide) { var positionX = -this.x - firstSlide.target; var progress = positionX / this.slidesWidth; this.dispatchEvent('scroll', null, [progress, positionX]); } }; proto.positionSliderAtSelected = function () { if (!this.cells.length) { return; } this.x = -this.selectedSlide.target; this.positionSlider(); }; proto.getPositionValue = function (position) { if (this.options.percentPosition) { // percent position, round to 2 digits, like 12.34% return Math.round(position / this.size.innerWidth * 10000) * 0.01 + '%'; } else { // pixel positioning return Math.round(position) + 'px'; } }; proto.settle = function (previousX) { // keep track of frames where x hasn't moved if (!this.isPointerDown && Math.round(this.x * 100) == Math.round(previousX * 100)) { this.restingFrames++; } // stop animating if resting for 3 or more frames if (this.restingFrames > 2) { this.isAnimating = false; delete this.isFreeScrolling; // render position with translateX when settled this.positionSlider(); this.dispatchEvent('settle'); } }; proto.shiftWrapCells = function (x) { // shift before cells var beforeGap = this.cursorPosition + x; this._shiftCells(this.beforeShiftCells, beforeGap, -1); // shift after cells var afterGap = this.size.innerWidth - (x + this.slideableWidth + this.cursorPosition); this._shiftCells(this.afterShiftCells, afterGap, 1); }; proto._shiftCells = function (cells, gap, shift) { for (var i = 0; i < cells.length; i++) { var cell = cells[i]; var cellShift = gap > 0 ? shift : 0; cell.wrapShift(cellShift); gap -= cell.size.outerWidth; } }; proto._unshiftCells = function (cells) { if (!cells || !cells.length) { return; } for (var i = 0; i < cells.length; i++) { cells[i].wrapShift(0); } }; // -------------------------- physics -------------------------- // proto.integratePhysics = function () { this.x += this.velocity; this.velocity *= this.getFrictionFactor(); }; proto.applyForce = function (force) { this.velocity += force; }; proto.getFrictionFactor = function () { return 1 - this.options[this.isFreeScrolling ? 'freeScrollFriction' : 'friction']; }; proto.getRestingPosition = function () { // my thanks to Steven Wittens, who simplified this math greatly return this.x + this.velocity / (1 - this.getFrictionFactor()); }; proto.applyDragForce = function () { if (!this.isPointerDown) { return; } // change the position to drag position by applying force var dragVelocity = this.dragX - this.x; var dragForce = dragVelocity - this.velocity; this.applyForce(dragForce); }; proto.applySelectedAttraction = function () { // do not attract if pointer down or no cells if (this.isPointerDown || this.isFreeScrolling || !this.cells.length) { return; } var distance = this.selectedSlide.target * -1 - this.x; var force = distance * this.options.selectedAttraction; this.applyForce(force); }; return proto; }); // Flickity main (function (window, factory) { // universal module definition /* jshint strict: false */ if (typeof define == 'function' && define.amd) { // AMD define('flickity/js/flickity', ['ev-emitter/ev-emitter', 'get-size/get-size', 'fizzy-ui-utils/utils', './cell', './slide', './animate'], function (EvEmitter, getSize, utils, Cell, Slide, animatePrototype) { return factory(window, EvEmitter, getSize, utils, Cell, Slide, animatePrototype); }); } else if (typeof module == 'object' && module.exports) { // CommonJS module.exports = factory(window, require('ev-emitter'), require('get-size'), require('fizzy-ui-utils'), require('./cell'), require('./slide'), require('./animate')); } else { // browser global var _Flickity = window.Flickity; window.Flickity = factory(window, window.EvEmitter, window.getSize, window.fizzyUIUtils, _Flickity.Cell, _Flickity.Slide, _Flickity.animatePrototype); } })(window, function factory(window, EvEmitter, getSize, utils, Cell, Slide, animatePrototype) { // vars var jQuery = window.jQuery; var getComputedStyle = window.getComputedStyle; var console = window.console; function moveElements(elems, toElem) { elems = utils.makeArray(elems); while (elems.length) { toElem.appendChild(elems.shift()); } } // -------------------------- Flickity -------------------------- // // globally unique identifiers var GUID = 0; // internal store of all Flickity intances var instances = {}; function Flickity(element, options) { var queryElement = utils.getQueryElement(element); if (!queryElement) { if (console) { console.error('Bad element for Flickity: ' + (queryElement || element)); } return; } this.element = queryElement; // do not initialize twice on same element if (this.element.flickityGUID) { var instance = instances[this.element.flickityGUID]; instance.option(options); return instance; } // add jQuery if (jQuery) { this.$element = jQuery(this.element); } // options this.options = utils.extend({}, this.constructor.defaults); this.option(options); // kick things off this._create(); } Flickity.defaults = { accessibility: true, // adaptiveHeight: false, cellAlign: 'center', // cellSelector: undefined, // contain: false, freeScrollFriction: 0.075, // friction when free-scrolling friction: 0.28, // friction when selecting namespaceJQueryEvents: true, // initialIndex: 0, percentPosition: true, resize: true, selectedAttraction: 0.025, setGallerySize: true // watchCSS: false, // wrapAround: false }; // hash of methods triggered on _create() Flickity.createMethods = []; var proto = Flickity.prototype; // inherit EventEmitter utils.extend(proto, EvEmitter.prototype); proto._create = function () { // add id for Flickity.data var id = this.guid = ++GUID; this.element.flickityGUID = id; // expando instances[id] = this; // associate via id // initial properties this.selectedIndex = 0; // how many frames slider has been in same position this.restingFrames = 0; // initial physics properties this.x = 0; this.velocity = 0; this.originSide = this.options.rightToLeft ? 'right' : 'left'; // create viewport & slider this.viewport = document.createElement('div'); this.viewport.className = 'flickity-viewport'; this._createSlider(); if (this.options.resize || this.options.watchCSS) { window.addEventListener('resize', this); } Flickity.createMethods.forEach(function (method) { this[method](); }, this); if (this.options.watchCSS) { this.watchCSS(); } else { this.activate(); } }; /** * set options * @param {Object} opts */ proto.option = function (opts) { utils.extend(this.options, opts); }; proto.activate = function () { if (this.isActive) { return; } this.isActive = true; this.element.classList.add('flickity-enabled'); if (this.options.rightToLeft) { this.element.classList.add('flickity-rtl'); } this.getSize(); // move initial cell elements so they can be loaded as cells var cellElems = this._filterFindCellElements(this.element.children); moveElements(cellElems, this.slider); this.viewport.appendChild(this.slider); this.element.appendChild(this.viewport); // get cells from children this.reloadCells(); if (this.options.accessibility) { // allow element to focusable this.element.tabIndex = 0; // listen for key presses this.element.addEventListener('keydown', this); } this.emitEvent('activate'); var index; var initialIndex = this.options.initialIndex; if (this.isInitActivated) { index = this.selectedIndex; } else if (initialIndex !== undefined) { index = this.cells[initialIndex] ? initialIndex : 0; } else { index = 0; } // select instantly this.select(index, false, true); // flag for initial activation, for using initialIndex this.isInitActivated = true; }; // slider positions the cells proto._createSlider = function () { // slider element does all the positioning var slider = document.createElement('div'); slider.className = 'flickity-slider'; slider.style[this.originSide] = 0; this.slider = slider; }; proto._filterFindCellElements = function (elems) { return utils.filterFindElements(elems, this.options.cellSelector); }; // goes through all children proto.reloadCells = function () { // collection of item elements this.cells = this._makeCells(this.slider.children); this.positionCells(); this._getWrapShiftCells(); this.setGallerySize(); }; /** * turn elements into Flickity.Cells * @param {Array or NodeList or HTMLElement} elems * @returns {Array} items - collection of new Flickity Cells */ proto._makeCells = function (elems) { var cellElems = this._filterFindCellElements(elems); // create new Flickity for collection var cells = cellElems.map(function (cellElem) { return new Cell(cellElem, this); }, this); return cells; }; proto.getLastCell = function () { return this.cells[this.cells.length - 1]; }; proto.getLastSlide = function () { return this.slides[this.slides.length - 1]; }; // positions all cells proto.positionCells = function () { // size all cells this._sizeCells(this.cells); // position all cells this._positionCells(0); }; /** * position certain cells * @param {Integer} index - which cell to start with */ proto._positionCells = function (index) { index = index || 0; // also measure maxCellHeight // start 0 if positioning all cells this.maxCellHeight = index ? this.maxCellHeight || 0 : 0; var cellX = 0; // get cellX if (index > 0) { var startCell = this.cells[index - 1]; cellX = startCell.x + startCell.size.outerWidth; } var len = this.cells.length; for (var i = index; i < len; i++) { var cell = this.cells[i]; cell.setPosition(cellX); cellX += cell.size.outerWidth; this.maxCellHeight = Math.max(cell.size.outerHeight, this.maxCellHeight); } // keep track of cellX for wrap-around this.slideableWidth = cellX; // slides this.updateSlides(); // contain slides target this._containSlides(); // update slidesWidth this.slidesWidth = len ? this.getLastSlide().target - this.slides[0].target : 0; }; /** * cell.getSize() on multiple cells * @param {Array} cells */ proto._sizeCells = function (cells) { cells.forEach(function (cell) { cell.getSize(); }); }; // -------------------------- -------------------------- // proto.updateSlides = function () { this.slides = []; if (!this.cells.length) { return; } var slide = new Slide(this); this.slides.push(slide); var isOriginLeft = this.originSide == 'left'; var nextMargin = isOriginLeft ? 'marginRight' : 'marginLeft'; var canCellFit = this._getCanCellFit(); this.cells.forEach(function (cell, i) { // just add cell if first cell in slide if (!slide.cells.length) { slide.addCell(cell); return; } var slideWidth = slide.outerWidth - slide.firstMargin + (cell.size.outerWidth - cell.size[nextMargin]); if (canCellFit.call(this, i, slideWidth)) { slide.addCell(cell); } else { // doesn't fit, new slide slide.updateTarget(); slide = new Slide(this); this.slides.push(slide); slide.addCell(cell); } }, this); // last slide slide.updateTarget(); // update .selectedSlide this.updateSelectedSlide(); }; proto._getCanCellFit = function () { var groupCells = this.options.groupCells; if (!groupCells) { return function () { return false; }; } else if (typeof groupCells == 'number') { // group by number. 3 -> [0,1,2], [3,4,5], ... var number = parseInt(groupCells, 10); return function (i) { return i % number !== 0; }; } // default, group by width of slide // parse '75% var percentMatch = typeof groupCells == 'string' && groupCells.match(/^(\d+)%$/); var percent = percentMatch ? parseInt(percentMatch[1], 10) / 100 : 1; return function (i, slideWidth) { return slideWidth <= (this.size.innerWidth + 1) * percent; }; }; // alias _init for jQuery plugin .flickity() proto._init = proto.reposition = function () { this.positionCells(); this.positionSliderAtSelected(); }; proto.getSize = function () { this.size = getSize(this.element); this.setCellAlign(); this.cursorPosition = this.size.innerWidth * this.cellAlign; }; var cellAlignShorthands = { // cell align, then based on origin side center: { left: 0.5, right: 0.5 }, left: { left: 0, right: 1 }, right: { right: 0, left: 1 } }; proto.setCellAlign = function () { var shorthand = cellAlignShorthands[this.options.cellAlign]; this.cellAlign = shorthand ? shorthand[this.originSide] : this.options.cellAlign; }; proto.setGallerySize = function () { if (this.options.setGallerySize) { var height = this.options.adaptiveHeight && this.selectedSlide ? this.selectedSlide.height : this.maxCellHeight; this.viewport.style.height = height + 'px'; } }; proto._getWrapShiftCells = function () { // only for wrap-around if (!this.options.wrapAround) { return; } // unshift previous cells this._unshiftCells(this.beforeShiftCells); this._unshiftCells(this.afterShiftCells); // get before cells // initial gap var gapX = this.cursorPosition; var cellIndex = this.cells.length - 1; this.beforeShiftCells = this._getGapCells(gapX, cellIndex, -1); // get after cells // ending gap between last cell and end of gallery viewport gapX = this.size.innerWidth - this.cursorPosition; // start cloning at first cell, working forwards this.afterShiftCells = this._getGapCells(gapX, 0, 1); }; proto._getGapCells = function (gapX, cellIndex, increment) { // keep adding cells until the cover the initial gap var cells = []; while (gapX > 0) { var cell = this.cells[cellIndex]; if (!cell) { break; } cells.push(cell); cellIndex += increment; gapX -= cell.size.outerWidth; } return cells; }; // ----- contain ----- // // contain cell targets so no excess sliding proto._containSlides = function () { if (!this.options.contain || this.options.wrapAround || !this.cells.length) { return; } var isRightToLeft = this.options.rightToLeft; var beginMargin = isRightToLeft ? 'marginRight' : 'marginLeft'; var endMargin = isRightToLeft ? 'marginLeft' : 'marginRight'; var contentWidth = this.slideableWidth - this.getLastCell().size[endMargin]; // content is less than gallery size var isContentSmaller = contentWidth < this.size.innerWidth; // bounds var beginBound = this.cursorPosition + this.cells[0].size[beginMargin]; var endBound = contentWidth - this.size.innerWidth * (1 - this.cellAlign); // contain each cell target this.slides.forEach(function (slide) { if (isContentSmaller) { // all cells fit inside gallery slide.target = contentWidth * this.cellAlign; } else { // contain to bounds slide.target = Math.max(slide.target, beginBound); slide.target = Math.min(slide.target, endBound); } }, this); }; // ----- ----- // /** * emits events via eventEmitter and jQuery events * @param {String} type - name of event * @param {Event} event - original event * @param {Array} args - extra arguments */ proto.dispatchEvent = function (type, event, args) { var emitArgs = event ? [event].concat(args) : args; this.emitEvent(type, emitArgs); if (jQuery && this.$element) { // default trigger with type if no event type += this.options.namespaceJQueryEvents ? '.flickity' : ''; var $event = type; if (event) { // create jQuery event var jQEvent = jQuery.Event(event); jQEvent.type = type; $event = jQEvent; } this.$element.trigger($event, args); } }; // -------------------------- select -------------------------- // /** * @param {Integer} index - index of the slide * @param {Boolean} isWrap - will wrap-around to last/first if at the end * @param {Boolean} isInstant - will immediately set position at selected cell */ proto.select = function (index, isWrap, isInstant) { if (!this.isActive) { return; } index = parseInt(index, 10); this._wrapSelect(index); if (this.options.wrapAround || isWrap) { index = utils.modulo(index, this.slides.length); } // bail if invalid index if (!this.slides[index]) { return; } this.selectedIndex = index; this.updateSelectedSlide(); if (isInstant) { this.positionSliderAtSelected(); } else { this.startAnimation(); } if (this.options.adaptiveHeight) { this.setGallerySize(); } this.dispatchEvent('select'); // old v1 event name, remove in v3 this.dispatchEvent('cellSelect'); }; // wraps position for wrapAround, to move to closest slide. #113 proto._wrapSelect = function (index) { var len = this.slides.length; var isWrapping = this.options.wrapAround && len > 1; if (!isWrapping) { return index; } var wrapIndex = utils.modulo(index, len); // go to shortest var delta = Math.abs(wrapIndex - this.selectedIndex); var backWrapDelta = Math.abs(wrapIndex + len - this.selectedIndex); var forewardWrapDelta = Math.abs(wrapIndex - len - this.selectedIndex); if (!this.isDragSelect && backWrapDelta < delta) { index += len; } else if (!this.isDragSelect && forewardWrapDelta < delta) { index -= len; } // wrap position so slider is within normal area if (index < 0) { this.x -= this.slideableWidth; } else if (index >= len) { this.x += this.slideableWidth; } }; proto.previous = function (isWrap, isInstant) { this.select(this.selectedIndex - 1, isWrap, isInstant); }; proto.next = function (isWrap, isInstant) { this.select(this.selectedIndex + 1, isWrap, isInstant); }; proto.updateSelectedSlide = function () { var slide = this.slides[this.selectedIndex]; // selectedIndex could be outside of slides, if triggered before resize() if (!slide) { return; } // unselect previous selected slide this.unselectSelectedSlide(); // update new selected slide this.selectedSlide = slide; slide.select(); this.selectedCells = slide.cells; this.selectedElements = slide.getCellElements(); // HACK: selectedCell & selectedElement is first cell in slide, backwards compatibility // Remove in v3? this.selectedCell = slide.cells[0]; this.selectedElement = this.selectedElements[0]; }; proto.unselectSelectedSlide = function () { if (this.selectedSlide) { this.selectedSlide.unselect(); } }; /** * select slide from number or cell element * @param {Element or Number} elem */ proto.selectCell = function (value, isWrap, isInstant) { // get cell var cell; if (typeof value == 'number') { cell = this.cells[value]; } else { // use string as selector if (typeof value == 'string') { value = this.element.querySelector(value); } // get cell from element cell = this.getCell(value); } // select slide that has cell for (var i = 0; cell && i < this.slides.length; i++) { var slide = this.slides[i]; var index = slide.cells.indexOf(cell); if (index != -1) { this.select(i, isWrap, isInstant); return; } } }; // -------------------------- get cells -------------------------- // /** * get Flickity.Cell, given an Element * @param {Element} elem * @returns {Flickity.Cell} item */ proto.getCell = function (elem) { // loop through cells to get the one that matches for (var i = 0; i < this.cells.length; i++) { var cell = this.cells[i]; if (cell.element == elem) { return cell; } } }; /** * get collection of Flickity.Cells, given Elements * @param {Element, Array, NodeList} elems * @returns {Array} cells - Flickity.Cells */ proto.getCells = function (elems) { elems = utils.makeArray(elems); var cells = []; elems.forEach(function (elem) { var cell = this.getCell(elem); if (cell) { cells.push(cell); } }, this); return cells; }; /** * get cell elements * @returns {Array} cellElems */ proto.getCellElements = function () { return this.cells.map(function (cell) { return cell.element; }); }; /** * get parent cell from an element * @param {Element} elem * @returns {Flickit.Cell} cell */ proto.getParentCell = function (elem) { // first check if elem is cell var cell = this.getCell(elem); if (cell) { return cell; } // try to get parent cell elem elem = utils.getParent(elem, '.flickity-slider > *'); return this.getCell(elem); }; /** * get cells adjacent to a slide * @param {Integer} adjCount - number of adjacent slides * @param {Integer} index - index of slide to start * @returns {Array} cells - array of Flickity.Cells */ proto.getAdjacentCellElements = function (adjCount, index) { if (!adjCount) { return this.selectedSlide.getCellElements(); } index = index === undefined ? this.selectedIndex : index; var len = this.slides.length; if (1 + adjCount * 2 >= len) { return this.getCellElements(); } var cellElems = []; for (var i = index - adjCount; i <= index + adjCount; i++) { var slideIndex = this.options.wrapAround ? utils.modulo(i, len) : i; var slide = this.slides[slideIndex]; if (slide) { cellElems = cellElems.concat(slide.getCellElements()); } } return cellElems; }; // -------------------------- events -------------------------- // proto.uiChange = function () { this.emitEvent('uiChange'); }; proto.childUIPointerDown = function (event) { this.emitEvent('childUIPointerDown', [event]); }; // ----- resize ----- // proto.onresize = function () { this.watchCSS(); this.resize(); }; utils.debounceMethod(Flickity, 'onresize', 150); proto.resize = function () { if (!this.isActive) { return; } this.getSize(); // wrap values if (this.options.wrapAround) { this.x = utils.modulo(this.x, this.slideableWidth); } this.positionCells(); this._getWrapShiftCells(); this.setGallerySize(); this.emitEvent('resize'); // update selected index for group slides, instant // TODO: position can be lost between groups of various numbers var selectedElement = this.selectedElements && this.selectedElements[0]; this.select