diff options
Diffstat (limited to 'www/external/packery.pkgd.js')
| -rw-r--r-- | www/external/packery.pkgd.js | 3349 |
1 files changed, 3349 insertions, 0 deletions
diff --git a/www/external/packery.pkgd.js b/www/external/packery.pkgd.js new file mode 100644 index 00000000..c8353909 --- /dev/null +++ b/www/external/packery.pkgd.js @@ -0,0 +1,3349 @@ +/*! + * 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 ); + + 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.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; + +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() { + // don't trigger if size did not change + // or if resize was unbound. See #285, outlayer#9 + if ( !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; + +})); + |
