diff options
Diffstat (limited to 'www/external/origjs')
| -rw-r--r-- | www/external/origjs/NeuQuant.js | 389 | ||||
| -rw-r--r-- | www/external/origjs/README.TXT | 3 | ||||
| -rw-r--r-- | www/external/origjs/angular-carousel.js | 2286 | ||||
| -rw-r--r-- | www/external/origjs/angular-circular-navigation.js | 70 | ||||
| -rw-r--r-- | www/external/origjs/angular-ios9-uiwebview.patch.js | 73 | ||||
| -rw-r--r-- | www/external/origjs/canvas-toBlob.js | 124 | ||||
| -rw-r--r-- | www/external/origjs/draggabilly.pkgd.js | 1530 | ||||
| -rw-r--r-- | www/external/origjs/gifwriter.js | 406 | ||||
| -rw-r--r-- | www/external/origjs/imagesloaded.pkgd.js | 487 | ||||
| -rwxr-xr-x | www/external/origjs/ion-pullup.js | 261 | ||||
| -rw-r--r-- | www/external/origjs/ionRadio.js | 55 | ||||
| -rw-r--r-- | www/external/origjs/ionic.content.banner.js | 190 | ||||
| -rw-r--r-- | www/external/origjs/ionic.scroll.sista.js | 321 | ||||
| -rw-r--r-- | www/external/origjs/ng-websocket.js | 381 | ||||
| -rw-r--r-- | www/external/origjs/packery.pkgd.js | 3413 |
15 files changed, 9989 insertions, 0 deletions
diff --git a/www/external/origjs/NeuQuant.js b/www/external/origjs/NeuQuant.js new file mode 100644 index 00000000..f0c124ba --- /dev/null +++ b/www/external/origjs/NeuQuant.js @@ -0,0 +1,389 @@ +NeuQuant = function () { + function NeuQuant() { + var netsize = 256; + var prime1 = 499; + var prime2 = 491; + var prime3 = 487; + var prime4 = 503; + var minpicturebytes = 3 * prime4; + var maxnetpos = netsize - 1; + var netbiasshift = 4; + var ncycles = 100; + var intbiasshift = 16; + var intbias = 1 << intbiasshift; + var gammashift = 10; + var gamma = 1 << gammashift; + var betashift = 10; + var beta = intbias >> betashift; + var betagamma = intbias << gammashift - betashift; + var initrad = netsize >> 3; + var radiusbiasshift = 6; + var radiusbias = 1 << radiusbiasshift; + var initradius = initrad * radiusbias; + var radiusdec = 30; + var alphabiasshift = 10; + var initalpha = 1 << alphabiasshift; + var alphadec; + var radbiasshift = 8; + var radbias = 1 << radbiasshift; + var alpharadbshift = alphabiasshift + radbiasshift; + var alpharadbias = 1 << alpharadbshift; + var thepicture; + var lengthcount; + var samplefac; + var network; + var netindex = []; + var bias = []; + var freq = []; + var radpower = []; + function NeuQuantConstructor(thepic, len, sample) { + var i; + var p; + thepicture = thepic; + lengthcount = len; + samplefac = sample; + network = new Array(netsize); + for (i = 0; i < netsize; i++) { + network[i] = new Array(4); + p = network[i]; + p[0] = p[1] = p[2] = (i << netbiasshift + 8) / netsize | 0; + freq[i] = intbias / netsize | 0; + bias[i] = 0; + } + } + function colorMap() { + var map = []; + var index = new Array(netsize); + for (var i = 0; i < netsize; i++) + index[network[i][3]] = i; + var k = 0; + for (var l = 0; l < netsize; l++) { + var j = index[l]; + map[k++] = network[j][0]; + map[k++] = network[j][1]; + map[k++] = network[j][2]; + } + return map; + } + function inxbuild() { + var i; + var j; + var smallpos; + var smallval; + var p; + var q; + var previouscol; + var startpos; + previouscol = 0; + startpos = 0; + for (i = 0; i < netsize; i++) { + p = network[i]; + smallpos = i; + smallval = p[1]; + for (j = i + 1; j < netsize; j++) { + q = network[j]; + if (q[1] < smallval) { + smallpos = j; + smallval = q[1]; + } + } + q = network[smallpos]; + if (i != smallpos) { + j = q[0]; + q[0] = p[0]; + p[0] = j; + j = q[1]; + q[1] = p[1]; + p[1] = j; + j = q[2]; + q[2] = p[2]; + p[2] = j; + j = q[3]; + q[3] = p[3]; + p[3] = j; + } + if (smallval != previouscol) { + netindex[previouscol] = startpos + i >> 1; + for (j = previouscol + 1; j < smallval; j++) { + netindex[j] = i; + } + previouscol = smallval; + startpos = i; + } + } + netindex[previouscol] = startpos + maxnetpos >> 1; + for (j = previouscol + 1; j < 256; j++) { + netindex[j] = maxnetpos; + } + } + function learn() { + var i; + var j; + var b; + var g; + var r; + var radius; + var rad; + var alpha; + var step; + var delta; + var samplepixels; + var p; + var pix; + var lim; + if (lengthcount < minpicturebytes) { + samplefac = 1; + } + alphadec = 30 + (samplefac - 1) / 3; + p = thepicture; + pix = 0; + lim = lengthcount; + samplepixels = lengthcount / (3 * samplefac); + delta = samplepixels / ncycles | 0; + alpha = initalpha; + radius = initradius; + rad = radius >> radiusbiasshift; + if (rad <= 1) { + rad = 0; + } + for (i = 0; i < rad; i++) { + radpower[i] = alpha * ((rad * rad - i * i) * radbias / (rad * rad)); + } + if (lengthcount < minpicturebytes) { + step = 3; + } else if (lengthcount % prime1 !== 0) { + step = 3 * prime1; + } else { + if (lengthcount % prime2 !== 0) { + step = 3 * prime2; + } else { + if (lengthcount % prime3 !== 0) { + step = 3 * prime3; + } else { + step = 3 * prime4; + } + } + } + i = 0; + while (i < samplepixels) { + b = (p[pix + 0] & 255) << netbiasshift; + g = (p[pix + 1] & 255) << netbiasshift; + r = (p[pix + 2] & 255) << netbiasshift; + j = contest(b, g, r); + altersingle(alpha, j, b, g, r); + if (rad !== 0) { + alterneigh(rad, j, b, g, r); + } + pix += step; + if (pix >= lim) { + pix -= lengthcount; + } + i++; + if (delta === 0) { + delta = 1; + } + if (i % delta === 0) { + alpha -= alpha / alphadec; + radius -= radius / radiusdec; + rad = radius >> radiusbiasshift; + if (rad <= 1) { + rad = 0; + } + for (j = 0; j < rad; j++) { + radpower[j] = alpha * ((rad * rad - j * j) * radbias / (rad * rad)); + } + } + } + } + function map(b, g, r) { + var i; + var j; + var dist; + var a; + var bestd; + var p; + var best; + bestd = 1000; + best = -1; + i = netindex[g]; + j = i - 1; + while (i < netsize || j >= 0) { + if (i < netsize) { + p = network[i]; + dist = p[1] - g; + if (dist >= bestd) { + i = netsize; + } else { + i++; + if (dist < 0) { + dist = -dist; + } + a = p[0] - b; + if (a < 0) { + a = -a; + } + dist += a; + if (dist < bestd) { + a = p[2] - r; + if (a < 0) { + a = -a; + } + dist += a; + if (dist < bestd) { + bestd = dist; + best = p[3]; + } + } + } + } + if (j >= 0) { + p = network[j]; + dist = g - p[1]; + if (dist >= bestd) { + j = -1; + } else { + j--; + if (dist < 0) { + dist = -dist; + } + a = p[0] - b; + if (a < 0) { + a = -a; + } + dist += a; + if (dist < bestd) { + a = p[2] - r; + if (a < 0) { + a = -a; + } + dist += a; + if (dist < bestd) { + bestd = dist; + best = p[3]; + } + } + } + } + } + return best; + } + function process() { + learn(); + unbiasnet(); + inxbuild(); + return colorMap(); + } + function unbiasnet() { + var i; + var j; + for (i = 0; i < netsize; i++) { + network[i][0] >>= netbiasshift; + network[i][1] >>= netbiasshift; + network[i][2] >>= netbiasshift; + network[i][3] = i; + } + } + function alterneigh(rad, i, b, g, r) { + var j; + var k; + var lo; + var hi; + var a; + var m; + var p; + lo = i - rad; + if (lo < -1) { + lo = -1; + } + hi = i + rad; + if (hi > netsize) { + hi = netsize; + } + j = i + 1; + k = i - 1; + m = 1; + while (j < hi || k > lo) { + a = radpower[m++]; + if (j < hi) { + p = network[j++]; + try { + p[0] -= a * (p[0] - b) / alpharadbias | 0; + p[1] -= a * (p[1] - g) / alpharadbias | 0; + p[2] -= a * (p[2] - r) / alpharadbias | 0; + } catch (e) { + } + } + if (k > lo) { + p = network[k--]; + try { + p[0] -= a * (p[0] - b) / alpharadbias | 0; + p[1] -= a * (p[1] - g) / alpharadbias | 0; + p[2] -= a * (p[2] - r) / alpharadbias | 0; + } catch (e) { + } + } + } + } + function altersingle(alpha, i, b, g, r) { + var n = network[i]; + var alphaMult = alpha / initalpha; + n[0] -= alphaMult * (n[0] - b) | 0; + n[1] -= alphaMult * (n[1] - g) | 0; + n[2] -= alphaMult * (n[2] - r) | 0; + } + function contest(b, g, r) { + var i; + var dist; + var a; + var biasdist; + var betafreq; + var bestpos; + var bestbiaspos; + var bestd; + var bestbiasd; + var n; + bestd = ~(1 << 31); + bestbiasd = bestd; + bestpos = -1; + bestbiaspos = bestpos; + for (i = 0; i < netsize; i++) { + n = network[i]; + dist = n[0] - b; + if (dist < 0) { + dist = -dist; + } + a = n[1] - g; + if (a < 0) { + a = -a; + } + dist += a; + a = n[2] - r; + if (a < 0) { + a = -a; + } + dist += a; + if (dist < bestd) { + bestd = dist; + bestpos = i; + } + biasdist = dist - (bias[i] >> intbiasshift - netbiasshift); + if (biasdist < bestbiasd) { + bestbiasd = biasdist; + bestbiaspos = i; + } + betafreq = freq[i] >> betashift; + freq[i] -= betafreq; + bias[i] += betafreq << gammashift; + } + freq[bestpos] += beta; + bias[bestpos] -= betagamma; + return bestbiaspos; + } + NeuQuantConstructor.apply(this, arguments); + var exports = {}; + exports.map = map; + exports.process = process; + return exports; + } + return NeuQuant; +}();
\ No newline at end of file diff --git a/www/external/origjs/README.TXT b/www/external/origjs/README.TXT new file mode 100644 index 00000000..19a2b36d --- /dev/null +++ b/www/external/origjs/README.TXT @@ -0,0 +1,3 @@ +These are original JS files. +I minified then in externals/ using npm minify +~pp diff --git a/www/external/origjs/angular-carousel.js b/www/external/origjs/angular-carousel.js new file mode 100644 index 00000000..48c94258 --- /dev/null +++ b/www/external/origjs/angular-carousel.js @@ -0,0 +1,2286 @@ + +//Angular Carousel - Mobile friendly touch carousel for AngularJS +//@version v0.3.12 - 2015-06-11 +//@link http://revolunet.github.com/angular-carousel +//@author Julien Bouquillon <julien@revolunet.com> +//@license MIT License, http://www.opensource.org/licenses/MIT + + + +//Angular touch carousel with CSS GPU accel and slide buffering +//http://github.com/revolunet/angular-carousel + + +// Modified by PP for mobile friendly touch, start without auto slide but enabled it if tapped +// and logic to wait for an image to load before it slides to the next one + +/* jshint ignore:start */ +/*global angular */ +angular.module('angular-carousel', [ + 'ngTouch', + 'angular-carousel.shifty' +]); + +angular.module('angular-carousel') + +// PP modified carousel to work in conjunction with lazy loading +// so slide does not progress if image is not loaded or gets an error +// imageLoadingDataShare is the factory that has a value +// of 0 if no image is being loaded, -1 if there was an error and 1 if an image is currently being loaded +.directive('rnCarouselAutoSlide', ['$interval','$timeout', 'imageLoadingDataShare', 'carouselUtils', function($interval,$timeout, imageLoadingDataShare, carouselUtils ) { + return { + restrict: 'A', + link: function (scope, element, attrs) { + + + var stopAutoPlay = function() { + if (scope.autoSlider) { + //console.log ("Cancelling timer"); + //$interval.cancel(scope.autoSlider); + scope.autoSlider = null; + } + }; + var restartTimer = function() { + //console.log ("restart timer"); + if (carouselUtils.isStopped() == false) + { + //console.log ("Timer restart rnForceStop "+ carouselUtils.get()); + //console.log ("Calling autoslide because isStopped is false"); + scope.autoSlide(); + } + else { + //console.log ("Not sliding as stop=true"); + } + }; + //PP - don't auto play if user taps + var toggleAutoPlay = function() { + //scope.rnForceStop = !scope.rnForceStop; + carouselUtils.setStop(!carouselUtils.getStop()); + if (carouselUtils.isStopped()) + carouselUtils.setIndex (scope.carouselIndex); + //console.log ("Autoplay is " + carouselUtils.get()); + if (scope.autoSlider ) + { + // PP - If autoslide was on and we tapped, stop auto slide + //scope.rnForceStop = true; //PP + carouselUtils.setStop (true); + carouselUtils.setIndex (scope.carouselIndex); + //console.log ("***RN: Stopping Play rnForceStop is "+carouselUtils.getStop()); + stopAutoPlay(); + } + else + { + //scope.rnForceStop = false; //PP + carouselUtils.setStop (false); + //console.log ("***RN: Starting Play rnForceStop is "+carouselUtils.getStop()); + //scope.carouselIndex = carouselUtils.getIndex(); + restartTimer(); + } + }; + +// start with autoplay and require tap to stop + //console.log ("*** Setting rnForceStop to false and watching"); + //scope.rnForceStop = false; // PP + carouselUtils.setStop (false); + scope.$watch('carouselIndex', restartTimer); + + if (attrs.hasOwnProperty('rnCarouselPauseOnHover') && attrs.rnCarouselPauseOnHover !== 'false'){ + // PP - added touchend to make it react to touch devices + if (attrs.hasOwnProperty('rnPlatform') && attrs.rnPlatform == 'desktop') + { + //console.log ("ranPlatform is " + attrs.rnPlatform); + //console.log ("Desktop, de-registering any old click"); + element.off('click', toggleAutoPlay); // PP - remove mouse click for desktop + element.on('click', toggleAutoPlay); // PP for desktop + //console.log ("Desktop, registering click"); + } + else + { + //console.log ("ranPlatform is " + attrs.rnPlatform); + //console.log ("Device, de-registering any old touch"); + element.off('touchend', toggleAutoPlay); // PP - remove touchend too + element.on('touchend', toggleAutoPlay); + //console.log ("Device, registering touch"); + } + //element.on('mouseenter', stopAutoPlay); + //element.on('mouseleave', restartTimer); + } + + scope.$on('$destroy', function(){ + stopAutoPlay(); + //element.off('mouseenter', stopAutoPlay); + //element.off('mouseleave', restartTimer); + element.off('touchend', toggleAutoPlay); // PP - remove touchend too + element.off('click', toggleAutoPlay); // PP - remove mouse click for desktop + }); + } + }; +}]); + +angular.module('angular-carousel') + +.directive('rnCarouselIndicators', ['$parse', function($parse) { + return { + restrict: 'A', + scope: { + slides: '=', + index: '=rnCarouselIndex' + }, + templateUrl: 'carousel-indicators.html', + link: function(scope, iElement, iAttributes) { + var indexModel = $parse(iAttributes.rnCarouselIndex); + scope.goToSlide = function(index) { + indexModel.assign(scope.$parent.$parent, index); + }; + } + }; +}]); + +angular.module('angular-carousel').run(['$templateCache', function($templateCache) { + $templateCache.put('carousel-indicators.html', + '<div class="rn-carousel-indicator">\n' + + '<span ng-repeat="slide in slides" ng-class="{active: $index==index}" ng-click="goToSlide($index)">●</span>' + + '</div>' + ); +}]); + +(function() { + "use strict"; + + angular.module('angular-carousel') + + .service('DeviceCapabilities', function() { + + // TODO: merge in a single function + + // detect supported CSS property + function detectTransformProperty() { + var transformProperty = 'transform', + safariPropertyHack = 'webkitTransform'; + if (typeof document.body.style[transformProperty] !== 'undefined') { + + ['webkit', 'moz', 'o', 'ms'].every(function (prefix) { + var e = '-' + prefix + '-transform'; + if (typeof document.body.style[e] !== 'undefined') { + transformProperty = e; + return false; + } + return true; + }); + } else if (typeof document.body.style[safariPropertyHack] !== 'undefined') { + transformProperty = '-webkit-transform'; + } else { + transformProperty = undefined; + } + return transformProperty; + } + + //Detect support of translate3d + function detect3dSupport() { + var el = document.createElement('p'), + has3d, + transforms = { + 'webkitTransform': '-webkit-transform', + 'msTransform': '-ms-transform', + 'transform': 'transform' + }; + // Add it to the body to get the computed style + document.body.insertBefore(el, null); + for (var t in transforms) { + if (el.style[t] !== undefined) { + el.style[t] = 'translate3d(1px,1px,1px)'; + has3d = window.getComputedStyle(el).getPropertyValue(transforms[t]); + } + } + document.body.removeChild(el); + return (has3d !== undefined && has3d.length > 0 && has3d !== "none"); + } + + return { + has3d: detect3dSupport(), + transformProperty: detectTransformProperty() + }; + + }) + + .service('computeCarouselSlideStyle', ["DeviceCapabilities", function(DeviceCapabilities) { + // compute transition transform properties for a given slide and global offset + return function(slideIndex, offset, transitionType) { + var style = { + display: 'inline-block' + }, + opacity, + absoluteLeft = (slideIndex * 100) + offset, + slideTransformValue = DeviceCapabilities.has3d ? 'translate3d(' + absoluteLeft + '%, 0, 0)' : 'translate3d(' + absoluteLeft + '%, 0)', + distance = ((100 - Math.abs(absoluteLeft)) / 100); + + if (!DeviceCapabilities.transformProperty) { + // fallback to default slide if transformProperty is not available + style['margin-left'] = absoluteLeft + '%'; + } else { + if (transitionType == 'fadeAndSlide') { + style[DeviceCapabilities.transformProperty] = slideTransformValue; + opacity = 0; + if (Math.abs(absoluteLeft) < 100) { + opacity = 0.3 + distance * 0.7; + } + style.opacity = opacity; + } else if (transitionType == 'hexagon') { + var transformFrom = 100, + degrees = 0, + maxDegrees = 60 * (distance - 1); + + transformFrom = offset < (slideIndex * -100) ? 100 : 0; + degrees = offset < (slideIndex * -100) ? maxDegrees : -maxDegrees; + style[DeviceCapabilities.transformProperty] = slideTransformValue + ' ' + 'rotateY(' + degrees + 'deg)'; + style[DeviceCapabilities.transformProperty + '-origin'] = transformFrom + '% 50%'; + } else if (transitionType == 'zoom') { + style[DeviceCapabilities.transformProperty] = slideTransformValue; + var scale = 1; + if (Math.abs(absoluteLeft) < 100) { + scale = 1 + ((1 - distance) * 2); + } + style[DeviceCapabilities.transformProperty] += ' scale(' + scale + ')'; + style[DeviceCapabilities.transformProperty + '-origin'] = '50% 50%'; + opacity = 0; + if (Math.abs(absoluteLeft) < 100) { + opacity = 0.3 + distance * 0.7; + } + style.opacity = opacity; + } else { + style[DeviceCapabilities.transformProperty] = slideTransformValue; + } + } + return style; + }; + }]) + + //PP + .factory ('carouselUtils', function() { + var isstopped = false; + var duration = 0; + var stoppedIndex = 0; + return { + + setDuration: function (val) + { + duration = val; + //console.log (">>>>>>>>>>>>>>>> DURATION SET TO (secs) " + duration); + }, + + getDuration: function () + { + return duration; + }, + + setStop: function(val) + { + isstopped = val; + if (isstopped) + { + console.log ("Paused at " + stoppedIndex); + } + }, + + setIndex: function(val) + { + stoppedIndex = val; + //console.log ("setting saved index to " + stoppedIndex); + }, + getIndex: function() + { + return stoppedIndex; + }, + getStop: function() + { + return isstopped; + }, + isStopped: function() + { + return isstopped; + }, + hello: function() + { + //console.log ("Hello from carouselUtils"); + } + + } + }) + + + .service('createStyleString', function() { + return function(object) { + var styles = []; + angular.forEach(object, function(value, key) { + styles.push(key + ':' + value); + }); + return styles.join(';'); + }; + }) + + .directive('rnCarousel', ['$swipe', '$window', '$document', '$parse', '$compile', '$timeout', '$interval', 'computeCarouselSlideStyle', 'createStyleString', 'Tweenable', 'imageLoadingDataShare', 'carouselUtils', + function($swipe, $window, $document, $parse, $compile, $timeout, $interval, computeCarouselSlideStyle, createStyleString, Tweenable, imageLoadingDataShare, carouselUtils) { + // internal ids to allow multiple instances + var carouselId = 0, + // in absolute pixels, at which distance the slide stick to the edge on release + rubberTreshold = 3; + + var requestAnimationFrame = $window.requestAnimationFrame || $window.webkitRequestAnimationFrame || $window.mozRequestAnimationFrame; + + function getItemIndex(collection, target, defaultIndex) { + var result = defaultIndex; + collection.every(function(item, index) { + if (angular.equals(item, target)) { + result = index; + return false; + } + return true; + }); + return result; + } + + return { + restrict: 'A', + scope: true, + + + compile: function(tElement, tAttributes) { + // use the compile phase to customize the DOM + var firstChild = tElement[0].querySelector('li'), + firstChildAttributes = (firstChild) ? firstChild.attributes : [], + isRepeatBased = false, + isBuffered = false, + repeatItem, + repeatCollection; + + // try to find an ngRepeat expression + // at this point, the attributes are not yet normalized so we need to try various syntax + ['ng-repeat', 'data-ng-repeat', 'ng:repeat', 'x-ng-repeat'].every(function(attr) { + var repeatAttribute = firstChildAttributes[attr]; + if (angular.isDefined(repeatAttribute)) { + // ngRepeat regexp extracted from angular 1.2.7 src + var exprMatch = repeatAttribute.value.match(/^\s*([\s\S]+?)\s+in\s+([\s\S]+?)(?:\s+track\s+by\s+([\s\S]+?))?\s*$/), + trackProperty = exprMatch[3]; + + repeatItem = exprMatch[1]; + repeatCollection = exprMatch[2]; + + if (repeatItem) { + if (angular.isDefined(tAttributes['rnCarouselBuffered'])) { + // update the current ngRepeat expression and add a slice operator if buffered + isBuffered = true; + repeatAttribute.value = repeatItem + ' in ' + repeatCollection + '|carouselSlice:carouselBufferIndex:carouselBufferSize'; + if (trackProperty) { + repeatAttribute.value += ' track by ' + trackProperty; + } + } + isRepeatBased = true; + return false; + } + } + return true; + }); + + return function(scope, iElement, iAttributes, containerCtrl) { + + carouselId++; + + var defaultOptions = { + transitionType: iAttributes.rnCarouselTransition || 'slide', + transitionEasing: iAttributes.rnCarouselEasing || 'easeTo', + transitionDuration: parseFloat(iAttributes.rnCarouselDuration, 10) || 300, + isSequential: true, + autoSlideDuration: 3, + bufferSize: 9, + /* in container % how much we need to drag to trigger the slide change */ + moveTreshold: 0.1, + defaultIndex: 0 + }; + + // TODO + var options = angular.extend({}, defaultOptions); + + var pressed, + startX, + isIndexBound = false, + offset = 0, + destination, + swipeMoved = false, + //animOnIndexChange = true, + currentSlides = [], + elWidth = null, + elX = null, + animateTransitions = true, + intialState = true, + animating = false, + mouseUpBound = false, + locked = false; + + //rn-swipe-disabled =true will only disable swipe events + if(iAttributes.rnSwipeDisabled !== "true") { + $swipe.bind(iElement, { + start: swipeStart, + move: swipeMove, + end: swipeEnd, + cancel: function(event) { + swipeEnd({}, event); + } + }); + } + + function getSlidesDOM() { + return iElement[0].querySelectorAll('ul[rn-carousel] > li'); + } + + function documentMouseUpEvent(event) { + // in case we click outside the carousel, trigger a fake swipeEnd + swipeMoved = true; + swipeEnd({ + x: event.clientX, + y: event.clientY + }, event); + } + + function updateSlidesPosition(offset) { + // manually apply transformation to carousel childrens + // todo : optim : apply only to visible items + var x = scope.carouselBufferIndex * 100 + offset; + angular.forEach(getSlidesDOM(), function(child, index) { + child.style.cssText = createStyleString(computeCarouselSlideStyle(index, x, options.transitionType)); + }); + } + + scope.nextSlide = function(slideOptions) { + if (carouselUtils.isStopped()==true) + { + //console.log ("Just updated index, but we are stopped"); + return; + } + var index = scope.carouselIndex + 1; + if (index > currentSlides.length - 1) { +//PP + index--; + } + + //console.log ("inside next slide with index = " + index); + // PP - we keep moving the index but don't load so it looks nicer + // and we don't mess up the rate + if (imageLoadingDataShare.get() != 1) // PP- If the image is still being loaded, hold on, don't change + { + goToSlide(index, slideOptions); + // console.log ("loaded carousel is " + scope.carouselIndex); + + } + else + { + scope.autoSlide(); + // lets move the index along - PP + // so playback total time is not affected + + // scope.carouselIndex = index; + //console.log ("Image is still loading, not skipping slides, index at "+index); + // updateBufferIndex(); + //console.log ("NOT LOADED but advancing carousel to " + scope.carouselIndex); + +} + + }; + + scope.prevSlide = function(slideOptions) { + var index = scope.carouselIndex - 1; + if (index < 0) { + index = currentSlides.length - 1; + } + goToSlide(index, slideOptions); + }; + + function goToSlide(index, slideOptions) { + //console.log('goToSlide', arguments); + // move a to the given slide index + if (index === undefined) { + index = scope.carouselIndex; + } + + slideOptions = slideOptions || {}; + if (slideOptions.animate === false || options.transitionType === 'none') { + locked = false; + offset = index * -100; + scope.carouselIndex = index; + //console.log ("inside goToSlide: updating carousel index to " + scope.carouselIndex); + updateBufferIndex(); + return; + } + + locked = true; + var tweenable = new Tweenable(); + tweenable.tween({ + from: { + 'x': offset + }, + to: { + 'x': index * -100 + }, + duration: options.transitionDuration, + easing: options.transitionEasing, + step: function(state) { + updateSlidesPosition(state.x); + }, + finish: function() { + scope.$apply(function() { + scope.carouselIndex = index; + offset = index * -100; + updateBufferIndex(); + $timeout(function () { + locked = false; + }, 0, false); + }); + } + }); + } + + function getContainerWidth() { + var rect = iElement[0].getBoundingClientRect(); + return rect.width ? rect.width : rect.right - rect.left; + } + + function updateContainerWidth() { + elWidth = getContainerWidth(); + } + + function bindMouseUpEvent() { + if (!mouseUpBound) { + mouseUpBound = true; + $document.bind('mouseup', documentMouseUpEvent); + } + } + + function unbindMouseUpEvent() { + if (mouseUpBound) { + mouseUpBound = false; + $document.unbind('mouseup', documentMouseUpEvent); + } + } + + function swipeStart(coords, event) { + // console.log('swipeStart', coords, event); + if (locked || currentSlides.length <= 1) { + return; + } + updateContainerWidth(); + elX = iElement[0].querySelector('li').getBoundingClientRect().left; + pressed = true; + startX = coords.x; + return false; + } + + function swipeMove(coords, event) { + //console.log('swipeMove', coords, event); + var x, delta; + bindMouseUpEvent(); + if (pressed) { + x = coords.x; + delta = startX - x; + if (delta > 2 || delta < -2) { + swipeMoved = true; + var moveOffset = offset + (-delta * 100 / elWidth); + updateSlidesPosition(moveOffset); + } + } + return false; + } + + var init = true; + scope.carouselIndex = 0; + + if (!isRepeatBased) { + // fake array when no ng-repeat + currentSlides = []; + angular.forEach(getSlidesDOM(), function(node, index) { + currentSlides.push({id: index}); + }); + } + + if (iAttributes.rnCarouselControls!==undefined) { + // dont use a directive for this + var nextSlideIndexCompareValue = isRepeatBased ? repeatCollection.replace('::', '') + '.length - 1' : currentSlides.length - 1; + var tpl = '<div class="rn-carousel-controls">\n' + + ' <span class="rn-carousel-control rn-carousel-control-prev" ng-click="prevSlide()" ng-if="carouselIndex > 0"></span>\n' + + ' <span class="rn-carousel-control rn-carousel-control-next" ng-click="nextSlide()" ng-if="carouselIndex < ' + nextSlideIndexCompareValue + '"></span>\n' + + '</div>'; + iElement.parent().append($compile(angular.element(tpl))(scope)); + } + + if (iAttributes.rnCarouselAutoSlide!==undefined) { + // PP was parseInt, changed to parseFloat so I can specify fractions of seconds + var duration = parseFloat(iAttributes.rnCarouselAutoSlide, 10) || options.autoSlideDuration; + carouselUtils.setDuration(duration); + console.log ("Setting duration - should only happen once"); + scope.autoSlide = function() { + //console.log ("Inside autoslide"); + if (scope.autoSlider) { + //$interval.cancel(scope.autoSlider); + scope.autoSlider = null; + } + if (carouselUtils.isStopped() == false) //PP - don't move slide if this variable is set + { + var mydur = carouselUtils.getDuration(); + + // what happens if mydur needs to be less than a millisecond? + var mydurms = mydur * 1000.0; + + if (mydurms < 5) + { + console.log ("duration is too small at "+mydurms+" making it to 5"); + mydurms = 5; + + } + //console.log ("Setting next slide duration at " + mydur *1000); + scope.autoSlider = $timeout(function() { + //console.log ("setting time to " + mydurms); + //if (!locked && !pressed ) { + if (1 ) { + scope.nextSlide(); + } + }, mydurms); + } + else { console.log ("We are stopped, doing nothing"); } + }; + } + + if (iAttributes.rnCarouselDefaultIndex) { + var defaultIndexModel = $parse(iAttributes.rnCarouselDefaultIndex); + options.defaultIndex = defaultIndexModel(scope.$parent) || 0; + } + + + var shouldInitialySlideTo = null; + if (iAttributes.rnCarouselIndex) { + var updateParentIndex = function(value) { + if (value < 0) { + return; + } + //console.log ("Still indexing"); + indexModel.assign(scope.$parent, value); + }; + var indexModel = $parse(iAttributes.rnCarouselIndex); + if (angular.isFunction(indexModel.assign)) { + /* check if this property is assignable then watch it */ + scope.$watch('carouselIndex', function(newValue) { + //if (carouselUtils.isStopped() == false) + //console.log ("Carouselc chanhed 1"); + updateParentIndex(newValue); + }); + scope.$parent.$watch(function () { + return indexModel(scope.$parent); + }, function(newValue, oldValue) { + shouldInitialySlideTo = newValue; + + if (newValue !== undefined && newValue !== null) { + if (currentSlides && currentSlides.length > 0 && newValue >= currentSlides.length) { + newValue = currentSlides.length - 1; + updateParentIndex(newValue); + } else if (currentSlides && newValue < 0) { + newValue = 0; // PP + updateParentIndex(newValue); + } + if (!locked) { + goToSlide(newValue, { + animate: !init + }); + } + init = false; + } + }); + isIndexBound = true; + + if (options.defaultIndex) { + goToSlide(options.defaultIndex, { + animate: !init + }); + } + } else if (!isNaN(iAttributes.rnCarouselIndex)) { + /* if user just set an initial number, set it */ + goToSlide(parseInt(iAttributes.rnCarouselIndex, 10), { + animate: false + }); + } + } else { + goToSlide(options.defaultIndex, { + animate: !init + }); + init = false; + } + + if (iAttributes.rnCarouselLocked) { + scope.$watch(iAttributes.rnCarouselLocked, function(newValue, oldValue) { + // only bind swipe when it's not switched off + if(newValue === true) { + locked = true; + } else { + locked = false; + } + }); + } + + if (isRepeatBased) { + // use rn-carousel-deep-watch to fight the Angular $watchCollection weakness : https://github.com/angular/angular.js/issues/2621 + // optional because it have some performance impacts (deep watch) + var deepWatch = (iAttributes.rnCarouselDeepWatch!==undefined); + + scope[deepWatch?'$watch':'$watchCollection'](repeatCollection, function(newValue, oldValue) { + //console.log('repeatCollection', currentSlides); + currentSlides = newValue; +// This will force the required initial carouselIndex + // specified with `rn-carousel-index` on carousel initialization. + if (shouldInitialySlideTo) { + scope.carouselIndex = shouldInitialySlideTo; + shouldInitialySlideTo = null; + } + // if deepWatch ON ,manually compare objects to guess the new position + if (deepWatch && angular.isArray(newValue)) { + var activeElement = oldValue[scope.carouselIndex]; + var newIndex = getItemIndex(newValue, activeElement, scope.carouselIndex); + goToSlide(newIndex, {animate: false}); + } else { + goToSlide(scope.carouselIndex, {animate: false}); + } + }, true); + } + + function swipeEnd(coords, event, forceAnimation) { + // console.log('swipeEnd', 'scope.carouselIndex', scope.carouselIndex); + // Prevent clicks on buttons inside slider to trigger "swipeEnd" event on touchend/mouseup + // console.log(iAttributes.rnCarouselOnInfiniteScroll); + if (event && !swipeMoved) { + return; + } + unbindMouseUpEvent(); + pressed = false; + swipeMoved = false; + destination = startX - coords.x; + if (destination===0) { + return; + } + if (locked) { + return; + } + offset += (-destination * 100 / elWidth); + if (options.isSequential) { + var minMove = options.moveTreshold * elWidth, + absMove = -destination, + slidesMove = -Math[absMove >= 0 ? 'ceil' : 'floor'](absMove / elWidth), + shouldMove = Math.abs(absMove) > minMove; + + if (currentSlides && (slidesMove + scope.carouselIndex) >= currentSlides.length) { + slidesMove = currentSlides.length - 1 - scope.carouselIndex; + } + if ((slidesMove + scope.carouselIndex) < 0) { + slidesMove = -scope.carouselIndex; + } + var moveOffset = shouldMove ? slidesMove : 0; + + destination = (scope.carouselIndex + moveOffset); + + goToSlide(destination); + if(iAttributes.rnCarouselOnInfiniteScrollRight!==undefined && slidesMove === 0 && scope.carouselIndex !== 0) { + $parse(iAttributes.rnCarouselOnInfiniteScrollRight)(scope) + goToSlide(0); + } + if(iAttributes.rnCarouselOnInfiniteScrollLeft!==undefined && slidesMove === 0 && scope.carouselIndex === 0 && moveOffset === 0) { + $parse(iAttributes.rnCarouselOnInfiniteScrollLeft)(scope) + goToSlide(currentSlides.length); + } + + } else { + scope.$apply(function() { + scope.carouselIndex = parseInt(-offset / 100, 10); + updateBufferIndex(); + }); + + } + + } + + scope.$on('$destroy', function() { + unbindMouseUpEvent(); + }); + + scope.carouselBufferIndex = 0; + scope.carouselBufferSize = options.bufferSize; + + function updateBufferIndex() { + // update and cap te buffer index + var bufferIndex = 0; + var bufferEdgeSize = (scope.carouselBufferSize - 1) / 2; + if (isBuffered) { + if (scope.carouselIndex <= bufferEdgeSize) { + // first buffer part + bufferIndex = 0; + } else if (currentSlides && currentSlides.length < scope.carouselBufferSize) { + // smaller than buffer + bufferIndex = 0; + } else if (currentSlides && scope.carouselIndex > currentSlides.length - scope.carouselBufferSize) { + // last buffer part + bufferIndex = currentSlides.length - scope.carouselBufferSize; + } else { + // compute buffer start + bufferIndex = scope.carouselIndex - bufferEdgeSize; + } + + scope.carouselBufferIndex = bufferIndex; + $timeout(function() { + updateSlidesPosition(offset); + }, 0, false); + } else { + $timeout(function() { + updateSlidesPosition(offset); + }, 0, false); + } + } + + function onOrientationChange() { + updateContainerWidth(); + goToSlide(); + } + + // handle orientation change + var winEl = angular.element($window); + winEl.bind('orientationchange', onOrientationChange); + winEl.bind('resize', onOrientationChange); + + scope.$on('$destroy', function() { + unbindMouseUpEvent(); + winEl.unbind('orientationchange', onOrientationChange); + winEl.unbind('resize', onOrientationChange); + }); + }; + } + }; + } + ]); +})(); + + + +angular.module('angular-carousel.shifty', []) + +.factory('Tweenable', function() { + + /*! shifty - v1.3.4 - 2014-10-29 - http://jeremyckahn.github.io/shifty */ + ;(function (root) { + + /*! + * Shifty Core + * By Jeremy Kahn - jeremyckahn@gmail.com + */ + + var Tweenable = (function () { + + 'use strict'; + + // Aliases that get defined later in this function + var formula; + + // CONSTANTS + var DEFAULT_SCHEDULE_FUNCTION; + var DEFAULT_EASING = 'linear'; + var DEFAULT_DURATION = 500; + var UPDATE_TIME = 1000 / 60; + + var _now = Date.now + ? Date.now + : function () {return +new Date();}; + + var now = typeof SHIFTY_DEBUG_NOW !== 'undefined' ? SHIFTY_DEBUG_NOW : _now; + + if (typeof window !== 'undefined') { + // requestAnimationFrame() shim by Paul Irish (modified for Shifty) + // http://paulirish.com/2011/requestanimationframe-for-smart-animating/ + DEFAULT_SCHEDULE_FUNCTION = window.requestAnimationFrame + || window.webkitRequestAnimationFrame + || window.oRequestAnimationFrame + || window.msRequestAnimationFrame + || (window.mozCancelRequestAnimationFrame + && window.mozRequestAnimationFrame) + || setTimeout; + } else { + DEFAULT_SCHEDULE_FUNCTION = setTimeout; + } + + function noop () { + // NOOP! + } + + /*! + * Handy shortcut for doing a for-in loop. This is not a "normal" each + * function, it is optimized for Shifty. The iterator function only receives + * the property name, not the value. + * @param {Object} obj + * @param {Function(string)} fn + */ + function each (obj, fn) { + var key; + for (key in obj) { + if (Object.hasOwnProperty.call(obj, key)) { + fn(key); + } + } + } + + /*! + * Perform a shallow copy of Object properties. + * @param {Object} targetObject The object to copy into + * @param {Object} srcObject The object to copy from + * @return {Object} A reference to the augmented `targetObj` Object + */ + function shallowCopy (targetObj, srcObj) { + each(srcObj, function (prop) { + targetObj[prop] = srcObj[prop]; + }); + + return targetObj; + } + + /*! + * Copies each property from src onto target, but only if the property to + * copy to target is undefined. + * @param {Object} target Missing properties in this Object are filled in + * @param {Object} src + */ + function defaults (target, src) { + each(src, function (prop) { + if (typeof target[prop] === 'undefined') { + target[prop] = src[prop]; + } + }); + } + + /*! + * Calculates the interpolated tween values of an Object for a given + * timestamp. + * @param {Number} forPosition The position to compute the state for. + * @param {Object} currentState Current state properties. + * @param {Object} originalState: The original state properties the Object is + * tweening from. + * @param {Object} targetState: The destination state properties the Object + * is tweening to. + * @param {number} duration: The length of the tween in milliseconds. + * @param {number} timestamp: The UNIX epoch time at which the tween began. + * @param {Object} easing: This Object's keys must correspond to the keys in + * targetState. + */ + function tweenProps (forPosition, currentState, originalState, targetState, + duration, timestamp, easing) { + var normalizedPosition = (forPosition - timestamp) / duration; + + var prop; + for (prop in currentState) { + if (currentState.hasOwnProperty(prop)) { + currentState[prop] = tweenProp(originalState[prop], + targetState[prop], formula[easing[prop]], normalizedPosition); + } + } + + return currentState; + } + + /*! + * Tweens a single property. + * @param {number} start The value that the tween started from. + * @param {number} end The value that the tween should end at. + * @param {Function} easingFunc The easing curve to apply to the tween. + * @param {number} position The normalized position (between 0.0 and 1.0) to + * calculate the midpoint of 'start' and 'end' against. + * @return {number} The tweened value. + */ + function tweenProp (start, end, easingFunc, position) { + return start + (end - start) * easingFunc(position); + } + + /*! + * Applies a filter to Tweenable instance. + * @param {Tweenable} tweenable The `Tweenable` instance to call the filter + * upon. + * @param {String} filterName The name of the filter to apply. + */ + function applyFilter (tweenable, filterName) { + var filters = Tweenable.prototype.filter; + var args = tweenable._filterArgs; + + each(filters, function (name) { + if (typeof filters[name][filterName] !== 'undefined') { + filters[name][filterName].apply(tweenable, args); + } + }); + } + + var timeoutHandler_endTime; + var timeoutHandler_currentTime; + var timeoutHandler_isEnded; + var timeoutHandler_offset; + /*! + * Handles the update logic for one step of a tween. + * @param {Tweenable} tweenable + * @param {number} timestamp + * @param {number} duration + * @param {Object} currentState + * @param {Object} originalState + * @param {Object} targetState + * @param {Object} easing + * @param {Function(Object, *, number)} step + * @param {Function(Function,number)}} schedule + */ + function timeoutHandler (tweenable, timestamp, duration, currentState, + originalState, targetState, easing, step, schedule) { + timeoutHandler_endTime = timestamp + duration; + timeoutHandler_currentTime = Math.min(now(), timeoutHandler_endTime); + timeoutHandler_isEnded = + timeoutHandler_currentTime >= timeoutHandler_endTime; + + timeoutHandler_offset = duration - ( + timeoutHandler_endTime - timeoutHandler_currentTime); + + if (tweenable.isPlaying() && !timeoutHandler_isEnded) { + tweenable._scheduleId = schedule(tweenable._timeoutHandler, UPDATE_TIME); + + applyFilter(tweenable, 'beforeTween'); + tweenProps(timeoutHandler_currentTime, currentState, originalState, + targetState, duration, timestamp, easing); + applyFilter(tweenable, 'afterTween'); + + step(currentState, tweenable._attachment, timeoutHandler_offset); + } else if (timeoutHandler_isEnded) { + step(targetState, tweenable._attachment, timeoutHandler_offset); + tweenable.stop(true); + } + } + + + /*! + * Creates a usable easing Object from either a string or another easing + * Object. If `easing` is an Object, then this function clones it and fills + * in the missing properties with "linear". + * @param {Object} fromTweenParams + * @param {Object|string} easing + */ + function composeEasingObject (fromTweenParams, easing) { + var composedEasing = {}; + + if (typeof easing === 'string') { + each(fromTweenParams, function (prop) { + composedEasing[prop] = easing; + }); + } else { + each(fromTweenParams, function (prop) { + if (!composedEasing[prop]) { + composedEasing[prop] = easing[prop] || DEFAULT_EASING; + } + }); + } + + return composedEasing; + } + + /** + * Tweenable constructor. + * @param {Object=} opt_initialState The values that the initial tween should start at if a "from" object is not provided to Tweenable#tween. + * @param {Object=} opt_config See Tweenable.prototype.setConfig() + * @constructor + */ + function Tweenable (opt_initialState, opt_config) { + this._currentState = opt_initialState || {}; + this._configured = false; + this._scheduleFunction = DEFAULT_SCHEDULE_FUNCTION; + + // To prevent unnecessary calls to setConfig do not set default configuration here. + // Only set default configuration immediately before tweening if none has been set. + if (typeof opt_config !== 'undefined') { + this.setConfig(opt_config); + } + } + + /** + * Configure and start a tween. + * @param {Object=} opt_config See Tweenable.prototype.setConfig() + * @return {Tweenable} + */ + Tweenable.prototype.tween = function (opt_config) { + if (this._isTweening) { + return this; + } + + // Only set default config if no configuration has been set previously and none is provided now. + if (opt_config !== undefined || !this._configured) { + this.setConfig(opt_config); + } + + this._timestamp = now(); + this._start(this.get(), this._attachment); + return this.resume(); + }; + + /** + * Sets the tween configuration. `config` may have the following options: + * + * - __from__ (_Object=_): Starting position. If omitted, the current state is used. + * - __to__ (_Object=_): Ending position. + * - __duration__ (_number=_): How many milliseconds to animate for. + * - __start__ (_Function(Object)_): Function to execute when the tween begins. Receives the state of the tween as the first parameter. Attachment is the second parameter. + * - __step__ (_Function(Object, *, number)_): Function to execute on every tick. Receives the state of the tween as the first parameter. Attachment is the second parameter, and the time elapsed since the start of the tween is the third parameter. This function is not called on the final step of the animation, but `finish` is. + * - __finish__ (_Function(Object, *)_): Function to execute upon tween completion. Receives the state of the tween as the first parameter. Attachment is the second parameter. + * - __easing__ (_Object|string=_): Easing curve name(s) to use for the tween. + * - __attachment__ (_Object|string|any=_): Value that is attached to this instance and passed on to the step/start/finish methods. + * @param {Object} config + * @return {Tweenable} + */ + Tweenable.prototype.setConfig = function (config) { + config = config || {}; + this._configured = true; + + // Attach something to this Tweenable instance (e.g.: a DOM element, an object, a string, etc.); + this._attachment = config.attachment; + + // Init the internal state + this._pausedAtTime = null; + this._scheduleId = null; + this._start = config.start || noop; + this._step = config.step || noop; + this._finish = config.finish || noop; + this._duration = config.duration || DEFAULT_DURATION; + this._currentState = config.from || this.get(); + this._originalState = this.get(); + this._targetState = config.to || this.get(); + + // Aliases used below + var currentState = this._currentState; + var targetState = this._targetState; + + // Ensure that there is always something to tween to. + defaults(targetState, currentState); + + this._easing = composeEasingObject( + currentState, config.easing || DEFAULT_EASING); + + this._filterArgs = + [currentState, this._originalState, targetState, this._easing]; + + applyFilter(this, 'tweenCreated'); + return this; + }; + + /** + * Gets the current state. + * @return {Object} + */ + Tweenable.prototype.get = function () { + return shallowCopy({}, this._currentState); + }; + + /** + * Sets the current state. + * @param {Object} state + */ + Tweenable.prototype.set = function (state) { + this._currentState = state; + }; + + /** + * Pauses a tween. Paused tweens can be resumed from the point at which they were paused. This is different than [`stop()`](#stop), as that method causes a tween to start over when it is resumed. + * @return {Tweenable} + */ + Tweenable.prototype.pause = function () { + this._pausedAtTime = now(); + this._isPaused = true; + return this; + }; + + /** + * Resumes a paused tween. + * @return {Tweenable} + */ + Tweenable.prototype.resume = function () { + if (this._isPaused) { + this._timestamp += now() - this._pausedAtTime; + } + + this._isPaused = false; + this._isTweening = true; + + var self = this; + this._timeoutHandler = function () { + timeoutHandler(self, self._timestamp, self._duration, self._currentState, + self._originalState, self._targetState, self._easing, self._step, + self._scheduleFunction); + }; + + this._timeoutHandler(); + + return this; + }; + + /** + * Move the state of the animation to a specific point in the tween's timeline. + * If the animation is not running, this will cause the `step` handlers to be + * called. + * @param {millisecond} millisecond The millisecond of the animation to seek to. + * @return {Tweenable} + */ + Tweenable.prototype.seek = function (millisecond) { + this._timestamp = now() - millisecond; + + if (!this.isPlaying()) { + this._isTweening = true; + this._isPaused = false; + + // If the animation is not running, call timeoutHandler to make sure that + // any step handlers are run. + timeoutHandler(this, this._timestamp, this._duration, this._currentState, + this._originalState, this._targetState, this._easing, this._step, + this._scheduleFunction); + + this._timeoutHandler(); + this.pause(); + } + + return this; + }; + + /** + * Stops and cancels a tween. + * @param {boolean=} gotoEnd If false or omitted, the tween just stops at its current state, and the "finish" handler is not invoked. If true, the tweened object's values are instantly set to the target values, and "finish" is invoked. + * @return {Tweenable} + */ + Tweenable.prototype.stop = function (gotoEnd) { + this._isTweening = false; + this._isPaused = false; + this._timeoutHandler = noop; + + (root.cancelAnimationFrame || + root.webkitCancelAnimationFrame || + root.oCancelAnimationFrame || + root.msCancelAnimationFrame || + root.mozCancelRequestAnimationFrame || + root.clearTimeout)(this._scheduleId); + + if (gotoEnd) { + shallowCopy(this._currentState, this._targetState); + applyFilter(this, 'afterTweenEnd'); + this._finish.call(this, this._currentState, this._attachment); + } + + return this; + }; + + /** + * Returns whether or not a tween is running. + * @return {boolean} + */ + Tweenable.prototype.isPlaying = function () { + return this._isTweening && !this._isPaused; + }; + + /** + * Sets a custom schedule function. + * + * If a custom function is not set the default one is used [`requestAnimationFrame`](https://developer.mozilla.org/en-US/docs/Web/API/window.requestAnimationFrame) if available, otherwise [`setTimeout`](https://developer.mozilla.org/en-US/docs/Web/API/Window.setTimeout)). + * + * @param {Function(Function,number)} scheduleFunction The function to be called to schedule the next frame to be rendered + */ + Tweenable.prototype.setScheduleFunction = function (scheduleFunction) { + this._scheduleFunction = scheduleFunction; + }; + + /** + * `delete`s all "own" properties. Call this when the `Tweenable` instance is no longer needed to free memory. + */ + Tweenable.prototype.dispose = function () { + var prop; + for (prop in this) { + if (this.hasOwnProperty(prop)) { + delete this[prop]; + } + } + }; + + /*! + * Filters are used for transforming the properties of a tween at various + * points in a Tweenable's life cycle. See the README for more info on this. + */ + Tweenable.prototype.filter = {}; + + /*! + * This object contains all of the tweens available to Shifty. It is extendible - simply attach properties to the Tweenable.prototype.formula Object following the same format at linear. + * + * `pos` should be a normalized `number` (between 0 and 1). + */ + Tweenable.prototype.formula = { + linear: function (pos) { + return pos; + } + }; + + formula = Tweenable.prototype.formula; + + shallowCopy(Tweenable, { + 'now': now + ,'each': each + ,'tweenProps': tweenProps + ,'tweenProp': tweenProp + ,'applyFilter': applyFilter + ,'shallowCopy': shallowCopy + ,'defaults': defaults + ,'composeEasingObject': composeEasingObject + }); + + root.Tweenable = Tweenable; + return Tweenable; + + } ()); + + /*! + * All equations are adapted from Thomas Fuchs' [Scripty2](https://github.com/madrobby/scripty2/blob/master/src/effects/transitions/penner.js). + * + * Based on Easing Equations (c) 2003 [Robert Penner](http://www.robertpenner.com/), all rights reserved. This work is [subject to terms](http://www.robertpenner.com/easing_terms_of_use.html). + */ + + /*! + * TERMS OF USE - EASING EQUATIONS + * Open source under the BSD License. + * Easing Equations (c) 2003 Robert Penner, all rights reserved. + */ + + ;(function () { + + Tweenable.shallowCopy(Tweenable.prototype.formula, { + easeInQuad: function (pos) { + return Math.pow(pos, 2); + }, + + easeOutQuad: function (pos) { + return -(Math.pow((pos - 1), 2) - 1); + }, + + easeInOutQuad: function (pos) { + if ((pos /= 0.5) < 1) {return 0.5 * Math.pow(pos,2);} + return -0.5 * ((pos -= 2) * pos - 2); + }, + + easeInCubic: function (pos) { + return Math.pow(pos, 3); + }, + + easeOutCubic: function (pos) { + return (Math.pow((pos - 1), 3) + 1); + }, + + easeInOutCubic: function (pos) { + if ((pos /= 0.5) < 1) {return 0.5 * Math.pow(pos,3);} + return 0.5 * (Math.pow((pos - 2),3) + 2); + }, + + easeInQuart: function (pos) { + return Math.pow(pos, 4); + }, + + easeOutQuart: function (pos) { + return -(Math.pow((pos - 1), 4) - 1); + }, + + easeInOutQuart: function (pos) { + if ((pos /= 0.5) < 1) {return 0.5 * Math.pow(pos,4);} + return -0.5 * ((pos -= 2) * Math.pow(pos,3) - 2); + }, + + easeInQuint: function (pos) { + return Math.pow(pos, 5); + }, + + easeOutQuint: function (pos) { + return (Math.pow((pos - 1), 5) + 1); + }, + + easeInOutQuint: function (pos) { + if ((pos /= 0.5) < 1) {return 0.5 * Math.pow(pos,5);} + return 0.5 * (Math.pow((pos - 2),5) + 2); + }, + + easeInSine: function (pos) { + return -Math.cos(pos * (Math.PI / 2)) + 1; + }, + + easeOutSine: function (pos) { + return Math.sin(pos * (Math.PI / 2)); + }, + + easeInOutSine: function (pos) { + return (-0.5 * (Math.cos(Math.PI * pos) - 1)); + }, + + easeInExpo: function (pos) { + return (pos === 0) ? 0 : Math.pow(2, 10 * (pos - 1)); + }, + + easeOutExpo: function (pos) { + return (pos === 1) ? 1 : -Math.pow(2, -10 * pos) + 1; + }, + + easeInOutExpo: function (pos) { + if (pos === 0) {return 0;} + if (pos === 1) {return 1;} + if ((pos /= 0.5) < 1) {return 0.5 * Math.pow(2,10 * (pos - 1));} + return 0.5 * (-Math.pow(2, -10 * --pos) + 2); + }, + + easeInCirc: function (pos) { + return -(Math.sqrt(1 - (pos * pos)) - 1); + }, + + easeOutCirc: function (pos) { + return Math.sqrt(1 - Math.pow((pos - 1), 2)); + }, + + easeInOutCirc: function (pos) { + if ((pos /= 0.5) < 1) {return -0.5 * (Math.sqrt(1 - pos * pos) - 1);} + return 0.5 * (Math.sqrt(1 - (pos -= 2) * pos) + 1); + }, + + easeOutBounce: function (pos) { + if ((pos) < (1 / 2.75)) { + return (7.5625 * pos * pos); + } else if (pos < (2 / 2.75)) { + return (7.5625 * (pos -= (1.5 / 2.75)) * pos + 0.75); + } else if (pos < (2.5 / 2.75)) { + return (7.5625 * (pos -= (2.25 / 2.75)) * pos + 0.9375); + } else { + return (7.5625 * (pos -= (2.625 / 2.75)) * pos + 0.984375); + } + }, + + easeInBack: function (pos) { + var s = 1.70158; + return (pos) * pos * ((s + 1) * pos - s); + }, + + easeOutBack: function (pos) { + var s = 1.70158; + return (pos = pos - 1) * pos * ((s + 1) * pos + s) + 1; + }, + + easeInOutBack: function (pos) { + var s = 1.70158; + if ((pos /= 0.5) < 1) {return 0.5 * (pos * pos * (((s *= (1.525)) + 1) * pos - s));} + return 0.5 * ((pos -= 2) * pos * (((s *= (1.525)) + 1) * pos + s) + 2); + }, + + elastic: function (pos) { + return -1 * Math.pow(4,-8 * pos) * Math.sin((pos * 6 - 1) * (2 * Math.PI) / 2) + 1; + }, + + swingFromTo: function (pos) { + var s = 1.70158; + return ((pos /= 0.5) < 1) ? 0.5 * (pos * pos * (((s *= (1.525)) + 1) * pos - s)) : + 0.5 * ((pos -= 2) * pos * (((s *= (1.525)) + 1) * pos + s) + 2); + }, + + swingFrom: function (pos) { + var s = 1.70158; + return pos * pos * ((s + 1) * pos - s); + }, + + swingTo: function (pos) { + var s = 1.70158; + return (pos -= 1) * pos * ((s + 1) * pos + s) + 1; + }, + + bounce: function (pos) { + if (pos < (1 / 2.75)) { + return (7.5625 * pos * pos); + } else if (pos < (2 / 2.75)) { + return (7.5625 * (pos -= (1.5 / 2.75)) * pos + 0.75); + } else if (pos < (2.5 / 2.75)) { + return (7.5625 * (pos -= (2.25 / 2.75)) * pos + 0.9375); + } else { + return (7.5625 * (pos -= (2.625 / 2.75)) * pos + 0.984375); + } + }, + + bouncePast: function (pos) { + if (pos < (1 / 2.75)) { + return (7.5625 * pos * pos); + } else if (pos < (2 / 2.75)) { + return 2 - (7.5625 * (pos -= (1.5 / 2.75)) * pos + 0.75); + } else if (pos < (2.5 / 2.75)) { + return 2 - (7.5625 * (pos -= (2.25 / 2.75)) * pos + 0.9375); + } else { + return 2 - (7.5625 * (pos -= (2.625 / 2.75)) * pos + 0.984375); + } + }, + + easeFromTo: function (pos) { + if ((pos /= 0.5) < 1) {return 0.5 * Math.pow(pos,4);} + return -0.5 * ((pos -= 2) * Math.pow(pos,3) - 2); + }, + + easeFrom: function (pos) { + return Math.pow(pos,4); + }, + + easeTo: function (pos) { + return Math.pow(pos,0.25); + } + }); + + }()); + + /*! + * The Bezier magic in this file is adapted/copied almost wholesale from + * [Scripty2](https://github.com/madrobby/scripty2/blob/master/src/effects/transitions/cubic-bezier.js), + * which was adapted from Apple code (which probably came from + * [here](http://opensource.apple.com/source/WebCore/WebCore-955.66/platform/graphics/UnitBezier.h)). + * Special thanks to Apple and Thomas Fuchs for much of this code. + */ + + /*! + * Copyright (c) 2006 Apple Computer, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder(s) nor the names of any + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + ;(function () { + // port of webkit cubic bezier handling by http://www.netzgesta.de/dev/ + function cubicBezierAtTime(t,p1x,p1y,p2x,p2y,duration) { + var ax = 0,bx = 0,cx = 0,ay = 0,by = 0,cy = 0; + function sampleCurveX(t) {return ((ax * t + bx) * t + cx) * t;} + function sampleCurveY(t) {return ((ay * t + by) * t + cy) * t;} + function sampleCurveDerivativeX(t) {return (3.0 * ax * t + 2.0 * bx) * t + cx;} + function solveEpsilon(duration) {return 1.0 / (200.0 * duration);} + function solve(x,epsilon) {return sampleCurveY(solveCurveX(x,epsilon));} + function fabs(n) {if (n >= 0) {return n;}else {return 0 - n;}} + function solveCurveX(x,epsilon) { + var t0,t1,t2,x2,d2,i; + for (t2 = x, i = 0; i < 8; i++) {x2 = sampleCurveX(t2) - x; if (fabs(x2) < epsilon) {return t2;} d2 = sampleCurveDerivativeX(t2); if (fabs(d2) < 1e-6) {break;} t2 = t2 - x2 / d2;} + t0 = 0.0; t1 = 1.0; t2 = x; if (t2 < t0) {return t0;} if (t2 > t1) {return t1;} + while (t0 < t1) {x2 = sampleCurveX(t2); if (fabs(x2 - x) < epsilon) {return t2;} if (x > x2) {t0 = t2;}else {t1 = t2;} t2 = (t1 - t0) * 0.5 + t0;} + return t2; // Failure. + } + cx = 3.0 * p1x; bx = 3.0 * (p2x - p1x) - cx; ax = 1.0 - cx - bx; cy = 3.0 * p1y; by = 3.0 * (p2y - p1y) - cy; ay = 1.0 - cy - by; + return solve(t, solveEpsilon(duration)); + } + /*! + * getCubicBezierTransition(x1, y1, x2, y2) -> Function + * + * Generates a transition easing function that is compatible + * with WebKit's CSS transitions `-webkit-transition-timing-function` + * CSS property. + * + * The W3C has more information about + * <a href="http://www.w3.org/TR/css3-transitions/#transition-timing-function_tag"> + * CSS3 transition timing functions</a>. + * + * @param {number} x1 + * @param {number} y1 + * @param {number} x2 + * @param {number} y2 + * @return {function} + */ + function getCubicBezierTransition (x1, y1, x2, y2) { + return function (pos) { + return cubicBezierAtTime(pos,x1,y1,x2,y2,1); + }; + } + // End ported code + + /** + * Creates a Bezier easing function and attaches it to `Tweenable.prototype.formula`. This function gives you total control over the easing curve. Matthew Lein's [Ceaser](http://matthewlein.com/ceaser/) is a useful tool for visualizing the curves you can make with this function. + * + * @param {string} name The name of the easing curve. Overwrites the old easing function on Tweenable.prototype.formula if it exists. + * @param {number} x1 + * @param {number} y1 + * @param {number} x2 + * @param {number} y2 + * @return {function} The easing function that was attached to Tweenable.prototype.formula. + */ + Tweenable.setBezierFunction = function (name, x1, y1, x2, y2) { + var cubicBezierTransition = getCubicBezierTransition(x1, y1, x2, y2); + cubicBezierTransition.x1 = x1; + cubicBezierTransition.y1 = y1; + cubicBezierTransition.x2 = x2; + cubicBezierTransition.y2 = y2; + + return Tweenable.prototype.formula[name] = cubicBezierTransition; + }; + + + /** + * `delete`s an easing function from `Tweenable.prototype.formula`. Be careful with this method, as it `delete`s whatever easing formula matches `name` (which means you can delete default Shifty easing functions). + * + * @param {string} name The name of the easing function to delete. + * @return {function} + */ + Tweenable.unsetBezierFunction = function (name) { + delete Tweenable.prototype.formula[name]; + }; + + })(); + + ;(function () { + + function getInterpolatedValues ( + from, current, targetState, position, easing) { + return Tweenable.tweenProps( + position, current, from, targetState, 1, 0, easing); + } + + // Fake a Tweenable and patch some internals. This approach allows us to + // skip uneccessary processing and object recreation, cutting down on garbage + // collection pauses. + var mockTweenable = new Tweenable(); + mockTweenable._filterArgs = []; + + /** + * Compute the midpoint of two Objects. This method effectively calculates a specific frame of animation that [Tweenable#tween](shifty.core.js.html#tween) does many times over the course of a tween. + * + * Example: + * + * var interpolatedValues = Tweenable.interpolate({ + * width: '100px', + * opacity: 0, + * color: '#fff' + * }, { + * width: '200px', + * opacity: 1, + * color: '#000' + * }, 0.5); + * + * console.log(interpolatedValues); + * // {opacity: 0.5, width: "150px", color: "rgb(127,127,127)"} + * + * @param {Object} from The starting values to tween from. + * @param {Object} targetState The ending values to tween to. + * @param {number} position The normalized position value (between 0.0 and 1.0) to interpolate the values between `from` and `to` for. `from` represents 0 and `to` represents `1`. + * @param {string|Object} easing The easing curve(s) to calculate the midpoint against. You can reference any easing function attached to `Tweenable.prototype.formula`. If omitted, this defaults to "linear". + * @return {Object} + */ + Tweenable.interpolate = function (from, targetState, position, easing) { + var current = Tweenable.shallowCopy({}, from); + var easingObject = Tweenable.composeEasingObject( + from, easing || 'linear'); + + mockTweenable.set({}); + + // Alias and reuse the _filterArgs array instead of recreating it. + var filterArgs = mockTweenable._filterArgs; + filterArgs.length = 0; + filterArgs[0] = current; + filterArgs[1] = from; + filterArgs[2] = targetState; + filterArgs[3] = easingObject; + + // Any defined value transformation must be applied + Tweenable.applyFilter(mockTweenable, 'tweenCreated'); + Tweenable.applyFilter(mockTweenable, 'beforeTween'); + + var interpolatedValues = getInterpolatedValues( + from, current, targetState, position, easingObject); + + // Transform values back into their original format + Tweenable.applyFilter(mockTweenable, 'afterTween'); + + return interpolatedValues; + }; + + }()); + + /** + * Adds string interpolation support to Shifty. + * + * The Token extension allows Shifty to tween numbers inside of strings. Among + * other things, this allows you to animate CSS properties. For example, you + * can do this: + * + * var tweenable = new Tweenable(); + * tweenable.tween({ + * from: { transform: 'translateX(45px)'}, + * to: { transform: 'translateX(90xp)'} + * }); + * + * ` ` + * `translateX(45)` will be tweened to `translateX(90)`. To demonstrate: + * + * var tweenable = new Tweenable(); + * tweenable.tween({ + * from: { transform: 'translateX(45px)'}, + * to: { transform: 'translateX(90px)'}, + * step: function (state) { + * console.log(state.transform); + * } + * }); + * + * ` ` + * The above snippet will log something like this in the console: + * + * translateX(60.3px) + * ... + * translateX(76.05px) + * ... + * translateX(90px) + * + * ` ` + * Another use for this is animating colors: + * + * var tweenable = new Tweenable(); + * tweenable.tween({ + * from: { color: 'rgb(0,255,0)'}, + * to: { color: 'rgb(255,0,255)'}, + * step: function (state) { + * console.log(state.color); + * } + * }); + * + * ` ` + * The above snippet will log something like this: + * + * rgb(84,170,84) + * ... + * rgb(170,84,170) + * ... + * rgb(255,0,255) + * + * ` ` + * This extension also supports hexadecimal colors, in both long (`#ff00ff`) + * and short (`#f0f`) forms. Be aware that hexadecimal input values will be + * converted into the equivalent RGB output values. This is done to optimize + * for performance. + * + * var tweenable = new Tweenable(); + * tweenable.tween({ + * from: { color: '#0f0'}, + * to: { color: '#f0f'}, + * step: function (state) { + * console.log(state.color); + * } + * }); + * + * ` ` + * This snippet will generate the same output as the one before it because + * equivalent values were supplied (just in hexadecimal form rather than RGB): + * + * rgb(84,170,84) + * ... + * rgb(170,84,170) + * ... + * rgb(255,0,255) + * + * ` ` + * ` ` + * ## Easing support + * + * Easing works somewhat differently in the Token extension. This is because + * some CSS properties have multiple values in them, and you might need to + * tween each value along its own easing curve. A basic example: + * + * var tweenable = new Tweenable(); + * tweenable.tween({ + * from: { transform: 'translateX(0px) translateY(0px)'}, + * to: { transform: 'translateX(100px) translateY(100px)'}, + * easing: { transform: 'easeInQuad' }, + * step: function (state) { + * console.log(state.transform); + * } + * }); + * + * ` ` + * The above snippet create values like this: + * + * translateX(11.560000000000002px) translateY(11.560000000000002px) + * ... + * translateX(46.24000000000001px) translateY(46.24000000000001px) + * ... + * translateX(100px) translateY(100px) + * + * ` ` + * In this case, the values for `translateX` and `translateY` are always the + * same for each step of the tween, because they have the same start and end + * points and both use the same easing curve. We can also tween `translateX` + * and `translateY` along independent curves: + * + * var tweenable = new Tweenable(); + * tweenable.tween({ + * from: { transform: 'translateX(0px) translateY(0px)'}, + * to: { transform: 'translateX(100px) translateY(100px)'}, + * easing: { transform: 'easeInQuad bounce' }, + * step: function (state) { + * console.log(state.transform); + * } + * }); + * + * ` ` + * The above snippet create values like this: + * + * translateX(10.89px) translateY(82.355625px) + * ... + * translateX(44.89000000000001px) translateY(86.73062500000002px) + * ... + * translateX(100px) translateY(100px) + * + * ` ` + * `translateX` and `translateY` are not in sync anymore, because `easeInQuad` + * was specified for `translateX` and `bounce` for `translateY`. Mixing and + * matching easing curves can make for some interesting motion in your + * animations. + * + * The order of the space-separated easing curves correspond the token values + * they apply to. If there are more token values than easing curves listed, + * the last easing curve listed is used. + */ + function token () { + // Functionality for this extension runs implicitly if it is loaded. + } /*!*/ + + // token function is defined above only so that dox-foundation sees it as + // documentation and renders it. It is never used, and is optimized away at + // build time. + + ;(function (Tweenable) { + + /*! + * @typedef {{ + * formatString: string + * chunkNames: Array.<string> + * }} + */ + var formatManifest; + + // CONSTANTS + + var R_NUMBER_COMPONENT = /(\d|\-|\.)/; + var R_FORMAT_CHUNKS = /([^\-0-9\.]+)/g; + var R_UNFORMATTED_VALUES = /[0-9.\-]+/g; + var R_RGB = new RegExp( + 'rgb\\(' + R_UNFORMATTED_VALUES.source + + (/,\s*/.source) + R_UNFORMATTED_VALUES.source + + (/,\s*/.source) + R_UNFORMATTED_VALUES.source + '\\)', 'g'); + var R_RGB_PREFIX = /^.*\(/; + var R_HEX = /#([0-9]|[a-f]){3,6}/gi; + var VALUE_PLACEHOLDER = 'VAL'; + + // HELPERS + + var getFormatChunksFrom_accumulator = []; + /*! + * @param {Array.number} rawValues + * @param {string} prefix + * + * @return {Array.<string>} + */ + function getFormatChunksFrom (rawValues, prefix) { + getFormatChunksFrom_accumulator.length = 0; + + var rawValuesLength = rawValues.length; + var i; + + for (i = 0; i < rawValuesLength; i++) { + getFormatChunksFrom_accumulator.push('_' + prefix + '_' + i); + } + + return getFormatChunksFrom_accumulator; + } + + /*! + * @param {string} formattedString + * + * @return {string} + */ + function getFormatStringFrom (formattedString) { + var chunks = formattedString.match(R_FORMAT_CHUNKS); + + if (!chunks) { + // chunks will be null if there were no tokens to parse in + // formattedString (for example, if formattedString is '2'). Coerce + // chunks to be useful here. + chunks = ['', '']; + + // If there is only one chunk, assume that the string is a number + // followed by a token... + // NOTE: This may be an unwise assumption. + } else if (chunks.length === 1 || + // ...or if the string starts with a number component (".", "-", or a + // digit)... + formattedString[0].match(R_NUMBER_COMPONENT)) { + // ...prepend an empty string here to make sure that the formatted number + // is properly replaced by VALUE_PLACEHOLDER + chunks.unshift(''); + } + + return chunks.join(VALUE_PLACEHOLDER); + } + + /*! + * Convert all hex color values within a string to an rgb string. + * + * @param {Object} stateObject + * + * @return {Object} The modified obj + */ + function sanitizeObjectForHexProps (stateObject) { + Tweenable.each(stateObject, function (prop) { + var currentProp = stateObject[prop]; + + if (typeof currentProp === 'string' && currentProp.match(R_HEX)) { + stateObject[prop] = sanitizeHexChunksToRGB(currentProp); + } + }); + } + + /*! + * @param {string} str + * + * @return {string} + */ + function sanitizeHexChunksToRGB (str) { + return filterStringChunks(R_HEX, str, convertHexToRGB); + } + + /*! + * @param {string} hexString + * + * @return {string} + */ + function convertHexToRGB (hexString) { + var rgbArr = hexToRGBArray(hexString); + return 'rgb(' + rgbArr[0] + ',' + rgbArr[1] + ',' + rgbArr[2] + ')'; + } + + var hexToRGBArray_returnArray = []; + /*! + * Convert a hexadecimal string to an array with three items, one each for + * the red, blue, and green decimal values. + * + * @param {string} hex A hexadecimal string. + * + * @returns {Array.<number>} The converted Array of RGB values if `hex` is a + * valid string, or an Array of three 0's. + */ + function hexToRGBArray (hex) { + + hex = hex.replace(/#/, ''); + + // If the string is a shorthand three digit hex notation, normalize it to + // the standard six digit notation + if (hex.length === 3) { + hex = hex.split(''); + hex = hex[0] + hex[0] + hex[1] + hex[1] + hex[2] + hex[2]; + } + + hexToRGBArray_returnArray[0] = hexToDec(hex.substr(0, 2)); + hexToRGBArray_returnArray[1] = hexToDec(hex.substr(2, 2)); + hexToRGBArray_returnArray[2] = hexToDec(hex.substr(4, 2)); + + return hexToRGBArray_returnArray; + } + + /*! + * Convert a base-16 number to base-10. + * + * @param {Number|String} hex The value to convert + * + * @returns {Number} The base-10 equivalent of `hex`. + */ + function hexToDec (hex) { + return parseInt(hex, 16); + } + + /*! + * Runs a filter operation on all chunks of a string that match a RegExp + * + * @param {RegExp} pattern + * @param {string} unfilteredString + * @param {function(string)} filter + * + * @return {string} + */ + function filterStringChunks (pattern, unfilteredString, filter) { + var pattenMatches = unfilteredString.match(pattern); + var filteredString = unfilteredString.replace(pattern, VALUE_PLACEHOLDER); + + if (pattenMatches) { + var pattenMatchesLength = pattenMatches.length; + var currentChunk; + + for (var i = 0; i < pattenMatchesLength; i++) { + currentChunk = pattenMatches.shift(); + filteredString = filteredString.replace( + VALUE_PLACEHOLDER, filter(currentChunk)); + } + } + + return filteredString; + } + + /*! + * Check for floating point values within rgb strings and rounds them. + * + * @param {string} formattedString + * + * @return {string} + */ + function sanitizeRGBChunks (formattedString) { + return filterStringChunks(R_RGB, formattedString, sanitizeRGBChunk); + } + + /*! + * @param {string} rgbChunk + * + * @return {string} + */ + function sanitizeRGBChunk (rgbChunk) { + var numbers = rgbChunk.match(R_UNFORMATTED_VALUES); + var numbersLength = numbers.length; + var sanitizedString = rgbChunk.match(R_RGB_PREFIX)[0]; + + for (var i = 0; i < numbersLength; i++) { + sanitizedString += parseInt(numbers[i], 10) + ','; + } + + sanitizedString = sanitizedString.slice(0, -1) + ')'; + + return sanitizedString; + } + + /*! + * @param {Object} stateObject + * + * @return {Object} An Object of formatManifests that correspond to + * the string properties of stateObject + */ + function getFormatManifests (stateObject) { + var manifestAccumulator = {}; + + Tweenable.each(stateObject, function (prop) { + var currentProp = stateObject[prop]; + + if (typeof currentProp === 'string') { + var rawValues = getValuesFrom(currentProp); + + manifestAccumulator[prop] = { + 'formatString': getFormatStringFrom(currentProp) + ,'chunkNames': getFormatChunksFrom(rawValues, prop) + }; + } + }); + + return manifestAccumulator; + } + + /*! + * @param {Object} stateObject + * @param {Object} formatManifests + */ + function expandFormattedProperties (stateObject, formatManifests) { + Tweenable.each(formatManifests, function (prop) { + var currentProp = stateObject[prop]; + var rawValues = getValuesFrom(currentProp); + var rawValuesLength = rawValues.length; + + for (var i = 0; i < rawValuesLength; i++) { + stateObject[formatManifests[prop].chunkNames[i]] = +rawValues[i]; + } + + delete stateObject[prop]; + }); + } + + /*! + * @param {Object} stateObject + * @param {Object} formatManifests + */ + function collapseFormattedProperties (stateObject, formatManifests) { + Tweenable.each(formatManifests, function (prop) { + var currentProp = stateObject[prop]; + var formatChunks = extractPropertyChunks( + stateObject, formatManifests[prop].chunkNames); + var valuesList = getValuesList( + formatChunks, formatManifests[prop].chunkNames); + currentProp = getFormattedValues( + formatManifests[prop].formatString, valuesList); + stateObject[prop] = sanitizeRGBChunks(currentProp); + }); + } + + /*! + * @param {Object} stateObject + * @param {Array.<string>} chunkNames + * + * @return {Object} The extracted value chunks. + */ + function extractPropertyChunks (stateObject, chunkNames) { + var extractedValues = {}; + var currentChunkName, chunkNamesLength = chunkNames.length; + + for (var i = 0; i < chunkNamesLength; i++) { + currentChunkName = chunkNames[i]; + extractedValues[currentChunkName] = stateObject[currentChunkName]; + delete stateObject[currentChunkName]; + } + + return extractedValues; + } + + var getValuesList_accumulator = []; + /*! + * @param {Object} stateObject + * @param {Array.<string>} chunkNames + * + * @return {Array.<number>} + */ + function getValuesList (stateObject, chunkNames) { + getValuesList_accumulator.length = 0; + var chunkNamesLength = chunkNames.length; + + for (var i = 0; i < chunkNamesLength; i++) { + getValuesList_accumulator.push(stateObject[chunkNames[i]]); + } + + return getValuesList_accumulator; + } + + /*! + * @param {string} formatString + * @param {Array.<number>} rawValues + * + * @return {string} + */ + function getFormattedValues (formatString, rawValues) { + var formattedValueString = formatString; + var rawValuesLength = rawValues.length; + + for (var i = 0; i < rawValuesLength; i++) { + formattedValueString = formattedValueString.replace( + VALUE_PLACEHOLDER, +rawValues[i].toFixed(4)); + } + + return formattedValueString; + } + + /*! + * Note: It's the duty of the caller to convert the Array elements of the + * return value into numbers. This is a performance optimization. + * + * @param {string} formattedString + * + * @return {Array.<string>|null} + */ + function getValuesFrom (formattedString) { + return formattedString.match(R_UNFORMATTED_VALUES); + } + + /*! + * @param {Object} easingObject + * @param {Object} tokenData + */ + function expandEasingObject (easingObject, tokenData) { + Tweenable.each(tokenData, function (prop) { + var currentProp = tokenData[prop]; + var chunkNames = currentProp.chunkNames; + var chunkLength = chunkNames.length; + var easingChunks = easingObject[prop].split(' '); + var lastEasingChunk = easingChunks[easingChunks.length - 1]; + + for (var i = 0; i < chunkLength; i++) { + easingObject[chunkNames[i]] = easingChunks[i] || lastEasingChunk; + } + + delete easingObject[prop]; + }); + } + + /*! + * @param {Object} easingObject + * @param {Object} tokenData + */ + function collapseEasingObject (easingObject, tokenData) { + Tweenable.each(tokenData, function (prop) { + var currentProp = tokenData[prop]; + var chunkNames = currentProp.chunkNames; + var chunkLength = chunkNames.length; + var composedEasingString = ''; + + for (var i = 0; i < chunkLength; i++) { + composedEasingString += ' ' + easingObject[chunkNames[i]]; + delete easingObject[chunkNames[i]]; + } + + easingObject[prop] = composedEasingString.substr(1); + }); + } + + Tweenable.prototype.filter.token = { + 'tweenCreated': function (currentState, fromState, toState, easingObject) { + sanitizeObjectForHexProps(currentState); + sanitizeObjectForHexProps(fromState); + sanitizeObjectForHexProps(toState); + this._tokenData = getFormatManifests(currentState); + }, + + 'beforeTween': function (currentState, fromState, toState, easingObject) { + expandEasingObject(easingObject, this._tokenData); + expandFormattedProperties(currentState, this._tokenData); + expandFormattedProperties(fromState, this._tokenData); + expandFormattedProperties(toState, this._tokenData); + }, + + 'afterTween': function (currentState, fromState, toState, easingObject) { + collapseFormattedProperties(currentState, this._tokenData); + collapseFormattedProperties(fromState, this._tokenData); + collapseFormattedProperties(toState, this._tokenData); + collapseEasingObject(easingObject, this._tokenData); + } + }; + + } (Tweenable)); + + }(window)); + + return window.Tweenable; +}); + +(function() { + "use strict"; + + angular.module('angular-carousel') + + .filter('carouselSlice', function() { + return function(collection, start, size) { + if (angular.isArray(collection)) { + return collection.slice(start, start + size); + } else if (angular.isObject(collection)) { + // dont try to slice collections :) + return collection; + } + }; + }); + +})(); +/* jshint ignore:end */ diff --git a/www/external/origjs/angular-circular-navigation.js b/www/external/origjs/angular-circular-navigation.js new file mode 100644 index 00000000..17488768 --- /dev/null +++ b/www/external/origjs/angular-circular-navigation.js @@ -0,0 +1,70 @@ +// PP - Modified to show at right angles +/* jshint -W041 */ +/* jslint browser: true*/ +/* global cordova,StatusBar,angular,console */ + +(function () { + + 'use strict'; + + /*global define, module, exports, require */ + // options.isOpen + + /* istanbul ignore next */ + var angular = window.angular ? window.angular : 'undefined' !== typeof require ? require('angular') : undefined; + + var circular = angular.module('angularCircularNavigation', []) + .directive('circular', ['$compile', function ($compile) { + + return { + restrict: 'EA', + scope: { + options: '=' + }, + template: '<button ng-click="toggleMenu()" class="cn-button {{options.button.size}}" ng-class="options.button.cssClass" style="background: {{options.button.background ? options.button.background : options.background}}; color: {{options.button.color ? options.button.color :options.color}};">{{options.content}}</button>' + + '<div class="cn-wrapper {{options.size}} items-{{options.items.length}}" ng-class="{\'opened-nav\':options.isOpen}"><ul>' + + '<li ng-repeat="item in options.items">' + + '<a ng-hide="item.empty" ng-click="perform(options, item)" ng-class="{\'is-active\': item.isActive}" class="{{item.cssClass}}" style="background: {{item.background ? item.background : options.background}}; color: {{item.color ? item.color : options.color}};">' + + '<span>{{item.content}}</span>' + + '</a></li></ul></div>', + controller: ['$scope', '$element', '$attrs', + function ($scope, $element, $attrs) { + + $scope.toggleMenu = function () { + //PP + if (typeof $scope.options.button.onclick === 'function') + { + // PP - console.log ("FUNCTION"); + //$scope.options.isOpen = !$scope.options.isOpen; + $scope.options.button.onclick(); + } + else + { + // console.log ("NO FUNCTION"); + $scope.options.isOpen = !$scope.options.isOpen; + } + }; + + $scope.perform = function (options, item) { + if (typeof item.onclick === 'function') { + item.onclick(options, item); + } + + if ($scope.options.toggleOnClick) { + $scope.toggleMenu(); + } + }; + + } + ] + }; + }]); + + /* istanbul ignore next */ + if (typeof define === 'function' && define.amd) { + define('circular', ['angular'], circular); + } else if ('undefined' !== typeof exports && 'undefined' !== typeof module) { + module.exports = circular; + } + +})(); diff --git a/www/external/origjs/angular-ios9-uiwebview.patch.js b/www/external/origjs/angular-ios9-uiwebview.patch.js new file mode 100644 index 00000000..c52cad82 --- /dev/null +++ b/www/external/origjs/angular-ios9-uiwebview.patch.js @@ -0,0 +1,73 @@ +/** + * ================== angular-ios9-uiwebview.patch.js v1.1.0 ================== + * + * This patch works around iOS9 UIWebView regression that causes infinite digest + * errors in Angular. + * + * The patch can be applied to Angular 1.2.0 – 1.4.5. Newer versions of Angular + * have the workaround baked in. + * + * To apply this patch load/bundle this file with your application and add a + * dependency on the "ngIOS9Patch" module to your main app module. + * + * For example: + * + * ``` + * angular.module('myApp', ['ngRoute'])` + * ``` + * + * becomes + * + * ``` + * angular.module('myApp', ['ngRoute', 'ngIOS9UIWebViewPatch']) + * ``` + * + * + * More info: + * - https://openradar.appspot.com/22186109 + * - https://github.com/angular/angular.js/issues/12241 + * - https://github.com/driftyco/ionic/issues/4082 + * + * + * @license AngularJS + * (c) 2010-2015 Google, Inc. http://angularjs.org + * License: MIT + */ + +angular.module('ngIOS9UIWebViewPatch', ['ng']).config(function($provide) { + $provide.decorator('$browser', ['$delegate', '$window', function($delegate, $window) { + + if (isIOS9UIWebView($window.navigator.userAgent)) { + return applyIOS9Shim($delegate); + } + + return $delegate; + + function isIOS9UIWebView(userAgent) { + return /(iPhone|iPad|iPod).* OS 9_\d/.test(userAgent) && !/Version\/9\./.test(userAgent); + } + + function applyIOS9Shim(browser) { + var pendingLocationUrl = null; + var originalUrlFn= browser.url; + + browser.url = function() { + if (arguments.length) { + pendingLocationUrl = arguments[0]; + return originalUrlFn.apply(browser, arguments); + } + + return pendingLocationUrl || originalUrlFn.apply(browser, arguments); + }; + + window.addEventListener('popstate', clearPendingLocationUrl, false); + window.addEventListener('hashchange', clearPendingLocationUrl, false); + + function clearPendingLocationUrl() { + pendingLocationUrl = null; + } + + return browser; + } + }]); +}); diff --git a/www/external/origjs/canvas-toBlob.js b/www/external/origjs/canvas-toBlob.js new file mode 100644 index 00000000..6d895a78 --- /dev/null +++ b/www/external/origjs/canvas-toBlob.js @@ -0,0 +1,124 @@ +/* canvas-toBlob.js + * A canvas.toBlob() implementation. + * 2013-12-27 + * + * By Eli Grey, http://eligrey.com and Devin Samarin, https://github.com/eboyjr + * License: MIT + * See https://github.com/eligrey/canvas-toBlob.js/blob/master/LICENSE.md + */ + +/*global self */ +/*jslint bitwise: true, regexp: true, confusion: true, es5: true, vars: true, white: true, + plusplus: true */ + +/*! @source http://purl.eligrey.com/github/canvas-toBlob.js/blob/master/canvas-toBlob.js */ + +(function(view) { +"use strict"; +var + Uint8Array = view.Uint8Array + , HTMLCanvasElement = view.HTMLCanvasElement + , canvas_proto = HTMLCanvasElement && HTMLCanvasElement.prototype + , is_base64_regex = /\s*;\s*base64\s*(?:;|$)/i + , to_data_url = "toDataURL" + , base64_ranks + , decode_base64 = function(base64) { + var + len = base64.length + , buffer = new Uint8Array(len / 4 * 3 | 0) + , i = 0 + , outptr = 0 + , last = [0, 0] + , state = 0 + , save = 0 + , rank + , code + , undef + ; + while (len--) { + code = base64.charCodeAt(i++); + rank = base64_ranks[code-43]; + if (rank !== 255 && rank !== undef) { + last[1] = last[0]; + last[0] = code; + save = (save << 6) | rank; + state++; + if (state === 4) { + buffer[outptr++] = save >>> 16; + if (last[1] !== 61 /* padding character */) { + buffer[outptr++] = save >>> 8; + } + if (last[0] !== 61 /* padding character */) { + buffer[outptr++] = save; + } + state = 0; + } + } + } + // 2/3 chance there's going to be some null bytes at the end, but that + // doesn't really matter with most image formats. + // If it somehow matters for you, truncate the buffer up outptr. + return buffer; + } +; +if (Uint8Array) { + base64_ranks = new Uint8Array([ + 62, -1, -1, -1, 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1 + , -1, -1, 0, -1, -1, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 + , 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25 + , -1, -1, -1, -1, -1, -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35 + , 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51 + ]); +} +if (HTMLCanvasElement && !canvas_proto.toBlob) { + canvas_proto.toBlob = function(callback, type /*, ...args*/) { + if (!type) { + type = "image/png"; + } if (this.mozGetAsFile) { + callback(this.mozGetAsFile("canvas", type)); + return; + } if (this.msToBlob && /^\s*image\/png\s*(?:$|;)/i.test(type)) { + callback(this.msToBlob()); + return; + } + + var + args = Array.prototype.slice.call(arguments, 1) + , dataURI = this[to_data_url].apply(this, args) + , header_end = dataURI.indexOf(",") + , data = dataURI.substring(header_end + 1) + , is_base64 = is_base64_regex.test(dataURI.substring(0, header_end)) + , blob + ; + if (Blob.fake) { + // no reason to decode a data: URI that's just going to become a data URI again + blob = new Blob + if (is_base64) { + blob.encoding = "base64"; + } else { + blob.encoding = "URI"; + } + blob.data = data; + blob.size = data.length; + } else if (Uint8Array) { + if (is_base64) { + blob = new Blob([decode_base64(data)], {type: type}); + } else { + blob = new Blob([decodeURIComponent(data)], {type: type}); + } + } + callback(blob); + }; + + if (canvas_proto.toDataURLHD) { + canvas_proto.toBlobHD = function() { + to_data_url = "toDataURLHD"; + var blob = this.toBlob(); + to_data_url = "toDataURL"; + return blob; + } + } else { + canvas_proto.toBlobHD = canvas_proto.toBlob; + } +} +}(typeof self !== "undefined" && self || typeof window !== "undefined" && window || this.content || this)); diff --git a/www/external/origjs/draggabilly.pkgd.js b/www/external/origjs/draggabilly.pkgd.js new file mode 100644 index 00000000..bef3db35 --- /dev/null +++ b/www/external/origjs/draggabilly.pkgd.js @@ -0,0 +1,1530 @@ +/*! + * Draggabilly PACKAGED v2.1.0 + * Make that shiz draggable + * http://draggabilly.desandro.com + * MIT license + */ + +/** + * Bridget makes jQuery widgets + * v2.0.0 + * MIT license + */ + +/* jshint browser: true, strict: true, undef: true, unused: true */ + +( function( window, factory ) { + + /* globals define: false, module: false, require: false */ + + if ( typeof define == 'function' && define.amd ) { + // AMD + define( 'jquery-bridget/jquery-bridget',[ 'jquery' ], function( jQuery ) { + 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 ) { + + +// ----- 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; + +})); + +/*! + * 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 ) { + + + 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() { + + +// -------------------------- 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; + +}); + +/** + * EvEmitter v1.0.1 + * Lil' event emitter + * MIT License + */ + +/* jshint unused: true, undef: true, strict: true */ + +( function( global, factory ) { + // universal module definition + /* jshint strict: false */ /* globals define, module */ + 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(); + } + +}( 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 array + 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; + +})); + +/*! + * Unipointer v2.1.0 + * base class for doing one thing with pointer event + * MIT license + */ + +/*jshint browser: true, undef: true, unused: true, strict: true */ + +( function( window, factory ) { + // universal module definition + /* jshint strict: false */ /*global define, module, require */ + if ( typeof define == 'function' && define.amd ) { + // AMD + define( 'unipointer/unipointer',[ + 'ev-emitter/ev-emitter' + ], function( EvEmitter ) { + return factory( window, EvEmitter ); + }); + } else if ( typeof module == 'object' && module.exports ) { + // CommonJS + module.exports = factory( + window, + require('ev-emitter') + ); + } else { + // browser global + window.Unipointer = factory( + window, + window.EvEmitter + ); + } + +}( window, function factory( window, EvEmitter ) { + + + +function noop() {} + +function Unipointer() {} + +// inherit EvEmitter +var proto = Unipointer.prototype = Object.create( EvEmitter.prototype ); + +proto.bindStartEvent = function( elem ) { + this._bindStartEvent( elem, true ); +}; + +proto.unbindStartEvent = function( elem ) { + this._bindStartEvent( elem, false ); +}; + +/** + * works as unbinder, as you can ._bindStart( false ) to unbind + * @param {Boolean} isBind - will unbind if falsey + */ +proto._bindStartEvent = function( elem, isBind ) { + // munge isBind, default to true + isBind = isBind === undefined ? true : !!isBind; + var bindMethod = isBind ? 'addEventListener' : 'removeEventListener'; + + if ( window.navigator.pointerEnabled ) { + // W3C Pointer Events, IE11. See https://coderwall.com/p/mfreca + elem[ bindMethod ]( 'pointerdown', this ); + } else if ( window.navigator.msPointerEnabled ) { + // IE10 Pointer Events + elem[ bindMethod ]( 'MSPointerDown', this ); + } else { + // listen for both, for devices like Chrome Pixel + elem[ bindMethod ]( 'mousedown', this ); + elem[ bindMethod ]( 'touchstart', this ); + } +}; + +// trigger handler methods for events +proto.handleEvent = function( event ) { + var method = 'on' + event.type; + if ( this[ method ] ) { + this[ method ]( event ); + } +}; + +// returns the touch that we're keeping track of +proto.getTouch = function( touches ) { + for ( var i=0; i < touches.length; i++ ) { + var touch = touches[i]; + if ( touch.identifier == this.pointerIdentifier ) { + return touch; + } + } +}; + +// ----- start event ----- // + +proto.onmousedown = function( event ) { + // dismiss clicks from right or middle buttons + var button = event.button; + if ( button && ( button !== 0 && button !== 1 ) ) { + return; + } + this._pointerDown( event, event ); +}; + +proto.ontouchstart = function( event ) { + this._pointerDown( event, event.changedTouches[0] ); +}; + +proto.onMSPointerDown = +proto.onpointerdown = function( event ) { + this._pointerDown( event, event ); +}; + +/** + * pointer start + * @param {Event} event + * @param {Event or Touch} pointer + */ +proto._pointerDown = function( event, pointer ) { + // dismiss other pointers + if ( this.isPointerDown ) { + return; + } + + this.isPointerDown = true; + // save pointer identifier to match up touch events + this.pointerIdentifier = pointer.pointerId !== undefined ? + // pointerId for pointer events, touch.indentifier for touch events + pointer.pointerId : pointer.identifier; + + this.pointerDown( event, pointer ); +}; + +proto.pointerDown = function( event, pointer ) { + this._bindPostStartEvents( event ); + this.emitEvent( 'pointerDown', [ event, pointer ] ); +}; + +// hash of events to be bound after start event +var postStartEvents = { + mousedown: [ 'mousemove', 'mouseup' ], + touchstart: [ 'touchmove', 'touchend', 'touchcancel' ], + pointerdown: [ 'pointermove', 'pointerup', 'pointercancel' ], + MSPointerDown: [ 'MSPointerMove', 'MSPointerUp', 'MSPointerCancel' ] +}; + +proto._bindPostStartEvents = function( event ) { + if ( !event ) { + return; + } + // get proper events to match start event + var events = postStartEvents[ event.type ]; + // bind events to node + events.forEach( function( eventName ) { + window.addEventListener( eventName, this ); + }, this ); + // save these arguments + this._boundPointerEvents = events; +}; + +proto._unbindPostStartEvents = function() { + // check for _boundEvents, in case dragEnd triggered twice (old IE8 bug) + if ( !this._boundPointerEvents ) { + return; + } + this._boundPointerEvents.forEach( function( eventName ) { + window.removeEventListener( eventName, this ); + }, this ); + + delete this._boundPointerEvents; +}; + +// ----- move event ----- // + +proto.onmousemove = function( event ) { + this._pointerMove( event, event ); +}; + +proto.onMSPointerMove = +proto.onpointermove = function( event ) { + if ( event.pointerId == this.pointerIdentifier ) { + this._pointerMove( event, event ); + } +}; + +proto.ontouchmove = function( event ) { + var touch = this.getTouch( event.changedTouches ); + if ( touch ) { + this._pointerMove( event, touch ); + } +}; + +/** + * pointer move + * @param {Event} event + * @param {Event or Touch} pointer + * @private + */ +proto._pointerMove = function( event, pointer ) { + this.pointerMove( event, pointer ); +}; + +// public +proto.pointerMove = function( event, pointer ) { + this.emitEvent( 'pointerMove', [ event, pointer ] ); +}; + +// ----- end event ----- // + + +proto.onmouseup = function( event ) { + this._pointerUp( event, event ); +}; + +proto.onMSPointerUp = +proto.onpointerup = function( event ) { + if ( event.pointerId == this.pointerIdentifier ) { + this._pointerUp( event, event ); + } +}; + +proto.ontouchend = function( event ) { + var touch = this.getTouch( event.changedTouches ); + if ( touch ) { + this._pointerUp( event, touch ); + } +}; + +/** + * pointer up + * @param {Event} event + * @param {Event or Touch} pointer + * @private + */ +proto._pointerUp = function( event, pointer ) { + this._pointerDone(); + this.pointerUp( event, pointer ); +}; + +// public +proto.pointerUp = function( event, pointer ) { + this.emitEvent( 'pointerUp', [ event, pointer ] ); +}; + +// ----- pointer done ----- // + +// triggered on pointer up & pointer cancel +proto._pointerDone = function() { + // reset properties + this.isPointerDown = false; + delete this.pointerIdentifier; + // remove events + this._unbindPostStartEvents(); + this.pointerDone(); +}; + +proto.pointerDone = noop; + +// ----- pointer cancel ----- // + +proto.onMSPointerCancel = +proto.onpointercancel = function( event ) { + if ( event.pointerId == this.pointerIdentifier ) { + this._pointerCancel( event, event ); + } +}; + +proto.ontouchcancel = function( event ) { + var touch = this.getTouch( event.changedTouches ); + if ( touch ) { + this._pointerCancel( event, touch ); + } +}; + +/** + * pointer cancel + * @param {Event} event + * @param {Event or Touch} pointer + * @private + */ +proto._pointerCancel = function( event, pointer ) { + this._pointerDone(); + this.pointerCancel( event, pointer ); +}; + +// public +proto.pointerCancel = function( event, pointer ) { + this.emitEvent( 'pointerCancel', [ event, pointer ] ); +}; + +// ----- ----- // + +// utility function for getting x/y coords from event +Unipointer.getPointerPoint = function( pointer ) { + return { + x: pointer.pageX, + y: pointer.pageY + }; +}; + +// ----- ----- // + +return Unipointer; + +})); + +/*! + * Unidragger v2.1.0 + * Draggable base class + * MIT license + */ + +/*jshint browser: true, unused: true, undef: true, strict: true */ + +( function( window, factory ) { + // universal module definition + /*jshint strict: false */ /*globals define, module, require */ + + if ( typeof define == 'function' && define.amd ) { + // AMD + define( 'unidragger/unidragger',[ + 'unipointer/unipointer' + ], function( Unipointer ) { + return factory( window, Unipointer ); + }); + } else if ( typeof module == 'object' && module.exports ) { + // CommonJS + module.exports = factory( + window, + require('unipointer') + ); + } else { + // browser global + window.Unidragger = factory( + window, + window.Unipointer + ); + } + +}( window, function factory( window, Unipointer ) { + + + +// ----- ----- // + +function noop() {} + +// -------------------------- Unidragger -------------------------- // + +function Unidragger() {} + +// inherit Unipointer & EvEmitter +var proto = Unidragger.prototype = Object.create( Unipointer.prototype ); + +// ----- bind start ----- // + +proto.bindHandles = function() { + this._bindHandles( true ); +}; + +proto.unbindHandles = function() { + this._bindHandles( false ); +}; + +var navigator = window.navigator; +/** + * works as unbinder, as you can .bindHandles( false ) to unbind + * @param {Boolean} isBind - will unbind if falsey + */ +proto._bindHandles = function( isBind ) { + // munge isBind, default to true + isBind = isBind === undefined ? true : !!isBind; + // extra bind logic + var binderExtra; + if ( navigator.pointerEnabled ) { + binderExtra = function( handle ) { + // disable scrolling on the element + handle.style.touchAction = isBind ? 'none' : ''; + }; + } else if ( navigator.msPointerEnabled ) { + binderExtra = function( handle ) { + // disable scrolling on the element + handle.style.msTouchAction = isBind ? 'none' : ''; + }; + } else { + binderExtra = noop; + } + // bind each handle + var bindMethod = isBind ? 'addEventListener' : 'removeEventListener'; + for ( var i=0; i < this.handles.length; i++ ) { + var handle = this.handles[i]; + this._bindStartEvent( handle, isBind ); + binderExtra( handle ); + handle[ bindMethod ]( 'click', this ); + } +}; + +// ----- start event ----- // + +/** + * pointer start + * @param {Event} event + * @param {Event or Touch} pointer + */ +proto.pointerDown = function( event, pointer ) { + // dismiss range sliders + if ( event.target.nodeName == 'INPUT' && event.target.type == 'range' ) { + // reset pointerDown logic + this.isPointerDown = false; + delete this.pointerIdentifier; + return; + } + + this._dragPointerDown( event, pointer ); + // kludge to blur focused inputs in dragger + var focused = document.activeElement; + if ( focused && focused.blur ) { + focused.blur(); + } + // bind move and end events + this._bindPostStartEvents( event ); + this.emitEvent( 'pointerDown', [ event, pointer ] ); +}; + +// base pointer down logic +proto._dragPointerDown = function( event, pointer ) { + // track to see when dragging starts + this.pointerDownPoint = Unipointer.getPointerPoint( pointer ); + + var canPreventDefault = this.canPreventDefaultOnPointerDown( event, pointer ); + if ( canPreventDefault ) { + event.preventDefault(); + } +}; + +// overwriteable method so Flickity can prevent for scrolling +proto.canPreventDefaultOnPointerDown = function( event ) { + // prevent default, unless touchstart or <select> + return event.target.nodeName != 'SELECT'; +}; + +// ----- move event ----- // + +/** + * drag move + * @param {Event} event + * @param {Event or Touch} pointer + */ +proto.pointerMove = function( event, pointer ) { + var moveVector = this._dragPointerMove( event, pointer ); + this.emitEvent( 'pointerMove', [ event, pointer, moveVector ] ); + this._dragMove( event, pointer, moveVector ); +}; + +// base pointer move logic +proto._dragPointerMove = function( event, pointer ) { + var movePoint = Unipointer.getPointerPoint( pointer ); + var moveVector = { + x: movePoint.x - this.pointerDownPoint.x, + y: movePoint.y - this.pointerDownPoint.y + }; + // start drag if pointer has moved far enough to start drag + if ( !this.isDragging && this.hasDragStarted( moveVector ) ) { + this._dragStart( event, pointer ); + } + return moveVector; +}; + +// condition if pointer has moved far enough to start drag +proto.hasDragStarted = function( moveVector ) { + return Math.abs( moveVector.x ) > 3 || Math.abs( moveVector.y ) > 3; +}; + + +// ----- end event ----- // + +/** + * pointer up + * @param {Event} event + * @param {Event or Touch} pointer + */ +proto.pointerUp = function( event, pointer ) { + this.emitEvent( 'pointerUp', [ event, pointer ] ); + this._dragPointerUp( event, pointer ); +}; + +proto._dragPointerUp = function( event, pointer ) { + if ( this.isDragging ) { + this._dragEnd( event, pointer ); + } else { + // pointer didn't move enough for drag to start + this._staticClick( event, pointer ); + } +}; + +// -------------------------- drag -------------------------- // + +// dragStart +proto._dragStart = function( event, pointer ) { + this.isDragging = true; + this.dragStartPoint = Unipointer.getPointerPoint( pointer ); + // prevent clicks + this.isPreventingClicks = true; + + this.dragStart( event, pointer ); +}; + +proto.dragStart = function( event, pointer ) { + this.emitEvent( 'dragStart', [ event, pointer ] ); +}; + +// dragMove +proto._dragMove = function( event, pointer, moveVector ) { + // do not drag if not dragging yet + if ( !this.isDragging ) { + return; + } + + this.dragMove( event, pointer, moveVector ); +}; + +proto.dragMove = function( event, pointer, moveVector ) { + event.preventDefault(); + this.emitEvent( 'dragMove', [ event, pointer, moveVector ] ); +}; + +// dragEnd +proto._dragEnd = function( event, pointer ) { + // set flags + this.isDragging = false; + // re-enable clicking async + setTimeout( function() { + delete this.isPreventingClicks; + }.bind( this ) ); + + this.dragEnd( event, pointer ); +}; + +proto.dragEnd = function( event, pointer ) { + this.emitEvent( 'dragEnd', [ event, pointer ] ); +}; + +// ----- onclick ----- // + +// handle all clicks and prevent clicks when dragging +proto.onclick = function( event ) { + if ( this.isPreventingClicks ) { + event.preventDefault(); + } +}; + +// ----- staticClick ----- // + +// triggered after pointer down & up with no/tiny movement +proto._staticClick = function( event, pointer ) { + // ignore emulated mouse up clicks + if ( this.isIgnoringMouseUp && event.type == 'mouseup' ) { + return; + } + + // allow click in <input>s and <textarea>s + var nodeName = event.target.nodeName; + if ( nodeName == 'INPUT' || nodeName == 'TEXTAREA' ) { + event.target.focus(); + } + this.staticClick( event, pointer ); + + // set flag for emulated clicks 300ms after touchend + if ( event.type != 'mouseup' ) { + this.isIgnoringMouseUp = true; + // reset flag after 300ms + setTimeout( function() { + delete this.isIgnoringMouseUp; + }.bind( this ), 400 ); + } +}; + +proto.staticClick = function( event, pointer ) { + this.emitEvent( 'staticClick', [ event, pointer ] ); +}; + +// ----- utils ----- // + +Unidragger.getPointerPoint = Unipointer.getPointerPoint; + +// ----- ----- // + +return Unidragger; + +})); + +/*! + * Draggabilly v2.1.0 + * Make that shiz draggable + * http://draggabilly.desandro.com + * 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( [ + 'get-size/get-size', + 'unidragger/unidragger' + ], + function( getSize, Unidragger ) { + return factory( window, getSize, Unidragger ); + }); + } else if ( typeof module == 'object' && module.exports ) { + // CommonJS + module.exports = factory( + window, + require('get-size'), + require('unidragger') + ); + } else { + // browser global + window.Draggabilly = factory( + window, + window.getSize, + window.Unidragger + ); + } + +}( window, function factory( window, getSize, Unidragger ) { + + + +// vars +var document = window.document; + +function noop() {} + +// -------------------------- helpers -------------------------- // + +// extend objects +function extend( a, b ) { + for ( var prop in b ) { + a[ prop ] = b[ prop ]; + } + return a; +} + +function isElement( obj ) { + return obj instanceof HTMLElement; +} + +// -------------------------- requestAnimationFrame -------------------------- // + +// get rAF, prefixed, if present +var requestAnimationFrame = window.requestAnimationFrame || + window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame; + +// 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; + }; +} + +// -------------------------- support -------------------------- // + +var docElem = document.documentElement; +var transformProperty = typeof docElem.style.transform == 'string' ? + 'transform' : 'WebkitTransform'; + +var jQuery = window.jQuery; + +// -------------------------- -------------------------- // + +function Draggabilly( element, options ) { + // querySelector if string + this.element = typeof element == 'string' ? + document.querySelector( element ) : element; + + if ( jQuery ) { + this.$element = jQuery( this.element ); + } + + // options + this.options = extend( {}, this.constructor.defaults ); + this.option( options ); + + this._create(); +} + +// inherit Unidragger methods +var proto = Draggabilly.prototype = Object.create( Unidragger.prototype ); + +Draggabilly.defaults = { +}; + +/** + * set options + * @param {Object} opts + */ +proto.option = function( opts ) { + extend( this.options, opts ); +}; + +proto._create = function() { + + // properties + this.position = {}; + this._getPosition(); + + this.startPoint = { x: 0, y: 0 }; + this.dragPoint = { x: 0, y: 0 }; + + this.startPosition = extend( {}, this.position ); + + // set relative positioning + var style = getComputedStyle( this.element ); + if ( style.position != 'relative' && style.position != 'absolute' ) { + this.element.style.position = 'relative'; + } + + this.enable(); + this.setHandles(); + +}; + +/** + * set this.handles and bind start events to 'em + */ +proto.setHandles = function() { + this.handles = this.options.handle ? + this.element.querySelectorAll( this.options.handle ) : [ this.element ]; + + this.bindHandles(); +}; + +/** + * emits events via EvEmitter 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 ].concat( args ); + this.emitEvent( type, emitArgs ); + var jQuery = window.jQuery; + // trigger jQuery event + if ( jQuery && this.$element ) { + if ( event ) { + // create jQuery event + var $event = jQuery.Event( event ); + $event.type = type; + this.$element.trigger( $event, args ); + } else { + // just trigger with type if no event available + this.$element.trigger( type, args ); + } + } +}; + +// -------------------------- position -------------------------- // + +// get x/y position from style +Draggabilly.prototype._getPosition = function() { + var style = getComputedStyle( this.element ); + var x = this._getPositionCoord( style.left, 'width' ); + var y = this._getPositionCoord( style.top, 'height' ); + // clean up 'auto' or other non-integer values + this.position.x = isNaN( x ) ? 0 : x; + this.position.y = isNaN( y ) ? 0 : y; + + this._addTransformPosition( style ); +}; + +Draggabilly.prototype._getPositionCoord = function( styleSide, measure ) { + if ( styleSide.indexOf('%') != -1 ) { + // convert percent into pixel for Safari, #75 + var parentSize = getSize( this.element.parentNode ); + return ( parseFloat( styleSide ) / 100 ) * parentSize[ measure ]; + } + + return parseInt( styleSide, 10 ); +}; + +// add transform: translate( x, y ) to position +proto._addTransformPosition = function( style ) { + var transform = style[ transformProperty ]; + // bail out if value is 'none' + if ( transform.indexOf('matrix') !== 0 ) { + return; + } + // split matrix(1, 0, 0, 1, x, y) + var matrixValues = transform.split(','); + // translate X value is in 12th or 4th position + var xIndex = transform.indexOf('matrix3d') === 0 ? 12 : 4; + var translateX = parseInt( matrixValues[ xIndex ], 10 ); + // translate Y value is in 13th or 5th position + var translateY = parseInt( matrixValues[ xIndex + 1 ], 10 ); + this.position.x += translateX; + this.position.y += translateY; +}; + +// -------------------------- events -------------------------- // + +/** + * pointer start + * @param {Event} event + * @param {Event or Touch} pointer + */ +proto.pointerDown = function( event, pointer ) { + this._dragPointerDown( event, pointer ); + // kludge to blur focused inputs in dragger + var focused = document.activeElement; + // do not blur body for IE10, metafizzy/flickity#117 + if ( focused && focused.blur && focused != document.body ) { + focused.blur(); + } + // bind move and end events + this._bindPostStartEvents( event ); + this.element.classList.add('is-pointer-down'); + this.dispatchEvent( 'pointerDown', event, [ pointer ] ); +}; + +/** + * drag move + * @param {Event} event + * @param {Event or Touch} pointer + */ +proto.pointerMove = function( event, pointer ) { + var moveVector = this._dragPointerMove( event, pointer ); + this.dispatchEvent( 'pointerMove', event, [ pointer, moveVector ] ); + this._dragMove( event, pointer, moveVector ); +}; + +/** + * drag start + * @param {Event} event + * @param {Event or Touch} pointer + */ +proto.dragStart = function( event, pointer ) { + if ( !this.isEnabled ) { + return; + } + this._getPosition(); + this.measureContainment(); + // position _when_ drag began + this.startPosition.x = this.position.x; + this.startPosition.y = this.position.y; + // reset left/top style + this.setLeftTop(); + + this.dragPoint.x = 0; + this.dragPoint.y = 0; + + this.element.classList.add('is-dragging'); + this.dispatchEvent( 'dragStart', event, [ pointer ] ); + // start animation + this.animate(); +}; + +proto.measureContainment = function() { + var containment = this.options.containment; + if ( !containment ) { + return; + } + + // use element if element + var container = isElement( containment ) ? containment : + // fallback to querySelector if string + typeof containment == 'string' ? document.querySelector( containment ) : + // otherwise just `true`, use the parent + this.element.parentNode; + + var elemSize = getSize( this.element ); + var containerSize = getSize( container ); + var elemRect = this.element.getBoundingClientRect(); + var containerRect = container.getBoundingClientRect(); + + var borderSizeX = containerSize.borderLeftWidth + containerSize.borderRightWidth; + var borderSizeY = containerSize.borderTopWidth + containerSize.borderBottomWidth; + + var position = this.relativeStartPosition = { + x: elemRect.left - ( containerRect.left + containerSize.borderLeftWidth ), + y: elemRect.top - ( containerRect.top + containerSize.borderTopWidth ) + }; + + this.containSize = { + width: ( containerSize.width - borderSizeX ) - position.x - elemSize.width, + height: ( containerSize.height - borderSizeY ) - position.y - elemSize.height + }; +}; + +// ----- move event ----- // + +/** + * drag move + * @param {Event} event + * @param {Event or Touch} pointer + */ +proto.dragMove = function( event, pointer, moveVector ) { + if ( !this.isEnabled ) { + return; + } + var dragX = moveVector.x; + var dragY = moveVector.y; + + var grid = this.options.grid; + var gridX = grid && grid[0]; + var gridY = grid && grid[1]; + + dragX = applyGrid( dragX, gridX ); + dragY = applyGrid( dragY, gridY ); + + dragX = this.containDrag( 'x', dragX, gridX ); + dragY = this.containDrag( 'y', dragY, gridY ); + + // constrain to axis + dragX = this.options.axis == 'y' ? 0 : dragX; + dragY = this.options.axis == 'x' ? 0 : dragY; + + this.position.x = this.startPosition.x + dragX; + this.position.y = this.startPosition.y + dragY; + // set dragPoint properties + this.dragPoint.x = dragX; + this.dragPoint.y = dragY; + + this.dispatchEvent( 'dragMove', event, [ pointer, moveVector ] ); +}; + +function applyGrid( value, grid, method ) { + method = method || 'round'; + return grid ? Math[ method ]( value / grid ) * grid : value; +} + +proto.containDrag = function( axis, drag, grid ) { + if ( !this.options.containment ) { + return drag; + } + var measure = axis == 'x' ? 'width' : 'height'; + + var rel = this.relativeStartPosition[ axis ]; + var min = applyGrid( -rel, grid, 'ceil' ); + var max = this.containSize[ measure ]; + max = applyGrid( max, grid, 'floor' ); + return Math.min( max, Math.max( min, drag ) ); +}; + +// ----- end event ----- // + +/** + * pointer up + * @param {Event} event + * @param {Event or Touch} pointer + */ +proto.pointerUp = function( event, pointer ) { + this.element.classList.remove('is-pointer-down'); + this.dispatchEvent( 'pointerUp', event, [ pointer ] ); + this._dragPointerUp( event, pointer ); +}; + +/** + * drag end + * @param {Event} event + * @param {Event or Touch} pointer + */ +proto.dragEnd = function( event, pointer ) { + if ( !this.isEnabled ) { + return; + } + // use top left position when complete + if ( transformProperty ) { + this.element.style[ transformProperty ] = ''; + this.setLeftTop(); + } + this.element.classList.remove('is-dragging'); + this.dispatchEvent( 'dragEnd', event, [ pointer ] ); +}; + +// -------------------------- animation -------------------------- // + +proto.animate = function() { + // only render and animate if dragging + if ( !this.isDragging ) { + return; + } + + this.positionDrag(); + + var _this = this; + requestAnimationFrame( function animateFrame() { + _this.animate(); + }); + +}; + +// left/top positioning +proto.setLeftTop = function() { + this.element.style.left = this.position.x + 'px'; + this.element.style.top = this.position.y + 'px'; +}; + +proto.positionDrag = function() { + this.element.style[ transformProperty ] = 'translate3d( ' + this.dragPoint.x + + 'px, ' + this.dragPoint.y + 'px, 0)'; +}; + +// ----- staticClick ----- // + +proto.staticClick = function( event, pointer ) { + this.dispatchEvent( 'staticClick', event, [ pointer ] ); +}; + +// ----- methods ----- // + +proto.enable = function() { + this.isEnabled = true; +}; + +proto.disable = function() { + this.isEnabled = false; + if ( this.isDragging ) { + this.dragEnd(); + } +}; + +proto.destroy = function() { + this.disable(); + // reset styles + this.element.style[ transformProperty ] = ''; + this.element.style.left = ''; + this.element.style.top = ''; + this.element.style.position = ''; + // unbind handles + this.unbindHandles(); + // remove jQuery data + if ( this.$element ) { + this.$element.removeData('draggabilly'); + } +}; + +// ----- jQuery bridget ----- // + +// required for jQuery bridget +proto._init = noop; + +if ( jQuery && jQuery.bridget ) { + jQuery.bridget( 'draggabilly', Draggabilly ); +} + +// ----- ----- // + +return Draggabilly; + +})); + diff --git a/www/external/origjs/gifwriter.js b/www/external/origjs/gifwriter.js new file mode 100644 index 00000000..67ae264e --- /dev/null +++ b/www/external/origjs/gifwriter.js @@ -0,0 +1,406 @@ +// (c) Dean McNamee <dean@gmail.com>, 2013. +// // +// // https://github.com/deanm/omggif +// // +// // Permission is hereby granted, free of charge, to any person obtaining a copy +// // of this software and associated documentation files (the "Software"), to +// // deal in the Software without restriction, including without limitation the +// // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// // sell copies of the Software, and to permit persons to whom the Software is +// // furnished to do so, subject to the following conditions: +// // +// // The above copyright notice and this permission notice shall be included in +// // all copies or substantial portions of the Software. +// // +// // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// // IN THE SOFTWARE. +// // +// // omggif is a JavaScript implementation of a GIF 89a encoder and decoder, +// // including animation and compression. It does not rely on any specific +// // underlying system, so should run in the browser, Node, or Plask. +'use strict'; + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + +function check_palette_and_num_colors(palette) { + var num_colors = palette.length; + + if (num_colors < 2 || num_colors > 256 || num_colors & num_colors - 1) { + throw new Error('Invalid code/color length, must be power of 2 and 2 .. 256.'); + } + + return num_colors; +} + +var GifWriter = function () { + function GifWriter(rs, width, height) { + var gopts = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {}; + + _classCallCheck(this, GifWriter); + + var loop = gopts.loop, + palette = gopts.palette; + + var p = 0; + var buf = []; + var global_palette = palette; + + if (width <= 0 || height <= 0 || width > 65535 || height > 65535) { + throw new Error('Width/Height invalid.'); + } + + // - Header. + buf[p++] = 0x47;buf[p++] = 0x49;buf[p++] = 0x46; // GIF + buf[p++] = 0x38;buf[p++] = 0x39;buf[p++] = 0x61; // 89a + + // Handling of Global Color Table (palette) and background index. + var gp_num_colors_pow2 = 0; + var background = 0; + if (global_palette) { + var gp_num_colors = check_palette_and_num_colors(global_palette); + while (gp_num_colors >>= 1) { + ++gp_num_colors_pow2; + }gp_num_colors = 1 << gp_num_colors_pow2; + gp_num_colors_pow2--; + if (gopts.background !== undefined) { + background = gopts.background; + if (background >= gp_num_colors) { + throw new Error('Background index out of range.'); + } + // The GIF spec states that a background index of 0 should be ignored, so + // this is probably a mistake and you really want to set it to another + // slot in the palette. But actually in the end most browsers, etc end + // up ignoring this almost completely (including for dispose background). + if (background === 0) throw new Error('Background index explicitly passed as 0.'); + } + } + + // - Logical Screen Descriptor. + // NOTE(deanm): w/h apparently ignored by implementations, but set anyway. + buf[p++] = width & 0xff; + buf[p++] = width >> 8 & 0xff; + buf[p++] = height & 0xff; + buf[p++] = height >> 8 & 0xff; + + // NOTE: Indicates 0-bpp original color resolution (unused?). + buf[p++] = (global_palette ? 0x80 : 0) | // Global Color Table Flag. + gp_num_colors_pow2; // NOTE: No sort flag (unused?). + buf[p++] = background; // Background Color Index. + buf[p++] = 0; // Pixel aspect ratio (unused?). + + // - Global Color Table + if (global_palette) { + for (var _iterator = global_palette, _isArray = Array.isArray(_iterator), _i = 0, _iterator = _isArray ? _iterator : _iterator[Symbol.iterator]();;) { + var _ref; + + if (_isArray) { + if (_i >= _iterator.length) break; + _ref = _iterator[_i++]; + } else { + _i = _iterator.next(); + if (_i.done) break; + _ref = _i.value; + } + + var rgb = _ref; + + buf[p++] = rgb >> 16 & 0xff; + buf[p++] = rgb >> 8 & 0xff; + buf[p++] = rgb & 0xff; + } + } + + if (Number.isInteger(loop)) { + // Netscape block for looping. + if (loop < 0 || loop > 65535) throw "Loop count invalid."; + // Extension code, label, and length. + buf[p++] = 0x21;buf[p++] = 0xff;buf[p++] = 0x0b; + // NETSCAPE2.0 + buf[p++] = 0x4e;buf[p++] = 0x45;buf[p++] = 0x54;buf[p++] = 0x53; + buf[p++] = 0x43;buf[p++] = 0x41;buf[p++] = 0x50;buf[p++] = 0x45; + buf[p++] = 0x32;buf[p++] = 0x2e;buf[p++] = 0x30; + // Sub-block + buf[p++] = 0x03;buf[p++] = 0x01; + buf[p++] = loop & 0xff;buf[p++] = loop >> 8 & 0xff; + buf[p++] = 0x00; // Terminator. + } + + var self = this; + var reader = rs.getReader(); + + return new ReadableStream({ + start: function start(controller) { + controller.enqueue(new Uint8Array(buf)); + }, + pull: function pull(controller) { + return reader.read().then(function (_ref2) { + var done = _ref2.done, + value = _ref2.value; + + if (done) { + controller.enqueue(new Uint8Array([0x3b])); + controller.close(); + return; + } + + self.addFrame.apply(self, [controller].concat(value)); + }); + } + }); + } + + GifWriter.prototype.addFrame = function addFrame(controller, x, y, w, h, indexed_pixels) { + var opts = arguments.length > 6 && arguments[6] !== undefined ? arguments[6] : {}; + + var p = 0; + var buf = []; + + // TODO(deanm): Bounds check x, y. Do they need to be within the virtual + // canvas width/height, I imagine? + if (x < 0 || y < 0 || x > 65535 || y > 65535) { + throw new Error('x/y invalid.'); + } + + if (w <= 0 || h <= 0 || w > 65535 || h > 65535) throw "Width/Height invalid."; + + if (indexed_pixels.length < w * h) throw "Not enough pixels for the frame size."; + + var using_local_palette = true; + var palette = opts.palette; + if (palette === undefined || palette === null) { + using_local_palette = false; + palette = global_palette; + } + + if (palette === undefined || palette === null) throw "Must supply either a local or global palette."; + + var num_colors = check_palette_and_num_colors(palette); + + // Compute the min_code_size (power of 2), destroying num_colors. + var min_code_size = 0; + while (num_colors >>= 1) { + ++min_code_size; + }num_colors = 1 << min_code_size; // Now we can easily get it back. + + var delay = opts.delay === undefined ? 0 : opts.delay; + + // From the spec: + // 0 - No disposal specified. The decoder is + // not required to take any action. + // 1 - Do not dispose. The graphic is to be left + // in place. + // 2 - Restore to background color. The area used by the + // graphic must be restored to the background color. + // 3 - Restore to previous. The decoder is required to + // restore the area overwritten by the graphic with + // what was there prior to rendering the graphic. + // 4-7 - To be defined. + // NOTE(deanm): Dispose background doesn't really work, apparently most + // browsers ignore the background palette index and clear to transparency. + var disposal = opts.disposal === undefined ? 0 : opts.disposal; + if (disposal < 0 || disposal > 3) // 4-7 is reserved. + throw "Disposal out of range."; + + var use_transparency = false; + var transparent_index = 0; + if (opts.transparent !== undefined && opts.transparent !== null) { + use_transparency = true; + transparent_index = opts.transparent; + if (transparent_index < 0 || transparent_index >= num_colors) throw "Transparent color index."; + } + + if (disposal !== 0 || use_transparency || delay !== 0) { + // - Graphics Control Extension + buf[p++] = 0x21;buf[p++] = 0xf9; // Extension / Label. + buf[p++] = 4; // Byte size. + + buf[p++] = disposal << 2 | (use_transparency === true ? 1 : 0); + buf[p++] = delay & 0xff;buf[p++] = delay >> 8 & 0xff; + buf[p++] = transparent_index; // Transparent color index. + buf[p++] = 0; // Block Terminator. + } + + // - Image Descriptor + buf[p++] = 0x2c; // Image Seperator. + buf[p++] = x & 0xff;buf[p++] = x >> 8 & 0xff; // Left. + buf[p++] = y & 0xff;buf[p++] = y >> 8 & 0xff; // Top. + buf[p++] = w & 0xff;buf[p++] = w >> 8 & 0xff; + buf[p++] = h & 0xff;buf[p++] = h >> 8 & 0xff; + // NOTE: No sort flag (unused?). + // TODO(deanm): Support interlace. + buf[p++] = using_local_palette === true ? 0x80 | min_code_size - 1 : 0; + + // - Local Color Table + if (using_local_palette === true) { + for (var i = 0, il = palette.length; i < il; ++i) { + var rgb = palette[i]; + buf[p++] = rgb >> 16 & 0xff; + buf[p++] = rgb >> 8 & 0xff; + buf[p++] = rgb & 0xff; + } + } + + GifWriterOutputLZWCodeStream(buf, p, min_code_size < 2 ? 2 : min_code_size, indexed_pixels); + + controller.enqueue(new Uint8Array(buf)); + }; + + return GifWriter; +}(); + +// Main compression routine, palette indexes -> LZW code stream. +// |index_stream| must have at least one entry. + + +function GifWriterOutputLZWCodeStream(buf, p, min_code_size, index_stream) { + buf[p++] = min_code_size; + var cur_subblock = p++; // Pointing at the length field. + + var clear_code = 1 << min_code_size; + var code_mask = clear_code - 1; + var eoi_code = clear_code + 1; + var next_code = eoi_code + 1; + + var cur_code_size = min_code_size + 1; // Number of bits per code. + var cur_shift = 0; + // We have at most 12-bit codes, so we should have to hold a max of 19 + // bits here (and then we would write out). + var cur = 0; + + function emit_bytes_to_buffer(bit_block_size) { + while (cur_shift >= bit_block_size) { + buf[p++] = cur & 0xff; + cur >>= 8;cur_shift -= 8; + if (p === cur_subblock + 256) { + // Finished a subblock. + buf[cur_subblock] = 255; + cur_subblock = p++; + } + } + } + + function emit_code(c) { + cur |= c << cur_shift; + cur_shift += cur_code_size; + emit_bytes_to_buffer(8); + } + + // I am not an expert on the topic, and I don't want to write a thesis. + // However, it is good to outline here the basic algorithm and the few data + // structures and optimizations here that make this implementation fast. + // The basic idea behind LZW is to build a table of previously seen runs + // addressed by a short id (herein called output code). All data is + // referenced by a code, which represents one or more values from the + // original input stream. All input bytes can be referenced as the same + // value as an output code. So if you didn't want any compression, you + // could more or less just output the original bytes as codes (there are + // some details to this, but it is the idea). In order to achieve + // compression, values greater then the input range (codes can be up to + // 12-bit while input only 8-bit) represent a sequence of previously seen + // inputs. The decompressor is able to build the same mapping while + // decoding, so there is always a shared common knowledge between the + // encoding and decoder, which is also important for "timing" aspects like + // how to handle variable bit width code encoding. + // + // One obvious but very important consequence of the table system is there + // is always a unique id (at most 12-bits) to map the runs. 'A' might be + // 4, then 'AA' might be 10, 'AAA' 11, 'AAAA' 12, etc. This relationship + // can be used for an effecient lookup strategy for the code mapping. We + // need to know if a run has been seen before, and be able to map that run + // to the output code. Since we start with known unique ids (input bytes), + // and then from those build more unique ids (table entries), we can + // continue this chain (almost like a linked list) to always have small + // integer values that represent the current byte chains in the encoder. + // This means instead of tracking the input bytes (AAAABCD) to know our + // current state, we can track the table entry for AAAABC (it is guaranteed + // to exist by the nature of the algorithm) and the next character D. + // Therefor the tuple of (table_entry, byte) is guaranteed to also be + // unique. This allows us to create a simple lookup key for mapping input + // sequences to codes (table indices) without having to store or search + // any of the code sequences. So if 'AAAA' has a table entry of 12, the + // tuple of ('AAAA', K) for any input byte K will be unique, and can be our + // key. This leads to a integer value at most 20-bits, which can always + // fit in an SMI value and be used as a fast sparse array / object key. + + // Output code for the current contents of the index buffer. + var ib_code = index_stream[0] & code_mask; // Load first input index. + var code_table = {}; // Key'd on our 20-bit "tuple". + + emit_code(clear_code); // Spec says first code should be a clear code. + + // First index already loaded, process the rest of the stream. + for (var i = 1, il = index_stream.length; i < il; ++i) { + var k = index_stream[i] & code_mask; + var cur_key = ib_code << 8 | k; // (prev, k) unique tuple. + var cur_code = code_table[cur_key]; // buffer + k. + + // Check if we have to create a new code table entry. + if (cur_code === undefined) { + // We don't have buffer + k. + // Emit index buffer (without k). + // This is an inline version of emit_code, because this is the core + // writing routine of the compressor (and V8 cannot inline emit_code + // because it is a closure here in a different context). Additionally + // we can call emit_byte_to_buffer less often, because we can have + // 30-bits (from our 31-bit signed SMI), and we know our codes will only + // be 12-bits, so can safely have 18-bits there without overflow. + // emit_code(ib_code); + cur |= ib_code << cur_shift; + cur_shift += cur_code_size; + while (cur_shift >= 8) { + buf[p++] = cur & 0xff; + cur >>= 8;cur_shift -= 8; + if (p === cur_subblock + 256) { + // Finished a subblock. + buf[cur_subblock] = 255; + cur_subblock = p++; + } + } + + if (next_code === 4096) { + // Table full, need a clear. + emit_code(clear_code); + next_code = eoi_code + 1; + cur_code_size = min_code_size + 1; + code_table = {}; + } else { + // Table not full, insert a new entry. + // Increase our variable bit code sizes if necessary. This is a bit + // tricky as it is based on "timing" between the encoding and + // decoder. From the encoders perspective this should happen after + // we've already emitted the index buffer and are about to create the + // first table entry that would overflow our current code bit size. + if (next_code >= 1 << cur_code_size) ++cur_code_size; + code_table[cur_key] = next_code++; // Insert into code table. + } + + ib_code = k; // Index buffer to single input k. + } else { + ib_code = cur_code; // Index buffer to sequence in code table. + } + } + + emit_code(ib_code); // There will still be something in the index buffer. + emit_code(eoi_code); // End Of Information. + + // Flush / finalize the sub-blocks stream to the buffer. + emit_bytes_to_buffer(1); + + // Finish the sub-blocks, writing out any unfinished lengths and + // terminating with a sub-block of length 0. If we have already started + // but not yet used a sub-block it can just become the terminator. + if (cur_subblock + 1 === p) { + // Started but unused. + buf[cur_subblock] = 0; + } else { + // Started and used, write length and additional terminator block. + buf[cur_subblock] = p - cur_subblock - 1; + buf[p++] = 0; + } + return p; +} diff --git a/www/external/origjs/imagesloaded.pkgd.js b/www/external/origjs/imagesloaded.pkgd.js new file mode 100644 index 00000000..ef23971b --- /dev/null +++ b/www/external/origjs/imagesloaded.pkgd.js @@ -0,0 +1,487 @@ +/*! + * imagesLoaded PACKAGED v4.1.0 + * JavaScript is all like "You images are done yet or what?" + * MIT License + */ + +/** + * EvEmitter v1.0.1 + * Lil' event emitter + * MIT License + */ + +/* jshint unused: true, undef: true, strict: true */ + +( function( global, factory ) { + // universal module definition + /* jshint strict: false */ /* globals define, module */ + 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(); + } + +}( 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 array + 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; + +})); + +/*! + * imagesLoaded v4.1.0 + * JavaScript is all like "You images are done yet or what?" + * MIT License + */ + +( function( window, factory ) { 'use strict'; + // universal module definition + + /*global define: false, module: false, require: false */ + + if ( typeof define == 'function' && define.amd ) { + // AMD + define( [ + 'ev-emitter/ev-emitter' + ], function( EvEmitter ) { + return factory( window, EvEmitter ); + }); + } else if ( typeof module == 'object' && module.exports ) { + // CommonJS + module.exports = factory( + window, + require('ev-emitter') + ); + } else { + // browser global + window.imagesLoaded = factory( + window, + window.EvEmitter + ); + } + +})( window, + +// -------------------------- factory -------------------------- // + +function factory( window, EvEmitter ) { + + + +var $ = window.jQuery; +var console = window.console; + +// -------------------------- helpers -------------------------- // + +// extend objects +function extend( a, b ) { + for ( var prop in b ) { + a[ prop ] = b[ prop ]; + } + return a; +} + +// turn element or nodeList into an array +function makeArray( obj ) { + var ary = []; + if ( Array.isArray( obj ) ) { + // use object if already an array + ary = obj; + } else if ( 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; +} + +// -------------------------- imagesLoaded -------------------------- // + +/** + * @param {Array, Element, NodeList, String} elem + * @param {Object or Function} options - if function, use as callback + * @param {Function} onAlways - callback function + */ +function ImagesLoaded( elem, options, onAlways ) { + // coerce ImagesLoaded() without new, to be new ImagesLoaded() + if ( !( this instanceof ImagesLoaded ) ) { + return new ImagesLoaded( elem, options, onAlways ); + } + // use elem as selector string + if ( typeof elem == 'string' ) { + elem = document.querySelectorAll( elem ); + } + + this.elements = makeArray( elem ); + this.options = extend( {}, this.options ); + + if ( typeof options == 'function' ) { + onAlways = options; + } else { + extend( this.options, options ); + } + + if ( onAlways ) { + this.on( 'always', onAlways ); + } + + this.getImages(); + + if ( $ ) { + // add jQuery Deferred object + this.jqDeferred = new $.Deferred(); + } + + // HACK check async to allow time to bind listeners + setTimeout( function() { + this.check(); + }.bind( this )); +} + +ImagesLoaded.prototype = Object.create( EvEmitter.prototype ); + +ImagesLoaded.prototype.options = {}; + +ImagesLoaded.prototype.getImages = function() { + this.images = []; + + // filter & find items if we have an item selector + this.elements.forEach( this.addElementImages, this ); +}; + +/** + * @param {Node} element + */ +ImagesLoaded.prototype.addElementImages = function( elem ) { + // filter siblings + if ( elem.nodeName == 'IMG' ) { + this.addImage( elem ); + } + // get background image on element + if ( this.options.background === true ) { + this.addElementBackgroundImages( elem ); + } + + // find children + // no non-element nodes, #143 + var nodeType = elem.nodeType; + if ( !nodeType || !elementNodeTypes[ nodeType ] ) { + return; + } + var childImgs = elem.querySelectorAll('img'); + // concat childElems to filterFound array + for ( var i=0; i < childImgs.length; i++ ) { + var img = childImgs[i]; + this.addImage( img ); + } + + // get child background images + if ( typeof this.options.background == 'string' ) { + var children = elem.querySelectorAll( this.options.background ); + for ( i=0; i < children.length; i++ ) { + var child = children[i]; + this.addElementBackgroundImages( child ); + } + } +}; + +var elementNodeTypes = { + 1: true, + 9: true, + 11: true +}; + +ImagesLoaded.prototype.addElementBackgroundImages = function( elem ) { + var style = getComputedStyle( elem ); + if ( !style ) { + // Firefox returns null if in a hidden iframe https://bugzil.la/548397 + return; + } + // get url inside url("...") + var reURL = /url\((['"])?(.*?)\1\)/gi; + var matches = reURL.exec( style.backgroundImage ); + while ( matches !== null ) { + var url = matches && matches[2]; + if ( url ) { + this.addBackground( url, elem ); + } + matches = reURL.exec( style.backgroundImage ); + } +}; + +/** + * @param {Image} img + */ +ImagesLoaded.prototype.addImage = function( img ) { + var loadingImage = new LoadingImage( img ); + this.images.push( loadingImage ); +}; + +ImagesLoaded.prototype.addBackground = function( url, elem ) { + var background = new Background( url, elem ); + this.images.push( background ); +}; + +ImagesLoaded.prototype.check = function() { + var _this = this; + this.progressedCount = 0; + this.hasAnyBroken = false; + // complete if no images + if ( !this.images.length ) { + this.complete(); + return; + } + + function onProgress( image, elem, message ) { + // HACK - Chrome triggers event before object properties have changed. #83 + setTimeout( function() { + _this.progress( image, elem, message ); + }); + } + + this.images.forEach( function( loadingImage ) { + loadingImage.once( 'progress', onProgress ); + loadingImage.check(); + }); +}; + +ImagesLoaded.prototype.progress = function( image, elem, message ) { + this.progressedCount++; + this.hasAnyBroken = this.hasAnyBroken || !image.isLoaded; + // progress event + this.emitEvent( 'progress', [ this, image, elem ] ); + if ( this.jqDeferred && this.jqDeferred.notify ) { + this.jqDeferred.notify( this, image ); + } + // check if completed + if ( this.progressedCount == this.images.length ) { + this.complete(); + } + + if ( this.options.debug && console ) { + console.log( 'progress: ' + message, image, elem ); + } +}; + +ImagesLoaded.prototype.complete = function() { + var eventName = this.hasAnyBroken ? 'fail' : 'done'; + this.isComplete = true; + this.emitEvent( eventName, [ this ] ); + this.emitEvent( 'always', [ this ] ); + if ( this.jqDeferred ) { + var jqMethod = this.hasAnyBroken ? 'reject' : 'resolve'; + this.jqDeferred[ jqMethod ]( this ); + } +}; + +// -------------------------- -------------------------- // + +function LoadingImage( img ) { + this.img = img; +} + +LoadingImage.prototype = Object.create( EvEmitter.prototype ); + +LoadingImage.prototype.check = function() { + // If complete is true and browser supports natural sizes, + // try to check for image status manually. + var isComplete = this.getIsImageComplete(); + if ( isComplete ) { + // report based on naturalWidth + this.confirm( this.img.naturalWidth !== 0, 'naturalWidth' ); + return; + } + + // If none of the checks above matched, simulate loading on detached element. + this.proxyImage = new Image(); + this.proxyImage.addEventListener( 'load', this ); + this.proxyImage.addEventListener( 'error', this ); + // bind to image as well for Firefox. #191 + this.img.addEventListener( 'load', this ); + this.img.addEventListener( 'error', this ); + this.proxyImage.src = this.img.src; +}; + +LoadingImage.prototype.getIsImageComplete = function() { + return this.img.complete && this.img.naturalWidth !== undefined; +}; + +LoadingImage.prototype.confirm = function( isLoaded, message ) { + this.isLoaded = isLoaded; + this.emitEvent( 'progress', [ this, this.img, message ] ); +}; + +// ----- events ----- // + +// trigger specified handler for event type +LoadingImage.prototype.handleEvent = function( event ) { + var method = 'on' + event.type; + if ( this[ method ] ) { + this[ method ]( event ); + } +}; + +LoadingImage.prototype.onload = function() { + this.confirm( true, 'onload' ); + this.unbindEvents(); +}; + +LoadingImage.prototype.onerror = function() { + this.confirm( false, 'onerror' ); + this.unbindEvents(); +}; + +LoadingImage.prototype.unbindEvents = function() { + this.proxyImage.removeEventListener( 'load', this ); + this.proxyImage.removeEventListener( 'error', this ); + this.img.removeEventListener( 'load', this ); + this.img.removeEventListener( 'error', this ); +}; + +// -------------------------- Background -------------------------- // + +function Background( url, element ) { + this.url = url; + this.element = element; + this.img = new Image(); +} + +// inherit LoadingImage prototype +Background.prototype = Object.create( LoadingImage.prototype ); + +Background.prototype.check = function() { + this.img.addEventListener( 'load', this ); + this.img.addEventListener( 'error', this ); + this.img.src = this.url; + // check if image is already complete + var isComplete = this.getIsImageComplete(); + if ( isComplete ) { + this.confirm( this.img.naturalWidth !== 0, 'naturalWidth' ); + this.unbindEvents(); + } +}; + +Background.prototype.unbindEvents = function() { + this.img.removeEventListener( 'load', this ); + this.img.removeEventListener( 'error', this ); +}; + +Background.prototype.confirm = function( isLoaded, message ) { + this.isLoaded = isLoaded; + this.emitEvent( 'progress', [ this, this.element, message ] ); +}; + +// -------------------------- jQuery -------------------------- // + +ImagesLoaded.makeJQueryPlugin = function( jQuery ) { + jQuery = jQuery || window.jQuery; + if ( !jQuery ) { + return; + } + // set local variable + $ = jQuery; + // $().imagesLoaded() + $.fn.imagesLoaded = function( options, callback ) { + var instance = new ImagesLoaded( this, options, callback ); + return instance.jqDeferred.promise( $(this) ); + }; +}; +// try making plugin +ImagesLoaded.makeJQueryPlugin(); + +// -------------------------- -------------------------- // + +return ImagesLoaded; + +}); + diff --git a/www/external/origjs/ion-pullup.js b/www/external/origjs/ion-pullup.js new file mode 100755 index 00000000..ae9109a8 --- /dev/null +++ b/www/external/origjs/ion-pullup.js @@ -0,0 +1,261 @@ +angular.module('ionic-pullup', []) + .constant('ionPullUpFooterState', { + COLLAPSED: 'COLLAPSED', + MINIMIZED: 'MINIMIZED', + EXPANDED: 'EXPANDED' + }) + .constant('ionPullUpFooterBehavior', { + HIDE: 'HIDE', + EXPAND: 'EXPAND' + }) + .directive('ionPullUpFooter', ['$timeout', '$rootScope', '$window', '$ionicPlatform', function($timeout, $rootScope, $window, $ionicPlatform) { + return { + restrict: 'AE', + scope: { + onExpand: '&', + onCollapse: '&', + onMinimize: '&' + }, + controller: ['$scope', '$element', '$attrs', 'ionPullUpFooterState', 'ionPullUpFooterBehavior', function($scope, $element, $attrs, FooterState, FooterBehavior) { + var + tabs, hasBottomTabs, header, tabsHeight, headerHeight, handleHeight = 0, + footer = { + height: 0, + posY: 0, + lastPosY: 0, + state: FooterState.COLLAPSED, + defaultHeight : $element[0].offsetHeight, + maxHeight: parseInt($attrs.maxHeight, 10) || 0, + initialState: $attrs.initialState ? $attrs.initialState.toUpperCase() : FooterState.COLLAPSED, + defaultBehavior: $attrs.defaultBehavior ? $attrs.defaultBehavior.toUpperCase() : FooterBehavior.EXPAND + }; + + function init() { + computeDefaultHeights(); + + $element.css({'transition': '300ms ease-in-out', 'padding': 0}); + if (tabs && hasBottomTabs) { + $element.css('bottom', tabs.offsetHeight + 'px'); + } + } + + function computeDefaultHeights() { + tabs = document.querySelector('.tabs'); + hasBottomTabs = document.querySelector('.tabs-bottom'); + header = document.querySelector('.bar-header'); + tabsHeight = tabs ? tabs.offsetHeight : 0; + headerHeight = header ? header.offsetHeight : 0; + + /*if ($rootScope.platformOS == 'ios') + { + tabsHeight +=40; + headerHeight +=40; + }*/ + } + + function computeHeights() { + footer.height = footer.maxHeight > 0 ? footer.maxHeight : $window.innerHeight - headerHeight - handleHeight - tabsHeight; + if ($rootScope.platformOS == 'ios') footer.height -=60; + if ($rootScope.platformOS == 'android') footer.height -=40; + $element.css({'height': footer.height + 'px'}); + + if (footer.initialState == FooterState.MINIMIZED) { + + minimize(); + } else { + collapse(); + } + } + + function updateUI() { + $timeout(function() { + computeHeights(); + }, 300); + $element.css({'transition': 'none', 'padding': 0}); + } + + function recomputeAllHeights() { + computeDefaultHeights(); + footer.height = footer.maxHeight > 0 ? footer.maxHeight : $window.innerHeight - headerHeight - handleHeight - tabsHeight; + if ($rootScope.platformOS == 'ios') footer.height -=60; + if ($rootScope.platformOS == 'android') footer.height -=40; + } + + function expand() { + // recompute height right here to make sure we have the latest + recomputeAllHeights(); + footer.lastPosY = 0; + // adjust CSS values with new heights in case they changed + $element.css({'height':footer.height + 'px', '-webkit-transform': 'translate3d(0, 0, 0)', 'transform': 'translate3d(0, 0, 0)'}); + $element.css({'transition': '300ms ease-in-out', 'padding': 0}); + $scope.onExpand(); + footer.state = FooterState.EXPANDED; + } + + function collapse() { + footer.lastPosY = (tabs && hasBottomTabs) ? footer.height - tabsHeight : footer.height - footer.defaultHeight; + $element.css({'-webkit-transform': 'translate3d(0, ' + footer.lastPosY + 'px, 0)', 'transform': 'translate3d(0, ' + footer.lastPosY + 'px, 0)'}); + $scope.onCollapse(); + footer.state = FooterState.COLLAPSED + } + + function minimize() { + footer.lastPosY = footer.height; + $element.css({'-webkit-transform': 'translate3d(0, ' + footer.lastPosY + 'px, 0)', 'transform': 'translate3d(0, ' + footer.lastPosY + 'px, 0)'}); + $scope.onMinimize(); + footer.state = FooterState.MINIMIZED; + } + + + this.setHandleHeight = function(height) { + handleHeight = height; + computeHeights(); + }; + + this.getHeight = function() { + return $element[0].offsetHeight; + }; + + this.getBackground = function() { + return $window.getComputedStyle($element[0]).background; + }; + + this.onTap = function(e) { + e.gesture.srcEvent.preventDefault(); + e.gesture.preventDefault(); + + if (footer.state == FooterState.COLLAPSED) { + if (footer.defaultBehavior == FooterBehavior.HIDE) { + minimize(); + } else { + expand(); + } + } else { + if (footer.state == FooterState.MINIMIZED) { + if (footer.defaultBehavior == FooterBehavior.HIDE) + collapse(); + else + expand(); + } else { + // footer is expanded + footer.initialState == FooterState.MINIMIZED ? minimize() : collapse(); + } + } + + $rootScope.$broadcast('ionPullUp:tap', footer.state); + }; + + this.onDrag = function(e) { + e.gesture.srcEvent.preventDefault(); + e.gesture.preventDefault(); + + switch (e.type) { + case 'dragstart': + $element.css('transition', 'none'); + break; + case 'drag': + footer.posY = Math.round(e.gesture.deltaY) + footer.lastPosY; + if (footer.posY < 0 || footer.posY > footer.height) return; + $element.css({'-webkit-transform': 'translate3d(0, ' + footer.posY + 'px, 0)', 'transform': 'translate3d(0, ' + footer.posY + 'px, 0)'}); + break; + case 'dragend': + $element.css({'transition': '300ms ease-in-out'}); + footer.lastPosY = footer.posY; + break; + } + }; + + init(); + + $ionicPlatform.ready(function() { + $window.addEventListener('orientationchange', updateUI); + $ionicPlatform.on("resume", updateUI); + }); + + }], + compile: function(element, attrs) { + attrs.defaultHeight && element.css('height', parseInt(attrs.defaultHeight, 10) + 'px'); + element.addClass('bar bar-footer'); + } + } + }]) + .directive('ionPullUpContent', [function() { + return { + restrict: 'AE', + require: '^ionPullUpFooter', + link: function (scope, element, attrs, controller) { + var + footerHeight = controller.getHeight(); + element.css({'display': 'block', 'margin-top': footerHeight + 'px', width: '100%'}); + // add scrolling if needed + if (attrs.scroll && attrs.scroll.toUpperCase() == 'TRUE') { + element.css({'overflow-y': 'scroll', 'overflow-x': 'hidden'}); + } + } + } + }]) + .directive('ionPullUpBar', [function() { + return { + restrict: 'AE', + require: '^ionPullUpFooter', + link: function (scope, element, attrs, controller) { + var + footerHeight = controller.getHeight(); + element.css({'display': 'flex', 'height': footerHeight + 'px', position: 'absolute', right: '0', left: '0'}); + + } + } + }]) + .directive('ionPullUpTrigger', ['$ionicGesture', function($ionicGesture) { + return { + restrict: 'AE', + require: '^ionPullUpFooter', + link: function (scope, element, attrs, controller) { + // add gesture + $ionicGesture.on('tap', controller.onTap, element); + $ionicGesture.on('drag dragstart dragend', controller.onDrag, element); + } + } + }]) + .directive('ionPullUpHandle', ['$ionicGesture', '$ionicPlatform', '$timeout', '$window', function($ionicGesture, $ionicPlatform, $timeout, $window) { + return { + restrict: 'AE', + require: '^ionPullUpFooter', + link: function (scope, element, attrs, controller) { + var height = parseInt(attrs.height,10) || 25, width = parseInt(attrs.width, 10) || 100, + background = controller.getBackground(), + toggleClasses = attrs.toggle; + + controller.setHandleHeight(height); + + element.css({ + display: 'block', + background: background, + position: 'absolute', + top: 1-height + 'px', + left: (($window.innerWidth - width) / 2) + 'px', + height: height + 'px', + width: width + 'px', + 'text-align': 'center' + }); + + // add gesture + $ionicGesture.on('tap', controller.onTap, element); + $ionicGesture.on('drag dragstart dragend', controller.onDrag, element); + + scope.$on('ionPullUp:tap', function() { + element.find('i').toggleClass(toggleClasses); + }); + + function updateUI() { + $timeout ( function() { + element.css('left', (($window.innerWidth - width) / 2) + 'px');},300); + } + + $ionicPlatform.ready(function() { + $window.addEventListener('orientationchange', updateUI); + $ionicPlatform.on("resume", updateUI); + }); + } + } + }]); diff --git a/www/external/origjs/ionRadio.js b/www/external/origjs/ionRadio.js new file mode 100644 index 00000000..cb240b76 --- /dev/null +++ b/www/external/origjs/ionRadio.js @@ -0,0 +1,55 @@ +/** + * ionRadioFix - fixes a bug in iOS 9 UIWebView that breaks the tilde selector in CSS. To + * use this fix, include it after your Ionic bundle JS. + * + * Note: due to Angular directive override limitations, you'll need to change any reference + * to <ion-radio> to <ion-radio-fix> to apply this patched radio button. + * + * Also, make sure to add the new CSS from the second part of this gist. + */ +angular.module('ionic').directive('ionRadioFix', function() { + return { + restrict: 'E', + replace: true, + require: '?ngModel', + transclude: true, + template: + '<label class="item item-radio">' + + '<input type="radio" name="radio-group">' + + '<div class="radio-content">' + + '<div class="item-content disable-pointer-events" ng-transclude></div>' + + '<i class="radio-icon disable-pointer-events icon ion-checkmark"></i>' + + '</div>' + + '</label>', + + compile: function(element, attr) { + if (attr.icon) { + var iconElm = element.find('i'); + iconElm.removeClass('ion-checkmark').addClass(attr.icon); + } + + var input = element.find('input'); + angular.forEach({ + 'name': attr.name, + 'value': attr.value, + 'disabled': attr.disabled, + 'ng-value': attr.ngValue, + 'ng-model': attr.ngModel, + 'ng-disabled': attr.ngDisabled, + 'ng-change': attr.ngChange, + 'ng-required': attr.ngRequired, + 'required': attr.required + }, function(value, name) { + if (angular.isDefined(value)) { + input.attr(name, value); + } + }); + + return function(scope, element, attr) { + scope.getValue = function() { + return scope.ngValue || attr.value; + }; + }; + } + }; +});
\ No newline at end of file diff --git a/www/external/origjs/ionic.content.banner.js b/www/external/origjs/ionic.content.banner.js new file mode 100644 index 00000000..900b96a6 --- /dev/null +++ b/www/external/origjs/ionic.content.banner.js @@ -0,0 +1,190 @@ +angular.module('jett.ionic.content.banner', ['ionic']); +/* global angular */ +(function (angular) { + 'use strict'; + + angular.module('jett.ionic.content.banner') + .directive('ionContentBanner', [ + '$interval', + function ($interval) { + return { + restrict: 'E', + scope: true, + link: function ($scope, $element) { + var stopInterval; + + $scope.currentIndex = 0; + + if ($scope.text.length > 1) { + stopInterval = $interval(function () { + $scope.currentIndex = ($scope.currentIndex < $scope.text.length - 1) ? $scope.currentIndex + 1 : 0; + }, $scope.interval); + } + + $scope.$on('$destroy', function() { + $element.remove(); + if (stopInterval) { + $interval.cancel(stopInterval); + } + }); + }, + template: + '<div class="content-banner-text-wrapper">' + + '<div ng-repeat="item in text track by $index" ng-class="{active: $index === currentIndex}" class="content-banner-text" ng-bind="item"></div>' + + '</div>' + + '<button class="content-banner-close button button-icon icon {{::icon}}" ng-click="close()"></button>' + }; + }]); + +})(angular); + +/* global angular,ionic */ +/** + * @ngdoc service + * @name $ionicContentBanner + * @module ionic + * @description The Content Banner is an animated banner that will show specific information to a user. + */ +(function (angular, ionic) { + 'use strict'; + + angular.module('jett.ionic.content.banner') + .factory('$ionicContentBanner', [ + '$document', + '$rootScope', + '$compile', + '$timeout', + '$ionicPlatform', + function ($document, $rootScope, $compile, $timeout, $ionicPlatform) { + + function isActiveView (node) { + // walk up the child-parent node chain until we get to the root or the BODY + while (node !== null && node.nodeName !== 'BODY') { + var navView = node.getAttribute("nav-view"); + + // as soon as we encounter a cached (parent) view then we know the view can't be active + if (navView !== null && navView === 'cached') { + return false; + } + node = node.parentNode; + } + // no cached parent seen, the view must be really active + return true; + } + + function getActiveView (body) { + // check if there is an active modal + var modal = body.querySelector('ion-modal-view[class*="ng-enter-active"]'); + if (modal != null) { + // check if modal is not leaving + if (modal.getAttribute('class').indexOf('ng-leave') == -1) { + return modal; + } + } + // get the candidate active views + var views = body.querySelectorAll('ion-view[nav-view="active"]'); + + // only one candidate, so we just take it + if (views.length === 1) { + return views[0]; + } + + // convert the NodeList to an array, filter it using 'isActiveView' and return the first element + return Array.prototype.slice.call(views).filter(function (view) { + return isActiveView(view); + })[0]; + } + + /** + * @ngdoc method + * @name $ionicContentBanner#show + * @description + * Load and show a new content banner. + */ + function contentBanner (opts) { + var scope = $rootScope.$new(true); + + angular.extend(scope, { + icon: 'ion-ios-close-empty', + transition: 'vertical', + interval: 7000, + type: 'info', + $deregisterBackButton: angular.noop, + closeOnStateChange: true, + autoClose: null + }, opts); + + // Compile the template + var classes = 'content-banner ' + scope.type + ' content-banner-transition-' + scope.transition; + var element = scope.element = $compile('<ion-content-banner class="' + classes + '"></ion-content-banner>')(scope); + var body = $document[0].body; + + var stateChangeListenDone = scope.closeOnStateChange ? + $rootScope.$on('$stateChangeSuccess', function() { scope.close(); }) : + angular.noop; + + scope.$deregisterBackButton = $ionicPlatform.registerBackButtonAction( + function() { + $timeout(scope.close); + }, 300 + ); + + scope.close = function() { + if (scope.removed) { + return; + } + scope.removed = true; + + ionic.requestAnimationFrame(function () { + element.removeClass('content-banner-in'); + + $timeout(function () { + scope.$destroy(); + element.remove(); + body = stateChangeListenDone = null; + }, 400); + }); + + scope.$deregisterBackButton(); + stateChangeListenDone(); + }; + + scope.show = function() { + if (scope.removed) { + return; + } + // PP: get rid of querySelector + if (getActiveView(body) !== undefined) + getActiveView(body).querySelector('.scroll-content').appendChild(element[0]); + + ionic.requestAnimationFrame(function () { + $timeout(function () { + element.addClass('content-banner-in'); + //automatically close if autoClose is configured + if (scope.autoClose) { + $timeout(function () { + scope.close(); + }, scope.autoClose, false); + } + }, 20, false); + }); + }; + + //set small timeout to let ionic set the active/cached view + $timeout(function () { + scope.show(); + }, 10, false); + + // Expose the scope on $ionContentBanner's return value for the sake of testing it. + scope.close.$scope = scope; + + return scope.close; + } + + return { + show: contentBanner + }; + }]); + + +})(angular, ionic); diff --git a/www/external/origjs/ionic.scroll.sista.js b/www/external/origjs/ionic.scroll.sista.js new file mode 100644 index 00000000..f8dd8143 --- /dev/null +++ b/www/external/origjs/ionic.scroll.sista.js @@ -0,0 +1,321 @@ +/* global angular,ionic */ +(function (angular, ionic) { + 'use strict'; + + angular.module('jett.ionic.scroll.sista', ['ionic']) + .directive('scrollSista', ['$document', '$timeout', '$rootScope', '$ionicScrollDelegate', '$ionicPlatform',function($document, $timeout, $rootScope, $ionicScrollDelegate, $ionicPlatform) { + var TRANSITION_DELAY = 400; + var defaultDelay = TRANSITION_DELAY * 2; + var defaultDuration = TRANSITION_DELAY + 'ms'; + var scaleHeaderElements = ionic.Platform.isAndroid() ? false : true; + + function getParentWithAttr (e, attrName, attrValue, depth) { + var attr; + + depth = depth || 10; + while (e.parentNode && depth--) { + attr = e.parentNode.getAttribute(attrName); + if (attr && attr === attrValue) { + return e.parentNode; + } + e = e.parentNode; + } + return null; + } + + return { + restrict: 'A', + link: function($scope, $element, $attr) { + var isNavBarTransitioning = true; + var body = $document[0].body; + var scrollDelegate = $attr.delegateHandle ? $ionicScrollDelegate.$getByHandle($attr.delegateHandle) : $ionicScrollDelegate; + var scrollView = scrollDelegate.getScrollView(); + var isIos = false; + + //coordinates + var y, prevY, prevScrollTop; + //headers + var cachedHeader, activeHeader, headerHeight, contentTop; + //subheader + var subHeader, subHeaderHeight; + //tabs + var tabs, tabsHeight, hasTabsTop = false, hasTabsBottom = false; + + //y position that will indicate where specific elements should start and end their transition. + var headerStart = 0; + var tabsStart = 0; + var subheaderStart = 0; + var defaultEnd, headerEnd, tabsEnd, subheaderEnd; + + /** + * translates an element along the y axis by the supplied value. if duration is passed in, + * a transition duration is set + * @param element + * @param y + * @param duration + */ + function translateY (element, y, duration) { + if (duration && !element.style[ionic.CSS.TRANSITION_DURATION]) { + element.style[ionic.CSS.TRANSITION_DURATION] = duration; + $timeout(function () { + element.style[ionic.CSS.TRANSITION_DURATION] = ''; + }, defaultDelay, false); + } + element.style[ionic.CSS.TRANSFORM] = 'translate3d(0, ' + (-y) + 'px, 0)'; + } + + /** + * Initializes y and scroll variables + */ + function initCoordinates () { + y = 0; + prevY = 0; + prevScrollTop = 0; + } + + /** + * Initializes headers, tabs, and subheaders, and determines how they will transition on scroll + */ + function init () { + var activeView; + + cachedHeader = body.querySelector('[nav-bar="cached"] .bar-header'); + activeHeader = body.querySelector('[nav-bar="active"] .bar-header'); + + if (!activeHeader) { + return; + } + + // credit - adapted 20px PR from https://github.com/driftyco/ionic-ion-header-shrink/pull/10 + + headerHeight = activeHeader.offsetHeight; + $ionicPlatform.ready(function() { + if($ionicPlatform.is('ios')) { + isIos = true; + headerHeight -= 20; // account 20px for the ios status bar + } + }); + contentTop = headerHeight; + + //since some people can have nested tabs, get the last tabs + tabs = body.querySelectorAll('.tabs'); + tabs = tabs[tabs.length - 1]; + if (tabs) { + tabsHeight = tabs.offsetHeight; + if (tabs.parentNode.classList.contains('tabs-top')) { + hasTabsTop = true; + contentTop += tabsHeight; + } else if (tabs.parentNode.classList.contains('tabs-bottom')) { + hasTabsBottom = true; + } + } + + //subheader + //since subheader is going to be nested in the active view, get the closest active view from $element and + activeView = getParentWithAttr($element[0], 'nav-view', 'active'); + subHeader = activeView && activeView.querySelector('.bar-subheader'); + if (subHeader) { + subHeaderHeight = subHeader.offsetHeight; + contentTop += subHeaderHeight; + } + + //set default end for header/tabs elements to scroll out of the scroll view and set elements end to default + defaultEnd = contentTop * 2; + headerEnd = tabsEnd = subheaderEnd = defaultEnd; + + //if tabs or subheader aren't available, set height to 0 + tabsHeight = tabsHeight || 0; + subHeaderHeight = subHeaderHeight || 0; + + switch($attr.scrollSista) { + case 'header': + subheaderEnd = headerHeight; + tabsEnd = hasTabsTop ? headerHeight : 0; + break; + case 'header-tabs': + headerStart = hasTabsTop ? tabsHeight : 0; + subheaderEnd = hasTabsTop ? headerHeight + tabsHeight : headerHeight; + break; + case 'tabs-subheader': + headerEnd = 0; + headerStart = hasTabsTop ? contentTop - headerHeight : subHeaderHeight; + tabsStart = hasTabsTop ? subHeaderHeight : 0; + break; + case 'tabs': + headerEnd = 0; + subheaderEnd = hasTabsTop ? tabsHeight : 0; + break; + case 'subheader': + headerEnd = 0; + tabsEnd = 0; + break; + case 'header-subheader': + tabsEnd = hasTabsTop ? headerHeight : 0; + break; + case 'subheader-header': + headerStart = subHeaderHeight; + tabsStart = hasTabsTop ? subHeaderHeight : 0; + tabsEnd = hasTabsTop ? headerHeight : 0; + break; + //defaults to header-tabs-subheader + default: + headerStart = hasTabsTop ? contentTop - headerHeight : subHeaderHeight; + tabsStart = hasTabsTop ? subHeaderHeight : 0; + } + } + + /** + * Translates active and cached headers, and animates active children + * @param y + * @param duration + */ + function translateHeaders (y, duration) { + var fadeAmt = Math.max(0, 1 - (y / headerHeight)); + + //translate active header + if (activeHeader) { + translateY(activeHeader, y, duration); + angular.forEach(activeHeader.children, function (child) { + child.style.opacity = fadeAmt; + if (scaleHeaderElements) { + child.style[ionic.CSS.TRANSFORM] = 'scale(' + fadeAmt + ',' + fadeAmt + ')'; + } + }); + } + + //translate cached header + if (cachedHeader) { + translateY(cachedHeader, y, duration); + } + } + + /** + * Translates header, tabs, subheader elements and resets content top and/or bottom + * When the active view leaves, we need sync functionality to reset headers and clear + * @param y + * @param duration + */ + function translateElementsSync (y, duration) { + var contentStyle = $element[0].style; + var headerY = y > headerStart ? y - headerStart : 0; + var tabsY, subheaderY; + + //subheader + if (subHeader) { + subheaderY = y > subheaderStart ? y - subheaderStart : 0; + translateY(subHeader, Math.min(subheaderEnd, subheaderY), duration); + } + + //tabs + if (tabs) { + tabsY = Math.min(tabsEnd, y > tabsStart ? y - tabsStart : 0); + + if (hasTabsBottom) { + tabsY = -tabsY; + contentStyle.bottom = Math.max(0, tabsHeight - y) + 'px'; + } + translateY(tabs, tabsY, duration); + } + + //headers + translateHeaders(Math.min(headerEnd, headerY), duration); + + //readjust top of ion-content + var st = Math.max(0, contentTop - y); + if (isIos) + st+=20; + + contentStyle.top = st + 'px'; + } + + /** + * Translates header, tabs, subheader elements and resets content top and/or bottom + * Wraps translate functionality in an animation frame request + * @param y + * @param duration + */ + function translateElements (y, duration) { + ionic.requestAnimationFrame(function() { + translateElementsSync(y, duration); + }); + } + + //Need to reinitialize the values on refreshComplete or things will get out of wack + $scope.$on('scroll.refreshComplete', function () { + initCoordinates(); + }); + + /** + * Before the active view leaves, reset elements, and reset the scroll container + */ + $scope.$parent.$on('$ionicView.beforeLeave', function () { + isNavBarTransitioning = true; + translateElementsSync(0); + activeHeader = null; + cachedHeader = null; + }); + + /** + * Scroll to the top when entering to reset then scrollView scrollTop. (prevents jumping) + */ + $scope.$parent.$on('$ionicView.beforeEnter', function () { + if (scrollView) { + scrollView.scrollTo(0, 0); + } + }); + + /** + * Ionic sets the active/cached nav-bar AFTER the afterEnter event is called, so we need to set a small + * timeout to let the nav-bar logic run. + */ + $scope.$parent.$on('$ionicView.afterEnter', function () { + initCoordinates(); + + $timeout(function () { + init(); + isNavBarTransitioning = false; + }, 20, false); + }); + + /** + * Called onScroll. computes coordinates based on scroll position and translates accordingly + */ + $element.bind('scroll', function (e) { + if (isNavBarTransitioning) { + return; + } + //support for jQuery event as well + e = e.originalEvent || e; + + var duration = 0; + var scrollTop = e.detail?e.detail.scrollTop:e.target.scrollTop; + + y = scrollTop >= 0 ? Math.min(defaultEnd, Math.max(0, y + scrollTop - prevScrollTop)) : 0; + if (isIos && y >headerHeight) + { + y = headerHeight; + } + + //if we are at the bottom, animate the header/tabs back in + if (scrollView.getScrollMax().top - scrollTop <= contentTop) { + // console.log ("MAX TOP="+scrollView.getScrollMax().top+" SCROLL TOP:"+scrollTop+" CONTOP="+contentTop); + // y = 0; + // duration = defaultDuration; + } + + prevScrollTop = scrollTop; + + //if previous and current y are the same, no need to continue + if (prevY === y) { + return; + } + prevY = y; + + translateElements(y, duration); + }); + + } + } + }]); + +})(angular, ionic); diff --git a/www/external/origjs/ng-websocket.js b/www/external/origjs/ng-websocket.js new file mode 100644 index 00000000..02f92d2b --- /dev/null +++ b/www/external/origjs/ng-websocket.js @@ -0,0 +1,381 @@ +'use strict'; + +(function () { + /** + * @ngdoc provider + * @name $websocketProvider + * @module ngWebsocket + * @description + * HTML5 WebSocket provider for AngularJS + */ + function $websocketProvider () { + var wsp = this; + + wsp.$$config = { + lazy: false, + reconnect: true, + reconnectInterval: 2000, + mock: false, + enqueue: false, + protocols: null + }; + + wsp.$setup = function (cfg) { + cfg = cfg || {}; + wsp.$$config = angular.extend({}, wsp.$$config, cfg); + + return wsp; + }; + + wsp.$get = ['$http', function ($http) { + return new $websocketService(wsp.$$config, $http); + }]; + } + + /** + * @ngdoc service + * @name $websocketService + * @module ngWebsocketService + * @description + * HTML5 Websocket service for AngularJS + */ + function $websocketService (cfg, $http) { + var wss = this; + + wss.$$websocketList = {}; + wss.$$config = cfg || {}; + + wss.$get = function (url) { + return wss.$$websocketList[url]; + }; + + wss.$new = function (cfg) { + cfg = cfg || {}; + + // Url or url + protocols initialization + if (typeof cfg === 'string') { + cfg = {url: cfg}; + + // url + protocols + if (arguments.length > 1) { + if (typeof arguments[1] === 'string' && arguments[1].length > 0) cfg.protocols = [arguments[1]]; + else if (typeof arguments[1] === 'object' && arguments[1].length > 0) cfg.protocols = arguments[1]; + } + } + + // If the websocket already exists, return that instance + var ws = wss.$get(cfg.url); + if (typeof ws === 'undefined') { + var wsCfg = angular.extend({}, wss.$$config, cfg); + + ws = new $websocket(wsCfg, $http); + wss.$$websocketList[wsCfg.url] = ws; + } + + return ws; + }; + } + + /** + * @ngdoc class + * @name $websocket + * @module ngWebsocket + * @description + * HTML5 Websocket wrapper class for AngularJS + */ + function $websocket (cfg, $http) { + var me = this; + + if (typeof cfg === 'undefined' || (typeof cfg === 'object' && typeof cfg.url === 'undefined')) throw new Error('An url must be specified for WebSocket'); + + me.$$eventMap = {}; + me.$$ws = undefined; + me.$$reconnectTask = undefined; + me.$$reconnectCopy = true; + me.$$queue = []; + me.$$config = { + url: undefined, + lazy: false, + reconnect: true, + reconnectInterval: 2000, + enqueue: false, + mock: false, + protocols: null + }; + + me.$$fireEvent = function () { + var args = []; + + Array.prototype.push.apply(args, arguments); + + var event = args.shift(), + handlers = me.$$eventMap[event]; + + if (typeof handlers !== 'undefined') { + for (var i = 0; i < handlers.length; i++) { + if (typeof handlers[i] === 'function') handlers[i].apply(me, args); + } + } + }; + + me.$$init = function (cfg) { + //https://github.com/wilk/ng-websocket/issues/11 + //me.$$ws = cfg.mock ? new $$mockWebsocket(cfg.mock, $http) : new WebSocket(cfg.url, cfg.protocols); + if (cfg.mock) { + me.$$ws = new $$mockWebsocket(cfg.mock, $http); + } + else if (cfg.protocols) { + me.$$ws = new WebSocket(cfg.url, cfg.protocols); + } + else { + me.$$ws = new WebSocket(cfg.url); + } + + me.$$ws.onmessage = function (message) { + try { + var decoded = JSON.parse(message.data); + me.$$fireEvent(decoded.event, decoded.data); + me.$$fireEvent('$message', decoded); + } + catch (err) { + me.$$fireEvent('$message', message.data); + } + }; + + me.$$ws.onerror = function (error) { + me.$$fireEvent('$error', error); + }; + + me.$$ws.onopen = function () { + // Clear the reconnect task if exists + if (me.$$reconnectTask) { + clearInterval(me.$$reconnectTask); + delete me.$$reconnectTask; + } + + // Flush the message queue + if (me.$$config.enqueue && me.$$queue.length > 0) { + while (me.$$queue.length > 0) { + if (me.$ready()) me.$$send(me.$$queue.shift()); + else break; + } + } + + me.$$fireEvent('$open'); + }; + + me.$$ws.onclose = function () { + // Activate the reconnect task + // PP https://github.com/cdupetit/ng-websocket/commit/bec0d2c56316871758fcd0e33cb55904efb877f1 + if (me.$$config.reconnect && !me.$$reconnectTask) { + me.$$reconnectTask = setInterval(function () { + if (me.$status() === me.$CLOSED) me.$open(); + }, me.$$config.reconnectInterval); + } + + me.$$fireEvent('$close'); + }; + + return me; + }; + + me.$CONNECTING = 0; + me.$OPEN = 1; + me.$CLOSING = 2; + me.$CLOSED = 3; + + // TODO: it doesn't refresh the view (maybe $apply on something?) + /*me.$bind = function (event, scope, model) { + me.$on(event, function (message) { + model = message; + scope.$apply(); + }); + };*/ + + me.$on = function () { + var handlers = []; + + Array.prototype.push.apply(handlers, arguments); + + var event = handlers.shift(); + if (typeof event !== 'string' || handlers.length === 0) throw new Error('$on accept two parameters at least: a String and a Function or an array of Functions'); + + me.$$eventMap[event] = me.$$eventMap[event] || []; + for (var i = 0; i < handlers.length; i++) { + me.$$eventMap[event].push(handlers[i]); + } + + return me; + }; + + me.$un = function (event) { + if (typeof event !== 'string') throw new Error('$un needs a String representing an event.'); + + if (typeof me.$$eventMap[event] !== 'undefined') delete me.$$eventMap[event]; + + return me; + }; + + me.$$send = function (message) { + if (me.$ready()) me.$$ws.send(JSON.stringify(message)); + else if (me.$$config.enqueue) me.$$queue.push(message); + }; + + me.$emit = function (event, data) { + if (typeof event !== 'string') throw new Error('$emit needs two parameter: a String and a Object or a String'); + + var message = { + event: event, + data: data + }; + + me.$$send(message); + + return me; + }; + + me.$open = function () { + me.$$config.reconnect = me.$$reconnectCopy; + + if (me.$status() !== me.$OPEN) me.$$init(me.$$config); + return me; + }; + + me.$close = function () { + if (me.$status() !== me.$CLOSED) me.$$ws.close(); + + if (me.$$reconnectTask) { + clearInterval(me.$$reconnectTask); + delete me.$$reconnectTask; + } + + me.$$config.reconnect = false; + + return me; + }; + + me.$status = function () { + if (typeof me.$$ws === 'undefined') return me.$CLOSED; + else return me.$$ws.readyState; + }; + + me.$ready = function () { + return me.$status() === me.$OPEN; + }; + + me.$mockup = function () { + return me.$$config.mock; + }; + + // setup + me.$$config = angular.extend({}, me.$$config, cfg); + me.$$reconnectCopy = me.$$config.reconnect; + + if (!me.$$config.lazy) me.$$init(me.$$config); + + return me; + } + + function $$mockWebsocket (cfg, $http) { + cfg = cfg || {}; + + var me = this, + openTimeout = cfg.openTimeout || 500, + closeTimeout = cfg.closeTimeout || 1000, + messageInterval = cfg.messageInterval || 2000, + fixtures = cfg.fixtures || {}, + messageQueue = []; + + me.CONNECTING = 0; + me.OPEN = 1; + me.CLOSING = 2; + me.CLOSED = 3; + + me.readyState = me.CONNECTING; + + me.send = function (message) { + if (me.readyState === me.OPEN) { + messageQueue.push(message); + return me; + } + else throw new Error('WebSocket is already in CLOSING or CLOSED state.'); + }; + + me.close = function () { + if (me.readyState === me.OPEN) { + me.readyState = me.CLOSING; + + setTimeout(function () { + me.readyState = me.CLOSED; + + me.onclose(); + }, closeTimeout); + } + + return me; + }; + + me.onmessage = function () {}; + me.onerror = function () {}; + me.onopen = function () {}; + me.onclose = function () {}; + + setInterval(function () { + if (messageQueue.length > 0) { + var message = messageQueue.shift(), + msgObj = JSON.parse(message); + + switch (msgObj.event) { + case '$close': + me.close(); + break; + default: + // Check for a custom response + if (typeof fixtures[msgObj.event] !== 'undefined') { + msgObj.data = fixtures[msgObj.event].data || msgObj.data; + msgObj.event = fixtures[msgObj.event].event || msgObj.event; + } + + message = JSON.stringify(msgObj); + + me.onmessage({ + data: message + }); + } + } + }, messageInterval); + + var start = function (fixs) { + fixs = fixs || {}; + fixs = fixs instanceof Error ? {} : fixs; + + fixtures = fixs; + + setTimeout(function () { + me.readyState = me.OPEN; + me.onopen(); + }, openTimeout); + }; + + // Get fixtures from a server or a file if it's a string + if (typeof fixtures === 'string') { + $http.get(fixtures) + .success(start) + .error(start); + } + else start(fixtures); + + return me; + } + + /** + * @ngdoc module + * @name $websocket + * @module ngWebsocket + * @description + * HTML5 WebSocket module for AngularJS + */ + angular + .module('ngWebsocket', []) + .provider('$websocket', $websocketProvider); +})(); diff --git a/www/external/origjs/packery.pkgd.js b/www/external/origjs/packery.pkgd.js new file mode 100644 index 00000000..fa4e8308 --- /dev/null +++ b/www/external/origjs/packery.pkgd.js @@ -0,0 +1,3413 @@ +/*! + * Packery PACKAGED v2.0.0 + * Gapless, draggable grid layouts + * + * Licensed GPLv3 for open source use + * or Packery Commercial License for commercial use + * + * http://packery.metafizzy.co + * Copyright 2016 Metafizzy + */ + +/** + * Bridget makes jQuery widgets + * v2.0.0 + * MIT license + */ + +/* jshint browser: true, strict: true, undef: true, unused: true */ + +( function( window, factory ) { + 'use strict'; + /* globals define: false, module: false, require: false */ + + if ( typeof define == 'function' && define.amd ) { + // AMD + define( 'jquery-bridget/jquery-bridget',[ 'jquery' ], function( jQuery ) { + 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; + +})); + +/*! + * 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 ); + + //PP https://github.com/metafizzy/packery/pull/382/commits/ea595864bfc161e992bcb28cbce132df9eac57b9 + getSize.isBoxSizeOuter = isBoxSizeOuter = Math.round(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; + +}); + +/** + * EvEmitter v1.0.2 + * Lil' event emitter + * MIT License + */ + +/* jshint unused: true, undef: true, strict: true */ + +( function( global, factory ) { + // universal module definition + /* jshint strict: false */ /* globals define, module */ + 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(); + } + +}( 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; + +})); + +/** + * 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.1 + * 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 ) { + if ( document.readyState == 'complete' ) { + 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('layoutname') + if ( jQuery ) { + jQuery.data( elem, namespace, instance ); + } + }); + + }); +}; + +// ----- ----- // + +return utils; + +})); + +/** + * Outlayer Item + */ + +( function( window, factory ) { + // universal module definition + /* jshint strict: false */ /* globals define, module, require */ + if ( typeof define == 'function' && define.amd ) { + // AMD - RequireJS + define( 'outlayer/item',[ + 'ev-emitter/ev-emitter', + 'get-size/get-size' + ], + factory + ); + } else if ( typeof module == 'object' && module.exports ) { + // CommonJS - Browserify, Webpack + module.exports = factory( + require('ev-emitter'), + require('get-size') + ); + } else { + // browser global + window.Outlayer = {}; + window.Outlayer.Item = factory( + window.EvEmitter, + window.getSize + ); + } + +}( window, function factory( EvEmitter, getSize ) { +'use strict'; + +// ----- helpers ----- // + +function isEmptyObj( obj ) { + for ( var prop in obj ) { + return false; + } + prop = null; + return true; +} + +// -------------------------- CSS3 support -------------------------- // + + +var docElemStyle = document.documentElement.style; + +var transitionProperty = typeof docElemStyle.transition == 'string' ? + 'transition' : 'WebkitTransition'; +var transformProperty = typeof docElemStyle.transform == 'string' ? + 'transform' : 'WebkitTransform'; + +var transitionEndEvent = { + WebkitTransition: 'webkitTransitionEnd', + transition: 'transitionend' +}[ transitionProperty ]; + +// cache all vendor properties that could have vendor prefix +var vendorProperties = { + transform: transformProperty, + transition: transitionProperty, + transitionDuration: transitionProperty + 'Duration', + transitionProperty: transitionProperty + 'Property' +}; + +// -------------------------- Item -------------------------- // + +function Item( element, layout ) { + if ( !element ) { + return; + } + + this.element = element; + // parent layout class, i.e. Masonry, Isotope, or Packery + this.layout = layout; + this.position = { + x: 0, + y: 0 + }; + + this._create(); +} + +// inherit EvEmitter +var proto = Item.prototype = Object.create( EvEmitter.prototype ); +proto.constructor = Item; + +proto._create = function() { + // transition objects + this._transn = { + ingProperties: {}, + clean: {}, + onEnd: {} + }; + + this.css({ + position: 'absolute' + }); +}; + +// trigger specified handler for event type +proto.handleEvent = function( event ) { + var method = 'on' + event.type; + if ( this[ method ] ) { + this[ method ]( event ); + } +}; + +proto.getSize = function() { + this.size = getSize( this.element ); +}; + +/** + * apply CSS styles to element + * @param {Object} style + */ +proto.css = function( style ) { + var elemStyle = this.element.style; + + for ( var prop in style ) { + // use vendor property if available + var supportedProp = vendorProperties[ prop ] || prop; + elemStyle[ supportedProp ] = style[ prop ]; + } +}; + + // measure position, and sets it +proto.getPosition = function() { + var style = getComputedStyle( this.element ); + var isOriginLeft = this.layout._getOption('originLeft'); + var isOriginTop = this.layout._getOption('originTop'); + var xValue = style[ isOriginLeft ? 'left' : 'right' ]; + var yValue = style[ isOriginTop ? 'top' : 'bottom' ]; + // convert percent to pixels + var layoutSize = this.layout.size; + var x = xValue.indexOf('%') != -1 ? + ( parseFloat( xValue ) / 100 ) * layoutSize.width : parseInt( xValue, 10 ); + var y = yValue.indexOf('%') != -1 ? + ( parseFloat( yValue ) / 100 ) * layoutSize.height : parseInt( yValue, 10 ); + + // clean up 'auto' or other non-integer values + x = isNaN( x ) ? 0 : x; + y = isNaN( y ) ? 0 : y; + // remove padding from measurement + x -= isOriginLeft ? layoutSize.paddingLeft : layoutSize.paddingRight; + y -= isOriginTop ? layoutSize.paddingTop : layoutSize.paddingBottom; + + this.position.x = x; + this.position.y = y; +}; + +// set settled position, apply padding +proto.layoutPosition = function() { + var layoutSize = this.layout.size; + var style = {}; + var isOriginLeft = this.layout._getOption('originLeft'); + var isOriginTop = this.layout._getOption('originTop'); + + // x + var xPadding = isOriginLeft ? 'paddingLeft' : 'paddingRight'; + var xProperty = isOriginLeft ? 'left' : 'right'; + var xResetProperty = isOriginLeft ? 'right' : 'left'; + + var x = this.position.x + layoutSize[ xPadding ]; + // set in percentage or pixels + style[ xProperty ] = this.getXValue( x ); + // reset other property + style[ xResetProperty ] = ''; + + // y + var yPadding = isOriginTop ? 'paddingTop' : 'paddingBottom'; + var yProperty = isOriginTop ? 'top' : 'bottom'; + var yResetProperty = isOriginTop ? 'bottom' : 'top'; + + var y = this.position.y + layoutSize[ yPadding ]; + // set in percentage or pixels + style[ yProperty ] = this.getYValue( y ); + // reset other property + style[ yResetProperty ] = ''; + + this.css( style ); + this.emitEvent( 'layout', [ this ] ); +}; + +proto.getXValue = function( x ) { + var isHorizontal = this.layout._getOption('horizontal'); + return this.layout.options.percentPosition && !isHorizontal ? + ( ( x / this.layout.size.width ) * 100 ) + '%' : x + 'px'; +}; + +proto.getYValue = function( y ) { + var isHorizontal = this.layout._getOption('horizontal'); + return this.layout.options.percentPosition && isHorizontal ? + ( ( y / this.layout.size.height ) * 100 ) + '%' : y + 'px'; +}; + +proto._transitionTo = function( x, y ) { + this.getPosition(); + // get current x & y from top/left + var curX = this.position.x; + var curY = this.position.y; + + var compareX = parseInt( x, 10 ); + var compareY = parseInt( y, 10 ); + var didNotMove = compareX === this.position.x && compareY === this.position.y; + + // save end position + this.setPosition( x, y ); + + // if did not move and not transitioning, just go to layout + if ( didNotMove && !this.isTransitioning ) { + this.layoutPosition(); + return; + } + + var transX = x - curX; + var transY = y - curY; + var transitionStyle = {}; + transitionStyle.transform = this.getTranslate( transX, transY ); + + this.transition({ + to: transitionStyle, + onTransitionEnd: { + transform: this.layoutPosition + }, + isCleaning: true + }); +}; + +proto.getTranslate = function( x, y ) { + // flip cooridinates if origin on right or bottom + var isOriginLeft = this.layout._getOption('originLeft'); + var isOriginTop = this.layout._getOption('originTop'); + x = isOriginLeft ? x : -x; + y = isOriginTop ? y : -y; + return 'translate3d(' + x + 'px, ' + y + 'px, 0)'; +}; + +// non transition + transform support +proto.goTo = function( x, y ) { + this.setPosition( x, y ); + this.layoutPosition(); +}; + +proto.moveTo = proto._transitionTo; + +proto.setPosition = function( x, y ) { + this.position.x = parseInt( x, 10 ); + this.position.y = parseInt( y, 10 ); +}; + +// ----- transition ----- // + +/** + * @param {Object} style - CSS + * @param {Function} onTransitionEnd + */ + +// non transition, just trigger callback +proto._nonTransition = function( args ) { + this.css( args.to ); + if ( args.isCleaning ) { + this._removeStyles( args.to ); + } + for ( var prop in args.onTransitionEnd ) { + args.onTransitionEnd[ prop ].call( this ); + } +}; + +/** + * proper transition + * @param {Object} args - arguments + * @param {Object} to - style to transition to + * @param {Object} from - style to start transition from + * @param {Boolean} isCleaning - removes transition styles after transition + * @param {Function} onTransitionEnd - callback + */ +proto.transition = function( args ) { + // redirect to nonTransition if no transition duration + if ( !parseFloat( this.layout.options.transitionDuration ) ) { + this._nonTransition( args ); + return; + } + + var _transition = this._transn; + // keep track of onTransitionEnd callback by css property + for ( var prop in args.onTransitionEnd ) { + _transition.onEnd[ prop ] = args.onTransitionEnd[ prop ]; + } + // keep track of properties that are transitioning + for ( prop in args.to ) { + _transition.ingProperties[ prop ] = true; + // keep track of properties to clean up when transition is done + if ( args.isCleaning ) { + _transition.clean[ prop ] = true; + } + } + + // set from styles + if ( args.from ) { + this.css( args.from ); + // force redraw. http://blog.alexmaccaw.com/css-transitions + var h = this.element.offsetHeight; + // hack for JSHint to hush about unused var + h = null; + } + // enable transition + this.enableTransition( args.to ); + // set styles that are transitioning + this.css( args.to ); + + this.isTransitioning = true; + +}; + +// dash before all cap letters, including first for +// WebkitTransform => -webkit-transform +function toDashedAll( str ) { + return str.replace( /([A-Z])/g, function( $1 ) { + return '-' + $1.toLowerCase(); + }); +} + +var transitionProps = 'opacity,' + toDashedAll( transformProperty ); + +proto.enableTransition = function(/* style */) { + // HACK changing transitionProperty during a transition + // will cause transition to jump + if ( this.isTransitioning ) { + return; + } + + // make `transition: foo, bar, baz` from style object + // HACK un-comment this when enableTransition can work + // while a transition is happening + // var transitionValues = []; + // for ( var prop in style ) { + // // dash-ify camelCased properties like WebkitTransition + // prop = vendorProperties[ prop ] || prop; + // transitionValues.push( toDashedAll( prop ) ); + // } + // enable transition styles + this.css({ + transitionProperty: transitionProps, + transitionDuration: this.layout.options.transitionDuration + }); + // listen for transition end event + this.element.addEventListener( transitionEndEvent, this, false ); +}; + +// ----- events ----- // + +proto.onwebkitTransitionEnd = function( event ) { + this.ontransitionend( event ); +}; + +proto.onotransitionend = function( event ) { + this.ontransitionend( event ); +}; + +// properties that I munge to make my life easier +var dashedVendorProperties = { + '-webkit-transform': 'transform' +}; + +proto.ontransitionend = function( event ) { + // disregard bubbled events from children + if ( event.target !== this.element ) { + return; + } + var _transition = this._transn; + // get property name of transitioned property, convert to prefix-free + var propertyName = dashedVendorProperties[ event.propertyName ] || event.propertyName; + + // remove property that has completed transitioning + delete _transition.ingProperties[ propertyName ]; + // check if any properties are still transitioning + if ( isEmptyObj( _transition.ingProperties ) ) { + // all properties have completed transitioning + this.disableTransition(); + } + // clean style + if ( propertyName in _transition.clean ) { + // clean up style + this.element.style[ event.propertyName ] = ''; + delete _transition.clean[ propertyName ]; + } + // trigger onTransitionEnd callback + if ( propertyName in _transition.onEnd ) { + var onTransitionEnd = _transition.onEnd[ propertyName ]; + onTransitionEnd.call( this ); + delete _transition.onEnd[ propertyName ]; + } + + this.emitEvent( 'transitionEnd', [ this ] ); +}; + +proto.disableTransition = function() { + this.removeTransitionStyles(); + this.element.removeEventListener( transitionEndEvent, this, false ); + this.isTransitioning = false; +}; + +/** + * removes style property from element + * @param {Object} style +**/ +proto._removeStyles = function( style ) { + // clean up transition styles + var cleanStyle = {}; + for ( var prop in style ) { + cleanStyle[ prop ] = ''; + } + this.css( cleanStyle ); +}; + +var cleanTransitionStyle = { + transitionProperty: '', + transitionDuration: '' +}; + +proto.removeTransitionStyles = function() { + // remove transition + this.css( cleanTransitionStyle ); +}; + +// ----- show/hide/remove ----- // + +// remove element from DOM +proto.removeElem = function() { + this.element.parentNode.removeChild( this.element ); + // remove display: none + this.css({ display: '' }); + this.emitEvent( 'remove', [ this ] ); +}; + +proto.remove = function() { + // just remove element if no transition support or no transition + if ( !transitionProperty || !parseFloat( this.layout.options.transitionDuration ) ) { + this.removeElem(); + return; + } + + // start transition + this.once( 'transitionEnd', function() { + this.removeElem(); + }); + this.hide(); +}; + +proto.reveal = function() { + delete this.isHidden; + // remove display: none + this.css({ display: '' }); + + var options = this.layout.options; + + var onTransitionEnd = {}; + var transitionEndProperty = this.getHideRevealTransitionEndProperty('visibleStyle'); + onTransitionEnd[ transitionEndProperty ] = this.onRevealTransitionEnd; + + this.transition({ + from: options.hiddenStyle, + to: options.visibleStyle, + isCleaning: true, + onTransitionEnd: onTransitionEnd + }); +}; + +proto.onRevealTransitionEnd = function() { + // check if still visible + // during transition, item may have been hidden + if ( !this.isHidden ) { + this.emitEvent('reveal'); + } +}; + +/** + * get style property use for hide/reveal transition end + * @param {String} styleProperty - hiddenStyle/visibleStyle + * @returns {String} + */ +proto.getHideRevealTransitionEndProperty = function( styleProperty ) { + var optionStyle = this.layout.options[ styleProperty ]; + // use opacity + if ( optionStyle.opacity ) { + return 'opacity'; + } + // get first property + for ( var prop in optionStyle ) { + return prop; + } +}; + +proto.hide = function() { + // set flag + this.isHidden = true; + // remove display: none + this.css({ display: '' }); + + var options = this.layout.options; + + var onTransitionEnd = {}; + var transitionEndProperty = this.getHideRevealTransitionEndProperty('hiddenStyle'); + onTransitionEnd[ transitionEndProperty ] = this.onHideTransitionEnd; + + this.transition({ + from: options.visibleStyle, + to: options.hiddenStyle, + // keep hidden stuff hidden + isCleaning: true, + onTransitionEnd: onTransitionEnd + }); +}; + +proto.onHideTransitionEnd = function() { + // check if still hidden + // during transition, item may have been un-hidden + if ( this.isHidden ) { + this.css({ display: 'none' }); + this.emitEvent('hide'); + } +}; + +proto.destroy = function() { + this.css({ + position: '', + left: '', + right: '', + top: '', + bottom: '', + transition: '', + transform: '' + }); +}; + +return Item; + +})); + +/*! + * Outlayer v2.0.1 + * the brains and guts of a layout library + * MIT license + */ + +( function( window, factory ) { + 'use strict'; + // universal module definition + /* jshint strict: false */ /* globals define, module, require */ + if ( typeof define == 'function' && define.amd ) { + // AMD - RequireJS + define( 'outlayer/outlayer',[ + 'ev-emitter/ev-emitter', + 'get-size/get-size', + 'fizzy-ui-utils/utils', + './item' + ], + function( EvEmitter, getSize, utils, Item ) { + return factory( window, EvEmitter, getSize, utils, Item); + } + ); + } else if ( typeof module == 'object' && module.exports ) { + // CommonJS - Browserify, Webpack + module.exports = factory( + window, + require('ev-emitter'), + require('get-size'), + require('fizzy-ui-utils'), + require('./item') + ); + } else { + // browser global + window.Outlayer = factory( + window, + window.EvEmitter, + window.getSize, + window.fizzyUIUtils, + window.Outlayer.Item + ); + } + +}( window, function factory( window, EvEmitter, getSize, utils, Item ) { +'use strict'; + +// ----- vars ----- // + +var console = window.console; +var jQuery = window.jQuery; +var noop = function() {}; + +// -------------------------- Outlayer -------------------------- // + +// globally unique identifiers +var GUID = 0; +// internal store of all Outlayer intances +var instances = {}; + + +/** + * @param {Element, String} element + * @param {Object} options + * @constructor + */ +function Outlayer( element, options ) { + var queryElement = utils.getQueryElement( element ); + if ( !queryElement ) { + if ( console ) { + console.error( 'Bad element for ' + this.constructor.namespace + + ': ' + ( queryElement || element ) ); + } + return; + } + this.element = queryElement; + // add jQuery + if ( jQuery ) { + this.$element = jQuery( this.element ); + } + + // options + this.options = utils.extend( {}, this.constructor.defaults ); + this.option( options ); + + // add id for Outlayer.getFromElement + var id = ++GUID; + this.element.outlayerGUID = id; // expando + instances[ id ] = this; // associate via id + + // kick it off + this._create(); + + var isInitLayout = this._getOption('initLayout'); + if ( isInitLayout ) { + this.layout(); + } +} + +// settings are for internal use only +Outlayer.namespace = 'outlayer'; +Outlayer.Item = Item; + +// default options +Outlayer.defaults = { + containerStyle: { + position: 'relative' + }, + initLayout: true, + originLeft: true, + originTop: true, + resize: true, + resizeContainer: true, + // item options + transitionDuration: '0.4s', + hiddenStyle: { + opacity: 0, + transform: 'scale(0.001)' + }, + visibleStyle: { + opacity: 1, + transform: 'scale(1)' + } +}; + +var proto = Outlayer.prototype; +// inherit EvEmitter +utils.extend( proto, EvEmitter.prototype ); + +/** + * set options + * @param {Object} opts + */ +proto.option = function( opts ) { + utils.extend( this.options, opts ); +}; + +/** + * get backwards compatible option value, check old name + */ +proto._getOption = function( option ) { + var oldOption = this.constructor.compatOptions[ option ]; + return oldOption && this.options[ oldOption ] !== undefined ? + this.options[ oldOption ] : this.options[ option ]; +}; + +Outlayer.compatOptions = { + // currentName: oldName + initLayout: 'isInitLayout', + horizontal: 'isHorizontal', + layoutInstant: 'isLayoutInstant', + originLeft: 'isOriginLeft', + originTop: 'isOriginTop', + resize: 'isResizeBound', + resizeContainer: 'isResizingContainer' +}; + +proto._create = function() { + // get items from children + this.reloadItems(); + // elements that affect layout, but are not laid out + this.stamps = []; + this.stamp( this.options.stamp ); + // set container style + utils.extend( this.element.style, this.options.containerStyle ); + + // bind resize method + var canBindResize = this._getOption('resize'); + if ( canBindResize ) { + this.bindResize(); + } +}; + +// goes through all children again and gets bricks in proper order +proto.reloadItems = function() { + // collection of item elements + this.items = this._itemize( this.element.children ); +}; + + +/** + * turn elements into Outlayer.Items to be used in layout + * @param {Array or NodeList or HTMLElement} elems + * @returns {Array} items - collection of new Outlayer Items + */ +proto._itemize = function( elems ) { + + var itemElems = this._filterFindItemElements( elems ); + var Item = this.constructor.Item; + + // create new Outlayer Items for collection + var items = []; + for ( var i=0; i < itemElems.length; i++ ) { + var elem = itemElems[i]; + var item = new Item( elem, this ); + items.push( item ); + } + + return items; +}; + +/** + * get item elements to be used in layout + * @param {Array or NodeList or HTMLElement} elems + * @returns {Array} items - item elements + */ +proto._filterFindItemElements = function( elems ) { + return utils.filterFindElements( elems, this.options.itemSelector ); +}; + +/** + * getter method for getting item elements + * @returns {Array} elems - collection of item elements + */ +proto.getItemElements = function() { + return this.items.map( function( item ) { + return item.element; + }); +}; + +// ----- init & layout ----- // + +/** + * lays out all items + */ +proto.layout = function() { + this._resetLayout(); + this._manageStamps(); + + // don't animate first layout + var layoutInstant = this._getOption('layoutInstant'); + var isInstant = layoutInstant !== undefined ? + layoutInstant : !this._isLayoutInited; + this.layoutItems( this.items, isInstant ); + + // flag for initalized + this._isLayoutInited = true; +}; + +// _init is alias for layout +proto._init = proto.layout; + +/** + * logic before any new layout + */ +proto._resetLayout = function() { + this.getSize(); +}; + + +proto.getSize = function() { + this.size = getSize( this.element ); +}; + +/** + * get measurement from option, for columnWidth, rowHeight, gutter + * if option is String -> get element from selector string, & get size of element + * if option is Element -> get size of element + * else use option as a number + * + * @param {String} measurement + * @param {String} size - width or height + * @private + */ +proto._getMeasurement = function( measurement, size ) { + var option = this.options[ measurement ]; + var elem; + if ( !option ) { + // default to 0 + this[ measurement ] = 0; + } else { + // use option as an element + if ( typeof option == 'string' ) { + elem = this.element.querySelector( option ); + } else if ( option instanceof HTMLElement ) { + elem = option; + } + // use size of element, if element + this[ measurement ] = elem ? getSize( elem )[ size ] : option; + } +}; + +/** + * layout a collection of item elements + * @api public + */ +proto.layoutItems = function( items, isInstant ) { + items = this._getItemsForLayout( items ); + + this._layoutItems( items, isInstant ); + + this._postLayout(); +}; + +/** + * get the items to be laid out + * you may want to skip over some items + * @param {Array} items + * @returns {Array} items + */ +proto._getItemsForLayout = function( items ) { + return items.filter( function( item ) { + return !item.isIgnored; + }); +}; + +/** + * layout items + * @param {Array} items + * @param {Boolean} isInstant + */ +proto._layoutItems = function( items, isInstant ) { + this._emitCompleteOnItems( 'layout', items ); + + if ( !items || !items.length ) { + // no items, emit event with empty array + return; + } + + var queue = []; + + items.forEach( function( item ) { + // get x/y object from method + var position = this._getItemLayoutPosition( item ); + // enqueue + position.item = item; + position.isInstant = isInstant || item.isLayoutInstant; + queue.push( position ); + }, this ); + + this._processLayoutQueue( queue ); +}; + +/** + * get item layout position + * @param {Outlayer.Item} item + * @returns {Object} x and y position + */ +proto._getItemLayoutPosition = function( /* item */ ) { + return { + x: 0, + y: 0 + }; +}; + +/** + * iterate over array and position each item + * Reason being - separating this logic prevents 'layout invalidation' + * thx @paul_irish + * @param {Array} queue + */ +proto._processLayoutQueue = function( queue ) { + queue.forEach( function( obj ) { + this._positionItem( obj.item, obj.x, obj.y, obj.isInstant ); + }, this ); +}; + +/** + * Sets position of item in DOM + * @param {Outlayer.Item} item + * @param {Number} x - horizontal position + * @param {Number} y - vertical position + * @param {Boolean} isInstant - disables transitions + */ +proto._positionItem = function( item, x, y, isInstant ) { + if ( isInstant ) { + // if not transition, just set CSS + item.goTo( x, y ); + } else { + item.moveTo( x, y ); + } +}; + +/** + * Any logic you want to do after each layout, + * i.e. size the container + */ +proto._postLayout = function() { + this.resizeContainer(); +}; + +proto.resizeContainer = function() { + var isResizingContainer = this._getOption('resizeContainer'); + if ( !isResizingContainer ) { + return; + } + var size = this._getContainerSize(); + if ( size ) { + this._setContainerMeasure( size.width, true ); + this._setContainerMeasure( size.height, false ); + } +}; + +/** + * Sets width or height of container if returned + * @returns {Object} size + * @param {Number} width + * @param {Number} height + */ +proto._getContainerSize = noop; + +/** + * @param {Number} measure - size of width or height + * @param {Boolean} isWidth + */ +proto._setContainerMeasure = function( measure, isWidth ) { + if ( measure === undefined ) { + return; + } + + var elemSize = this.size; + // add padding and border width if border box + if ( elemSize.isBorderBox ) { + measure += isWidth ? elemSize.paddingLeft + elemSize.paddingRight + + elemSize.borderLeftWidth + elemSize.borderRightWidth : + elemSize.paddingBottom + elemSize.paddingTop + + elemSize.borderTopWidth + elemSize.borderBottomWidth; + } + + measure = Math.max( measure, 0 ); + this.element.style[ isWidth ? 'width' : 'height' ] = measure + 'px'; +}; + +/** + * emit eventComplete on a collection of items events + * @param {String} eventName + * @param {Array} items - Outlayer.Items + */ +proto._emitCompleteOnItems = function( eventName, items ) { + var _this = this; + function onComplete() { + _this.dispatchEvent( eventName + 'Complete', null, [ items ] ); + } + + var count = items.length; + if ( !items || !count ) { + onComplete(); + return; + } + + var doneCount = 0; + function tick() { + doneCount++; + if ( doneCount == count ) { + onComplete(); + } + } + + // bind callback + items.forEach( function( item ) { + item.once( eventName, tick ); + }); +}; + +/** + * emits events via EvEmitter 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 ) { + // add original event to arguments + var emitArgs = event ? [ event ].concat( args ) : args; + this.emitEvent( type, emitArgs ); + + if ( jQuery ) { + // set this.$element + this.$element = this.$element || jQuery( this.element ); + if ( event ) { + // create jQuery event + var $event = jQuery.Event( event ); + $event.type = type; + this.$element.trigger( $event, args ); + } else { + // just trigger with type if no event available + this.$element.trigger( type, args ); + } + } +}; + +// -------------------------- ignore & stamps -------------------------- // + + +/** + * keep item in collection, but do not lay it out + * ignored items do not get skipped in layout + * @param {Element} elem + */ +proto.ignore = function( elem ) { + var item = this.getItem( elem ); + if ( item ) { + item.isIgnored = true; + } +}; + +/** + * return item to layout collection + * @param {Element} elem + */ +proto.unignore = function( elem ) { + var item = this.getItem( elem ); + if ( item ) { + delete item.isIgnored; + } +}; + +/** + * adds elements to stamps + * @param {NodeList, Array, Element, or String} elems + */ +proto.stamp = function( elems ) { + elems = this._find( elems ); + if ( !elems ) { + return; + } + + this.stamps = this.stamps.concat( elems ); + // ignore + elems.forEach( this.ignore, this ); +}; + +/** + * removes elements to stamps + * @param {NodeList, Array, or Element} elems + */ +proto.unstamp = function( elems ) { + elems = this._find( elems ); + if ( !elems ){ + return; + } + + elems.forEach( function( elem ) { + // filter out removed stamp elements + utils.removeFrom( this.stamps, elem ); + this.unignore( elem ); + }, this ); +}; + +/** + * finds child elements + * @param {NodeList, Array, Element, or String} elems + * @returns {Array} elems + */ +proto._find = function( elems ) { + if ( !elems ) { + return; + } + // if string, use argument as selector string + if ( typeof elems == 'string' ) { + elems = this.element.querySelectorAll( elems ); + } + elems = utils.makeArray( elems ); + return elems; +}; + +proto._manageStamps = function() { + if ( !this.stamps || !this.stamps.length ) { + return; + } + + this._getBoundingRect(); + + this.stamps.forEach( this._manageStamp, this ); +}; + +// update boundingLeft / Top +proto._getBoundingRect = function() { + // get bounding rect for container element + var boundingRect = this.element.getBoundingClientRect(); + var size = this.size; + this._boundingRect = { + left: boundingRect.left + size.paddingLeft + size.borderLeftWidth, + top: boundingRect.top + size.paddingTop + size.borderTopWidth, + right: boundingRect.right - ( size.paddingRight + size.borderRightWidth ), + bottom: boundingRect.bottom - ( size.paddingBottom + size.borderBottomWidth ) + }; +}; + +/** + * @param {Element} stamp +**/ +proto._manageStamp = noop; + +/** + * get x/y position of element relative to container element + * @param {Element} elem + * @returns {Object} offset - has left, top, right, bottom + */ +proto._getElementOffset = function( elem ) { + var boundingRect = elem.getBoundingClientRect(); + var thisRect = this._boundingRect; + var size = getSize( elem ); + var offset = { + left: boundingRect.left - thisRect.left - size.marginLeft, + top: boundingRect.top - thisRect.top - size.marginTop, + right: thisRect.right - boundingRect.right - size.marginRight, + bottom: thisRect.bottom - boundingRect.bottom - size.marginBottom + }; + return offset; +}; + +// -------------------------- resize -------------------------- // + +// enable event handlers for listeners +// i.e. resize -> onresize +proto.handleEvent = utils.handleEvent; + +/** + * Bind layout to window resizing + */ +proto.bindResize = function() { + window.addEventListener( 'resize', this ); + this.isResizeBound = true; +}; + +/** + * Unbind layout to window resizing + */ +proto.unbindResize = function() { + window.removeEventListener( 'resize', this ); + this.isResizeBound = false; +}; + +proto.onresize = function() { + this.resize(); +}; + +utils.debounceMethod( Outlayer, 'onresize', 100 ); + +proto.resize = function() { + // don't trigger if size did not change + // or if resize was unbound. See #9 + if ( !this.isResizeBound || !this.needsResizeLayout() ) { + return; + } + + this.layout(); +}; + +/** + * check if layout is needed post layout + * @returns Boolean + */ +proto.needsResizeLayout = function() { + var size = getSize( this.element ); + // check that this.size and size are there + // IE8 triggers resize on body size change, so they might not be + var hasSizes = this.size && size; + return hasSizes && size.innerWidth !== this.size.innerWidth; +}; + +// -------------------------- methods -------------------------- // + +/** + * add items to Outlayer instance + * @param {Array or NodeList or Element} elems + * @returns {Array} items - Outlayer.Items +**/ +proto.addItems = function( elems ) { + var items = this._itemize( elems ); + // add items to collection + if ( items.length ) { + this.items = this.items.concat( items ); + } + return items; +}; + +/** + * Layout newly-appended item elements + * @param {Array or NodeList or Element} elems + */ +proto.appended = function( elems ) { + var items = this.addItems( elems ); + if ( !items.length ) { + return; + } + // layout and reveal just the new items + this.layoutItems( items, true ); + this.reveal( items ); +}; + +/** + * Layout prepended elements + * @param {Array or NodeList or Element} elems + */ +proto.prepended = function( elems ) { + var items = this._itemize( elems ); + if ( !items.length ) { + return; + } + // add items to beginning of collection + var previousItems = this.items.slice(0); + this.items = items.concat( previousItems ); + // start new layout + this._resetLayout(); + this._manageStamps(); + // layout new stuff without transition + this.layoutItems( items, true ); + this.reveal( items ); + // layout previous items + this.layoutItems( previousItems ); +}; + +/** + * reveal a collection of items + * @param {Array of Outlayer.Items} items + */ +proto.reveal = function( items ) { + this._emitCompleteOnItems( 'reveal', items ); + if ( !items || !items.length ) { + return; + } + items.forEach( function( item ) { + item.reveal(); + }); +}; + +/** + * hide a collection of items + * @param {Array of Outlayer.Items} items + */ +proto.hide = function( items ) { + this._emitCompleteOnItems( 'hide', items ); + if ( !items || !items.length ) { + return; + } + items.forEach( function( item ) { + item.hide(); + }); +}; + +/** + * reveal item elements + * @param {Array}, {Element}, {NodeList} items + */ +proto.revealItemElements = function( elems ) { + var items = this.getItems( elems ); + this.reveal( items ); +}; + +/** + * hide item elements + * @param {Array}, {Element}, {NodeList} items + */ +proto.hideItemElements = function( elems ) { + var items = this.getItems( elems ); + this.hide( items ); +}; + +/** + * get Outlayer.Item, given an Element + * @param {Element} elem + * @param {Function} callback + * @returns {Outlayer.Item} item + */ +proto.getItem = function( elem ) { + // loop through items to get the one that matches + for ( var i=0; i < this.items.length; i++ ) { + var item = this.items[i]; + if ( item.element == elem ) { + // return item + return item; + } + } +}; + +/** + * get collection of Outlayer.Items, given Elements + * @param {Array} elems + * @returns {Array} items - Outlayer.Items + */ +proto.getItems = function( elems ) { + elems = utils.makeArray( elems ); + var items = []; + elems.forEach( function( elem ) { + var item = this.getItem( elem ); + if ( item ) { + items.push( item ); + } + }, this ); + + return items; +}; + +/** + * remove element(s) from instance and DOM + * @param {Array or NodeList or Element} elems + */ +proto.remove = function( elems ) { + var removeItems = this.getItems( elems ); + + this._emitCompleteOnItems( 'remove', removeItems ); + + // bail if no items to remove + if ( !removeItems || !removeItems.length ) { + return; + } + + removeItems.forEach( function( item ) { + item.remove(); + // remove item from collection + utils.removeFrom( this.items, item ); + }, this ); +}; + +// ----- destroy ----- // + +// remove and disable Outlayer instance +proto.destroy = function() { + // clean up dynamic styles + var style = this.element.style; + style.height = ''; + style.position = ''; + style.width = ''; + // destroy items + this.items.forEach( function( item ) { + item.destroy(); + }); + + this.unbindResize(); + + var id = this.element.outlayerGUID; + delete instances[ id ]; // remove reference to instance by id + delete this.element.outlayerGUID; + // remove data for jQuery + if ( jQuery ) { + jQuery.removeData( this.element, this.constructor.namespace ); + } + +}; + +// -------------------------- data -------------------------- // + +/** + * get Outlayer instance from element + * @param {Element} elem + * @returns {Outlayer} + */ +Outlayer.data = function( elem ) { + elem = utils.getQueryElement( elem ); + var id = elem && elem.outlayerGUID; + return id && instances[ id ]; +}; + + +// -------------------------- create Outlayer class -------------------------- // + +/** + * create a layout class + * @param {String} namespace + */ +Outlayer.create = function( namespace, options ) { + // sub-class Outlayer + var Layout = subclass( Outlayer ); + // apply new options and compatOptions + Layout.defaults = utils.extend( {}, Outlayer.defaults ); + utils.extend( Layout.defaults, options ); + Layout.compatOptions = utils.extend( {}, Outlayer.compatOptions ); + + Layout.namespace = namespace; + + Layout.data = Outlayer.data; + + // sub-class Item + Layout.Item = subclass( Item ); + + // -------------------------- declarative -------------------------- // + + utils.htmlInit( Layout, namespace ); + + // -------------------------- jQuery bridge -------------------------- // + + // make into jQuery plugin + if ( jQuery && jQuery.bridget ) { + jQuery.bridget( namespace, Layout ); + } + + return Layout; +}; + +function subclass( Parent ) { + function SubClass() { + Parent.apply( this, arguments ); + } + + SubClass.prototype = Object.create( Parent.prototype ); + SubClass.prototype.constructor = SubClass; + + return SubClass; +} + +// ----- fin ----- // + +// back in global +Outlayer.Item = Item; + +return Outlayer; + +})); + +/** + * Rect + * low-level utility class for basic geometry + */ + +( function( window, factory ) { + // universal module definition + /* jshint strict: false */ /* globals define, module */ + if ( typeof define == 'function' && define.amd ) { + // AMD + define( 'packery/rect',factory ); + } else if ( typeof module == 'object' && module.exports ) { + // CommonJS + module.exports = factory(); + } else { + // browser global + window.Packery = window.Packery || {}; + window.Packery.Rect = factory(); + } + +}( window, function factory() { +'use strict'; + +// -------------------------- Rect -------------------------- // + +function Rect( props ) { + // extend properties from defaults + for ( var prop in Rect.defaults ) { + this[ prop ] = Rect.defaults[ prop ]; + } + + for ( prop in props ) { + this[ prop ] = props[ prop ]; + } + +} + +Rect.defaults = { + x: 0, + y: 0, + width: 0, + height: 0 +}; + +var proto = Rect.prototype; + +/** + * Determines whether or not this rectangle wholly encloses another rectangle or point. + * @param {Rect} rect + * @returns {Boolean} +**/ +proto.contains = function( rect ) { + // points don't have width or height + var otherWidth = rect.width || 0; + var otherHeight = rect.height || 0; + return this.x <= rect.x && + this.y <= rect.y && + this.x + this.width >= rect.x + otherWidth && + this.y + this.height >= rect.y + otherHeight; +}; + +/** + * Determines whether or not the rectangle intersects with another. + * @param {Rect} rect + * @returns {Boolean} +**/ +proto.overlaps = function( rect ) { + var thisRight = this.x + this.width; + var thisBottom = this.y + this.height; + var rectRight = rect.x + rect.width; + var rectBottom = rect.y + rect.height; + + // http://stackoverflow.com/a/306332 + return this.x < rectRight && + thisRight > rect.x && + this.y < rectBottom && + thisBottom > rect.y; +}; + +/** + * @param {Rect} rect - the overlapping rect + * @returns {Array} freeRects - rects representing the area around the rect +**/ +proto.getMaximalFreeRects = function( rect ) { + + // if no intersection, return false + if ( !this.overlaps( rect ) ) { + return false; + } + + var freeRects = []; + var freeRect; + + var thisRight = this.x + this.width; + var thisBottom = this.y + this.height; + var rectRight = rect.x + rect.width; + var rectBottom = rect.y + rect.height; + + // top + if ( this.y < rect.y ) { + freeRect = new Rect({ + x: this.x, + y: this.y, + width: this.width, + height: rect.y - this.y + }); + freeRects.push( freeRect ); + } + + // right + if ( thisRight > rectRight ) { + freeRect = new Rect({ + x: rectRight, + y: this.y, + width: thisRight - rectRight, + height: this.height + }); + freeRects.push( freeRect ); + } + + // bottom + if ( thisBottom > rectBottom ) { + freeRect = new Rect({ + x: this.x, + y: rectBottom, + width: this.width, + height: thisBottom - rectBottom + }); + freeRects.push( freeRect ); + } + + // left + if ( this.x < rect.x ) { + freeRect = new Rect({ + x: this.x, + y: this.y, + width: rect.x - this.x, + height: this.height + }); + freeRects.push( freeRect ); + } + + return freeRects; +}; + +proto.canFit = function( rect ) { + return this.width >= rect.width && this.height >= rect.height; +}; + +return Rect; + +})); + +/** + * Packer + * bin-packing algorithm + */ + +( function( window, factory ) { + // universal module definition + /* jshint strict: false */ /* globals define, module, require */ + if ( typeof define == 'function' && define.amd ) { + // AMD + define( 'packery/packer',[ './rect' ], factory ); + } else if ( typeof module == 'object' && module.exports ) { + // CommonJS + module.exports = factory( + require('./rect') + ); + } else { + // browser global + var Packery = window.Packery = window.Packery || {}; + Packery.Packer = factory( Packery.Rect ); + } + +}( window, function factory( Rect ) { +'use strict'; + +// -------------------------- Packer -------------------------- // + +/** + * @param {Number} width + * @param {Number} height + * @param {String} sortDirection + * topLeft for vertical, leftTop for horizontal + */ +function Packer( width, height, sortDirection ) { + this.width = width || 0; + this.height = height || 0; + this.sortDirection = sortDirection || 'downwardLeftToRight'; + + this.reset(); +} + +var proto = Packer.prototype; + +proto.reset = function() { + this.spaces = []; + + var initialSpace = new Rect({ + x: 0, + y: 0, + width: this.width, + height: this.height + }); + + this.spaces.push( initialSpace ); + // set sorter + this.sorter = sorters[ this.sortDirection ] || sorters.downwardLeftToRight; +}; + +// change x and y of rect to fit with in Packer's available spaces +proto.pack = function( rect ) { + for ( var i=0; i < this.spaces.length; i++ ) { + var space = this.spaces[i]; + if ( space.canFit( rect ) ) { + this.placeInSpace( rect, space ); + break; + } + } +}; + +proto.columnPack = function( rect ) { + for ( var i=0; i < this.spaces.length; i++ ) { + var space = this.spaces[i]; + var canFitInSpaceColumn = space.x <= rect.x && + space.x + space.width >= rect.x + rect.width && + space.height >= rect.height - 0.01; // fudge number for rounding error + if ( canFitInSpaceColumn ) { + rect.y = space.y; + this.placed( rect ); + break; + } + } +}; + +proto.rowPack = function( rect ) { + for ( var i=0; i < this.spaces.length; i++ ) { + var space = this.spaces[i]; + var canFitInSpaceRow = space.y <= rect.y && + space.y + space.height >= rect.y + rect.height && + space.width >= rect.width - 0.01; // fudge number for rounding error + if ( canFitInSpaceRow ) { + rect.x = space.x; + this.placed( rect ); + break; + } + } +}; + +proto.placeInSpace = function( rect, space ) { + // place rect in space + rect.x = space.x; + rect.y = space.y; + + this.placed( rect ); +}; + +// update spaces with placed rect +proto.placed = function( rect ) { + // update spaces + var revisedSpaces = []; + for ( var i=0; i < this.spaces.length; i++ ) { + var space = this.spaces[i]; + var newSpaces = space.getMaximalFreeRects( rect ); + // add either the original space or the new spaces to the revised spaces + if ( newSpaces ) { + revisedSpaces.push.apply( revisedSpaces, newSpaces ); + } else { + revisedSpaces.push( space ); + } + } + + this.spaces = revisedSpaces; + + this.mergeSortSpaces(); +}; + +proto.mergeSortSpaces = function() { + // remove redundant spaces + Packer.mergeRects( this.spaces ); + this.spaces.sort( this.sorter ); +}; + +// add a space back +proto.addSpace = function( rect ) { + this.spaces.push( rect ); + this.mergeSortSpaces(); +}; + +// -------------------------- utility functions -------------------------- // + +/** + * Remove redundant rectangle from array of rectangles + * @param {Array} rects: an array of Rects + * @returns {Array} rects: an array of Rects +**/ +Packer.mergeRects = function( rects ) { + var i = 0; + var rect = rects[i]; + + rectLoop: + while ( rect ) { + var j = 0; + var compareRect = rects[ i + j ]; + + while ( compareRect ) { + if ( compareRect == rect ) { + j++; // next + } else if ( compareRect.contains( rect ) ) { + // remove rect + rects.splice( i, 1 ); + rect = rects[i]; // set next rect + continue rectLoop; // bail on compareLoop + } else if ( rect.contains( compareRect ) ) { + // remove compareRect + rects.splice( i + j, 1 ); + } else { + j++; + } + compareRect = rects[ i + j ]; // set next compareRect + } + i++; + rect = rects[i]; + } + + return rects; +}; + + +// -------------------------- sorters -------------------------- // + +// functions for sorting rects in order +var sorters = { + // top down, then left to right + downwardLeftToRight: function( a, b ) { + return a.y - b.y || a.x - b.x; + }, + // left to right, then top down + rightwardTopToBottom: function( a, b ) { + return a.x - b.x || a.y - b.y; + } +}; + + +// -------------------------- -------------------------- // + +return Packer; + +})); + +/** + * Packery Item Element +**/ + +( function( window, factory ) { + // universal module definition + /* jshint strict: false */ /* globals define, module, require */ + if ( typeof define == 'function' && define.amd ) { + // AMD + define( 'packery/item',[ + 'outlayer/outlayer', + './rect' + ], + factory ); + } else if ( typeof module == 'object' && module.exports ) { + // CommonJS + module.exports = factory( + require('outlayer'), + require('./rect') + ); + } else { + // browser global + window.Packery.Item = factory( + window.Outlayer, + window.Packery.Rect + ); + } + +}( window, function factory( Outlayer, Rect ) { +'use strict'; + +// -------------------------- Item -------------------------- // + +var docElemStyle = document.documentElement.style; + +var transformProperty = typeof docElemStyle.transform == 'string' ? + 'transform' : 'WebkitTransform'; + +// sub-class Item +var Item = function PackeryItem() { + Outlayer.Item.apply( this, arguments ); +}; + +var proto = Item.prototype = Object.create( Outlayer.Item.prototype ); + +var __create = proto._create; +proto._create = function() { + // call default _create logic + __create.call( this ); + this.rect = new Rect(); +}; + +var _moveTo = proto.moveTo; +proto.moveTo = function( x, y ) { + // don't shift 1px while dragging + var dx = Math.abs( this.position.x - x ); + var dy = Math.abs( this.position.y - y ); + + var canHackGoTo = this.layout.dragItemCount && !this.isPlacing && + !this.isTransitioning && dx < 1 && dy < 1; + if ( canHackGoTo ) { + this.goTo( x, y ); + return; + } + _moveTo.apply( this, arguments ); +}; + +// -------------------------- placing -------------------------- // + +proto.enablePlacing = function() { + this.removeTransitionStyles(); + // remove transform property from transition + if ( this.isTransitioning && transformProperty ) { + this.element.style[ transformProperty ] = 'none'; + } + this.isTransitioning = false; + this.getSize(); + this.layout._setRectSize( this.element, this.rect ); + this.isPlacing = true; +}; + +proto.disablePlacing = function() { + this.isPlacing = false; +}; + +// ----- ----- // + +// remove element from DOM +proto.removeElem = function() { + this.element.parentNode.removeChild( this.element ); + // add space back to packer + this.layout.packer.addSpace( this.rect ); + this.emitEvent( 'remove', [ this ] ); +}; + +// ----- dropPlaceholder ----- // + +proto.showDropPlaceholder = function() { + var dropPlaceholder = this.dropPlaceholder; + if ( !dropPlaceholder ) { + // create dropPlaceholder + dropPlaceholder = this.dropPlaceholder = document.createElement('div'); + dropPlaceholder.className = 'packery-drop-placeholder'; + dropPlaceholder.style.position = 'absolute'; + } + + dropPlaceholder.style.width = this.size.width + 'px'; + dropPlaceholder.style.height = this.size.height + 'px'; + this.positionDropPlaceholder(); + this.layout.element.appendChild( dropPlaceholder ); +}; + +proto.positionDropPlaceholder = function() { + this.dropPlaceholder.style[ transformProperty ] = 'translate(' + + this.rect.x + 'px, ' + this.rect.y + 'px)'; +}; + +proto.hideDropPlaceholder = function() { + this.layout.element.removeChild( this.dropPlaceholder ); +}; + +// ----- ----- // + +return Item; + +})); + +/*! + * Packery v2.0.0 + * Gapless, draggable grid layouts + * + * Licensed GPLv3 for open source use + * or Packery Commercial License for commercial use + * + * http://packery.metafizzy.co + * Copyright 2016 Metafizzy + */ + +( function( window, factory ) { + // universal module definition + /* jshint strict: false */ /* globals define, module, require */ + if ( typeof define == 'function' && define.amd ) { + // AMD + define( [ + 'get-size/get-size', + 'outlayer/outlayer', + './rect', + './packer', + './item' + ], + factory ); + } else if ( typeof module == 'object' && module.exports ) { + // CommonJS + module.exports = factory( + require('get-size'), + require('outlayer'), + require('./rect'), + require('./packer'), + require('./item') + ); + } else { + // browser global + window.Packery = factory( + window.getSize, + window.Outlayer, + window.Packery.Rect, + window.Packery.Packer, + window.Packery.Item + ); + } + +}( window, function factory( getSize, Outlayer, Rect, Packer, Item ) { +'use strict'; + +// ----- Rect ----- // + +// allow for pixel rounding errors IE8-IE11 & Firefox; #227 +Rect.prototype.canFit = function( rect ) { + return this.width >= rect.width - 1 && this.height >= rect.height - 1; +}; + +// -------------------------- Packery -------------------------- // + +// create an Outlayer layout class +var Packery = Outlayer.create('packery'); +Packery.Item = Item; + +var proto = Packery.prototype; + +// get JSON-friendly data for items positions +Packery.prototype.getShiftPositions = function( attrName ) { + attrName = attrName || 'id'; + var _this = this; + return this.items.map( function( item ) { + //console.log ("x for "+item.element.getAttribute(attrName)+" is " + item.rect.x); + return { + attr: item.element.getAttribute( attrName ), + size: item.element.getAttribute ("data-item-size"), + display: item.element.getAttribute ("data-item-listdisplay"), + x: item.rect.x / _this.packer.width, + y: item.rect.y / _this.packer.height + } + }); +}; + +// get JSON-friendly data for items positions +Packery.prototype.EHgetShiftPositions = function( attrName ) { + attrName = attrName || 'id'; + var _this = this; + return this.items.map( function( item ) { + return { + attr: item.element.getAttribute( attrName ), + size: item.element.getAttribute ("eh-data-item-size"), + display: item.element.getAttribute ("eh-data-item-listdisplay"), + x: item.rect.x / _this.packer.width, + y: item.rect.y / _this.packer.height + } + }); +}; +Packery.prototype.initShiftLayout = function( positions, attr ) { + if ( !positions ) { + // if no initial positions, run packery layout + this.layout(); + return; + } + // parse string to JSON + if ( typeof positions == 'string' ) { + try { + positions = JSON.parse( positions ); + } catch( error ) { + console.error( 'JSON parse error: ' + error ); + this.layout(); + return; + } + } + + attr = attr || 'id'; // default to id attribute + this._resetLayout(); + // set item order and horizontal position from saved positions + this.items = positions.map( function( itemPosition ) { + var selector = '[' + attr + '="' + itemPosition.attr + '"]' + var itemElem = this.element.querySelector( selector ); + var item = this.getItem( itemElem ); + item.rect.x = itemPosition.x * this.packer.width; + //item.rect.x = Math.ceil(itemPosition.x * this.packer.width); + item.rect.y = itemPosition.y * this.packer.height; + //console.log ("X computed as " + item.rect.x); + return item; + }, this ); + this.shiftLayout(); +}; + +proto._create = function() { + // call super + Outlayer.prototype._create.call( this ); + + // initial properties + this.packer = new Packer(); + // packer for drop targets + this.shiftPacker = new Packer(); + this.isEnabled = true; + + this.dragItemCount = 0; + + // create drag handlers + var _this = this; + this.handleDraggabilly = { + dragStart: function() { + _this.itemDragStart( this.element ); + }, + dragMove: function() { + _this.itemDragMove( this.element, this.position.x, this.position.y ); + }, + dragEnd: function() { + _this.itemDragEnd( this.element ); + } + }; + + this.handleUIDraggable = { + start: function handleUIDraggableStart( event, ui ) { + // HTML5 may trigger dragstart, dismiss HTML5 dragging + if ( !ui ) { + return; + } + _this.itemDragStart( event.currentTarget ); + }, + drag: function handleUIDraggableDrag( event, ui ) { + if ( !ui ) { + return; + } + _this.itemDragMove( event.currentTarget, ui.position.left, ui.position.top ); + }, + stop: function handleUIDraggableStop( event, ui ) { + if ( !ui ) { + return; + } + _this.itemDragEnd( event.currentTarget ); + } + }; + +}; + + +// ----- init & layout ----- // + +/** + * logic before any new layout + */ +proto._resetLayout = function() { + this.getSize(); + + this._getMeasurements(); + + // reset packer + var width, height, sortDirection; + // packer settings, if horizontal or vertical + if ( this._getOption('horizontal') ) { + width = Infinity; + height = this.size.innerHeight + this.gutter; + sortDirection = 'rightwardTopToBottom'; + } else { + width = this.size.innerWidth + this.gutter; + height = Infinity; + sortDirection = 'downwardLeftToRight'; + } + + this.packer.width = this.shiftPacker.width = width; + this.packer.height = this.shiftPacker.height = height; + this.packer.sortDirection = this.shiftPacker.sortDirection = sortDirection; + + this.packer.reset(); + + // layout + this.maxY = 0; + this.maxX = 0; +}; + +/** + * update columnWidth, rowHeight, & gutter + * @private + */ +proto._getMeasurements = function() { + this._getMeasurement( 'columnWidth', 'width' ); + this._getMeasurement( 'rowHeight', 'height' ); + this._getMeasurement( 'gutter', 'width' ); +}; + +proto._getItemLayoutPosition = function( item ) { + this._setRectSize( item.element, item.rect ); + if ( this.isShifting || this.dragItemCount > 0 ) { + var packMethod = this._getPackMethod(); + this.packer[ packMethod ]( item.rect ); + } else { + this.packer.pack( item.rect ); + } + + this._setMaxXY( item.rect ); + return item.rect; +}; + +proto.shiftLayout = function() { + this.isShifting = true; + this.layout(); + delete this.isShifting; +}; + +proto._getPackMethod = function() { + return this._getOption('horizontal') ? 'rowPack' : 'columnPack'; +}; + + +/** + * set max X and Y value, for size of container + * @param {Packery.Rect} rect + * @private + */ +proto._setMaxXY = function( rect ) { + this.maxX = Math.max( rect.x + rect.width, this.maxX ); + this.maxY = Math.max( rect.y + rect.height, this.maxY ); +}; + +/** + * set the width and height of a rect, applying columnWidth and rowHeight + * @param {Element} elem + * @param {Packery.Rect} rect + */ +proto._setRectSize = function( elem, rect ) { + var size = getSize( elem ); + var w = size.outerWidth; + var h = size.outerHeight; + // size for columnWidth and rowHeight, if available + // only check if size is non-zero, #177 + if ( w || h ) { + w = this._applyGridGutter( w, this.columnWidth ); + h = this._applyGridGutter( h, this.rowHeight ); + } + // rect must fit in packer + rect.width = Math.min( w, this.packer.width ); + rect.height = Math.min( h, this.packer.height ); +}; + +/** + * fits item to columnWidth/rowHeight and adds gutter + * @param {Number} measurement - item width or height + * @param {Number} gridSize - columnWidth or rowHeight + * @returns measurement + */ +proto._applyGridGutter = function( measurement, gridSize ) { + // just add gutter if no gridSize + if ( !gridSize ) { + return measurement + this.gutter; + } + gridSize += this.gutter; + // fit item to columnWidth/rowHeight + var remainder = measurement % gridSize; + var mathMethod = remainder && remainder < 1 ? 'round' : 'ceil'; + measurement = Math[ mathMethod ]( measurement / gridSize ) * gridSize; + return measurement; +}; + +proto._getContainerSize = function() { + if ( this._getOption('horizontal') ) { + return { + width: this.maxX - this.gutter + }; + } else { + return { + height: this.maxY - this.gutter + }; + } +}; + + +// -------------------------- stamp -------------------------- // + +/** + * makes space for element + * @param {Element} elem + */ +proto._manageStamp = function( elem ) { + + var item = this.getItem( elem ); + var rect; + if ( item && item.isPlacing ) { + rect = item.rect; + } else { + var offset = this._getElementOffset( elem ); + rect = new Rect({ + x: this._getOption('originLeft') ? offset.left : offset.right, + y: this._getOption('originTop') ? offset.top : offset.bottom + }); + } + + this._setRectSize( elem, rect ); + // save its space in the packer + this.packer.placed( rect ); + this._setMaxXY( rect ); +}; + +// -------------------------- methods -------------------------- // + +function verticalSorter( a, b ) { + return a.position.y - b.position.y || a.position.x - b.position.x; +} + +function horizontalSorter( a, b ) { + return a.position.x - b.position.x || a.position.y - b.position.y; +} + +proto.sortItemsByPosition = function() { + var sorter = this._getOption('horizontal') ? horizontalSorter : verticalSorter; + this.items.sort( sorter ); +}; + +/** + * Fit item element in its current position + * Packery will position elements around it + * useful for expanding elements + * + * @param {Element} elem + * @param {Number} x - horizontal destination position, optional + * @param {Number} y - vertical destination position, optional + */ +proto.fit = function( elem, x, y ) { + var item = this.getItem( elem ); + if ( !item ) { + return; + } + + // stamp item to get it out of layout + this.stamp( item.element ); + // set placing flag + item.enablePlacing(); + this.updateShiftTargets( item ); + // fall back to current position for fitting + x = x === undefined ? item.rect.x: x; + y = y === undefined ? item.rect.y: y; + // position it best at its destination + this.shift( item, x, y ); + this._bindFitEvents( item ); + item.moveTo( item.rect.x, item.rect.y ); + // layout everything else + this.shiftLayout(); + // return back to regularly scheduled programming + this.unstamp( item.element ); + this.sortItemsByPosition(); + item.disablePlacing(); +}; + +/** + * emit event when item is fit and other items are laid out + * @param {Packery.Item} item + * @private + */ +proto._bindFitEvents = function( item ) { + var _this = this; + var ticks = 0; + function onLayout() { + ticks++; + if ( ticks != 2 ) { + return; + } + _this.dispatchEvent( 'fitComplete', null, [ item ] ); + } + // when item is laid out + item.once( 'layout', onLayout ); + // when all items are laid out + this.once( 'layoutComplete', onLayout ); +}; + +// -------------------------- resize -------------------------- // + +// debounced, layout on resize +proto.resize = function(force) { + // don't trigger if size did not change + // or if resize was unbound. See #285, outlayer#9 + if ( !force && (!this.isResizeBound || !this.needsResizeLayout()) ) { + return; + } + + if ( this.options.shiftPercentResize ) { + this.resizeShiftPercentLayout(); + } else { + this.layout(); + } +}; + +/** + * check if layout is needed post layout + * @returns Boolean + */ +proto.needsResizeLayout = function() { + var size = getSize( this.element ); + var innerSize = this._getOption('horizontal') ? 'innerHeight' : 'innerWidth'; + return size[ innerSize ] != this.size[ innerSize ]; +}; + +proto.resizeShiftPercentLayout = function() { + var items = this._getItemsForLayout( this.items ); + + var isHorizontal = this._getOption('horizontal'); + var coord = isHorizontal ? 'y' : 'x'; + var measure = isHorizontal ? 'height' : 'width'; + var segmentName = isHorizontal ? 'rowHeight' : 'columnWidth'; + var innerSize = isHorizontal ? 'innerHeight' : 'innerWidth'; + + // proportional re-align items + var previousSegment = this[ segmentName ]; + previousSegment = previousSegment && previousSegment + this.gutter; + + if ( previousSegment ) { + this._getMeasurements(); + var currentSegment = this[ segmentName ] + this.gutter; + items.forEach( function( item ) { + var seg = Math.round( item.rect[ coord ] / previousSegment ); + item.rect[ coord ] = seg * currentSegment; + }); + } else { + var currentSize = getSize( this.element )[ innerSize ] + this.gutter; + var previousSize = this.packer[ measure ]; + items.forEach( function( item ) { + item.rect[ coord ] = ( item.rect[ coord ] / previousSize ) * currentSize; + }); + } + + this.shiftLayout(); +}; + +// -------------------------- drag -------------------------- // + +/** + * handle an item drag start event + * @param {Element} elem + */ +proto.itemDragStart = function( elem ) { + if ( !this.isEnabled ) { + return; + } + this.stamp( elem ); + // this.ignore( elem ); + var item = this.getItem( elem ); + if ( !item ) { + return; + } + + item.enablePlacing(); + item.showDropPlaceholder(); + this.dragItemCount++; + this.updateShiftTargets( item ); +}; + +proto.updateShiftTargets = function( dropItem ) { + this.shiftPacker.reset(); + + // pack stamps + this._getBoundingRect(); + var isOriginLeft = this._getOption('originLeft'); + var isOriginTop = this._getOption('originTop'); + this.stamps.forEach( function( stamp ) { + // ignore dragged item + var item = this.getItem( stamp ); + if ( item && item.isPlacing ) { + return; + } + var offset = this._getElementOffset( stamp ); + var rect = new Rect({ + x: isOriginLeft ? offset.left : offset.right, + y: isOriginTop ? offset.top : offset.bottom + }); + this._setRectSize( stamp, rect ); + // save its space in the packer + this.shiftPacker.placed( rect ); + }, this ); + + // reset shiftTargets + var isHorizontal = this._getOption('horizontal'); + var segmentName = isHorizontal ? 'rowHeight' : 'columnWidth'; + var measure = isHorizontal ? 'height' : 'width'; + + this.shiftTargetKeys = []; + this.shiftTargets = []; + var boundsSize; + var segment = this[ segmentName ]; + segment = segment && segment + this.gutter; + + if ( segment ) { + var segmentSpan = Math.ceil( dropItem.rect[ measure ] / segment ); + var segs = Math.floor( ( this.shiftPacker[ measure ] + this.gutter ) / segment ); + boundsSize = ( segs - segmentSpan ) * segment; + // add targets on top + for ( var i=0; i < segs; i++ ) { + this._addShiftTarget( i * segment, 0, boundsSize ); + } + } else { + boundsSize = ( this.shiftPacker[ measure ] + this.gutter ) - dropItem.rect[ measure ]; + this._addShiftTarget( 0, 0, boundsSize ); + } + + // pack each item to measure where shiftTargets are + var items = this._getItemsForLayout( this.items ); + var packMethod = this._getPackMethod(); + items.forEach( function( item ) { + var rect = item.rect; + this._setRectSize( item.element, rect ); + this.shiftPacker[ packMethod ]( rect ); + + // add top left corner + this._addShiftTarget( rect.x, rect.y, boundsSize ); + // add bottom left / top right corner + var cornerX = isHorizontal ? rect.x + rect.width : rect.x; + var cornerY = isHorizontal ? rect.y : rect.y + rect.height; + this._addShiftTarget( cornerX, cornerY, boundsSize ); + + if ( segment ) { + // add targets for each column on bottom / row on right + var segSpan = Math.round( rect[ measure ] / segment ); + for ( var i=1; i < segSpan; i++ ) { + var segX = isHorizontal ? cornerX : rect.x + segment * i; + var segY = isHorizontal ? rect.y + segment * i : cornerY; + this._addShiftTarget( segX, segY, boundsSize ); + } + } + }, this ); + +}; + +proto._addShiftTarget = function( x, y, boundsSize ) { + var checkCoord = this._getOption('horizontal') ? y : x; + if ( checkCoord !== 0 && checkCoord > boundsSize ) { + return; + } + // create string for a key, easier to keep track of what targets + var key = x + ',' + y; + var hasKey = this.shiftTargetKeys.indexOf( key ) != -1; + if ( hasKey ) { + return; + } + this.shiftTargetKeys.push( key ); + this.shiftTargets.push({ x: x, y: y }); +}; + +// -------------------------- drop -------------------------- // + +proto.shift = function( item, x, y ) { + var shiftPosition; + var minDistance = Infinity; + var position = { x: x, y: y }; + this.shiftTargets.forEach( function( target ) { + var distance = getDistance( target, position ); + if ( distance < minDistance ) { + shiftPosition = target; + minDistance = distance; + } + }); + item.rect.x = shiftPosition.x; + item.rect.y = shiftPosition.y; +}; + +function getDistance( a, b ) { + var dx = b.x - a.x; + var dy = b.y - a.y; + return Math.sqrt( dx * dx + dy * dy ); +} + +// -------------------------- drag move -------------------------- // + +var DRAG_THROTTLE_TIME = 120; + +/** + * handle an item drag move event + * @param {Element} elem + * @param {Number} x - horizontal change in position + * @param {Number} y - vertical change in position + */ +proto.itemDragMove = function( elem, x, y ) { + var item = this.isEnabled && this.getItem( elem ); + if ( !item ) { + return; + } + + x -= this.size.paddingLeft; + y -= this.size.paddingTop; + + var _this = this; + function onDrag() { + _this.shift( item, x, y ); + item.positionDropPlaceholder(); + _this.layout(); + } + + // throttle + var now = new Date(); + if ( this._itemDragTime && now - this._itemDragTime < DRAG_THROTTLE_TIME ) { + clearTimeout( this.dragTimeout ); + this.dragTimeout = setTimeout( onDrag, DRAG_THROTTLE_TIME ); + } else { + onDrag(); + this._itemDragTime = now; + } +}; + +// -------------------------- drag end -------------------------- // + +/** + * handle an item drag end event + * @param {Element} elem + */ +proto.itemDragEnd = function( elem ) { + var item = this.isEnabled && this.getItem( elem ); + if ( !item ) { + return; + } + + clearTimeout( this.dragTimeout ); + item.element.classList.add('is-positioning-post-drag'); + + var completeCount = 0; + var _this = this; + function onDragEndLayoutComplete() { + completeCount++; + if ( completeCount != 2 ) { + return; + } + // reset drag item + item.element.classList.remove('is-positioning-post-drag'); + item.hideDropPlaceholder(); + _this.dispatchEvent( 'dragItemPositioned', null, [ item ] ); + } + + item.once( 'layout', onDragEndLayoutComplete ); + this.once( 'layoutComplete', onDragEndLayoutComplete ); + item.moveTo( item.rect.x, item.rect.y ); + this.layout(); + this.dragItemCount = Math.max( 0, this.dragItemCount - 1 ); + this.sortItemsByPosition(); + item.disablePlacing(); + this.unstamp( item.element ); +}; + +/** + * binds Draggabilly events + * @param {Draggabilly} draggie + */ +proto.bindDraggabillyEvents = function( draggie ) { + this._bindDraggabillyEvents( draggie, 'on' ); +}; + +proto.unbindDraggabillyEvents = function( draggie ) { + this._bindDraggabillyEvents( draggie, 'off' ); +}; + +proto._bindDraggabillyEvents = function( draggie, method ) { + var handlers = this.handleDraggabilly; + draggie[ method ]( 'dragStart', handlers.dragStart ); + draggie[ method ]( 'dragMove', handlers.dragMove ); + draggie[ method ]( 'dragEnd', handlers.dragEnd ); +}; + +/** + * binds jQuery UI Draggable events + * @param {jQuery} $elems + */ +proto.bindUIDraggableEvents = function( $elems ) { + this._bindUIDraggableEvents( $elems, 'on' ); +}; + +proto.unbindUIDraggableEvents = function( $elems ) { + this._bindUIDraggableEvents( $elems, 'off' ); +}; + +proto._bindUIDraggableEvents = function( $elems, method ) { + var handlers = this.handleUIDraggable; + $elems + [ method ]( 'dragstart', handlers.start ) + [ method ]( 'drag', handlers.drag ) + [ method ]( 'dragstop', handlers.stop ); +}; + +// ----- destroy ----- // + +var _destroy = proto.destroy; +proto.destroy = function() { + _destroy.apply( this, arguments ); + // disable flag; prevent drag events from triggering. #72 + this.isEnabled = false; +}; + +// ----- ----- // + +Packery.Rect = Rect; +Packery.Packer = Packer; + +return Packery; + +})); + |
