UNPKG

packery

Version:

Gapless, draggable grid layouts

1,946 lines (1,613 loc) 402 kB
(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){ /** * Packery Item Element **/ ( function( window ) { 'use strict'; // -------------------------- Item -------------------------- // function itemDefinition( getStyleProperty, Outlayer, Rect ) { var transformProperty = getStyleProperty('transform'); // sub-class Item var Item = function PackeryItem() { Outlayer.Item.apply( this, arguments ); }; Item.prototype = new Outlayer.Item(); var protoCreate = Item.prototype._create; Item.prototype._create = function() { // call default _create logic protoCreate.call( this ); this.rect = new Rect(); // rect used for placing, in drag or Packery.fit() this.placeRect = new Rect(); }; // -------------------------- drag -------------------------- // Item.prototype.dragStart = function() { this.getPosition(); this.removeTransitionStyles(); // remove transform property from transition if ( this.isTransitioning && transformProperty ) { this.element.style[ transformProperty ] = 'none'; } this.getSize(); // create place rect, used for position when dragged then dropped // or when positioning this.isPlacing = true; this.needsPositioning = false; this.positionPlaceRect( this.position.x, this.position.y ); this.isTransitioning = false; this.didDrag = false; }; /** * handle item when it is dragged * @param {Number} x - horizontal position of dragged item * @param {Number} y - vertical position of dragged item */ Item.prototype.dragMove = function( x, y ) { this.didDrag = true; var packerySize = this.layout.size; x -= packerySize.paddingLeft; y -= packerySize.paddingTop; this.positionPlaceRect( x, y ); }; Item.prototype.dragStop = function() { this.getPosition(); var isDiffX = this.position.x !== this.placeRect.x; var isDiffY = this.position.y !== this.placeRect.y; // set post-drag positioning flag this.needsPositioning = isDiffX || isDiffY; // reset flag this.didDrag = false; }; // -------------------------- placing -------------------------- // /** * position a rect that will occupy space in the packer * @param {Number} x * @param {Number} y * @param {Boolean} isMaxYContained */ Item.prototype.positionPlaceRect = function( x, y, isMaxYOpen ) { this.placeRect.x = this.getPlaceRectCoord( x, true ); this.placeRect.y = this.getPlaceRectCoord( y, false, isMaxYOpen ); }; /** * get x/y coordinate for place rect * @param {Number} coord - x or y * @param {Boolean} isX * @param {Boolean} isMaxOpen - does not limit value to outer bound * @returns {Number} coord - processed x or y */ Item.prototype.getPlaceRectCoord = function( coord, isX, isMaxOpen ) { var measure = isX ? 'Width' : 'Height'; var size = this.size[ 'outer' + measure ]; var segment = this.layout[ isX ? 'columnWidth' : 'rowHeight' ]; var parentSize = this.layout.size[ 'inner' + measure ]; // additional parentSize calculations for Y if ( !isX ) { parentSize = Math.max( parentSize, this.layout.maxY ); // prevent gutter from bumping up height when non-vertical grid if ( !this.layout.rowHeight ) { parentSize -= this.layout.gutter; } } var max; if ( segment ) { segment += this.layout.gutter; // allow for last column to reach the edge parentSize += isX ? this.layout.gutter : 0; // snap to closest segment coord = Math.round( coord / segment ); // contain to outer bound // contain non-growing bound, allow growing bound to grow var mathMethod; if ( this.layout.options.isHorizontal ) { mathMethod = !isX ? 'floor' : 'ceil'; } else { mathMethod = isX ? 'floor' : 'ceil'; } var maxSegments = Math[ mathMethod ]( parentSize / segment ); maxSegments -= Math.ceil( size / segment ); max = maxSegments; } else { max = parentSize - size; } coord = isMaxOpen ? coord : Math.min( coord, max ); coord *= segment || 1; return Math.max( 0, coord ); }; Item.prototype.copyPlaceRectPosition = function() { this.rect.x = this.placeRect.x; this.rect.y = this.placeRect.y; }; // ----- ----- // // remove element from DOM Item.prototype.removeElem = function() { this.element.parentNode.removeChild( this.element ); // add space back to packer this.layout.packer.addSpace( this.rect ); this.emitEvent( 'remove', [ this ] ); }; // ----- ----- // return Item; } // -------------------------- transport -------------------------- // if ( typeof define === 'function' && define.amd ) { // AMD define( [ 'get-style-property/get-style-property', 'outlayer/outlayer', './rect' ], itemDefinition ); } else if ( typeof exports === 'object' ) { // CommonJS module.exports = itemDefinition( require('desandro-get-style-property'), require('outlayer'), require('./rect') ); } else { // browser global window.Packery.Item = itemDefinition( window.getStyleProperty, window.Outlayer, window.Packery.Rect ); } })( window ); },{"./rect":4,"desandro-get-style-property":6,"outlayer":18}],2:[function(require,module,exports){ /** * Packer * bin-packing algorithm */ ( function( window ) { 'use strict'; // -------------------------- Packer -------------------------- // function packerDefinition( Rect ) { /** * @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(); } Packer.prototype.reset = function() { this.spaces = []; this.newSpaces = []; 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 Packer.prototype.pack = function( rect ) { for ( var i=0, len = this.spaces.length; i < len; i++ ) { var space = this.spaces[i]; if ( space.canFit( rect ) ) { this.placeInSpace( rect, space ); break; } } }; Packer.prototype.placeInSpace = function( rect, space ) { // place rect in space rect.x = space.x; rect.y = space.y; this.placed( rect ); }; // update spaces with placed rect Packer.prototype.placed = function( rect ) { // update spaces var revisedSpaces = []; for ( var i=0, len = this.spaces.length; i < len; 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(); }; Packer.prototype.mergeSortSpaces = function() { // remove redundant spaces Packer.mergeRects( this.spaces ); this.spaces.sort( this.sorter ); }; // add a space back Packer.prototype.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 ) { for ( var i=0, len = rects.length; i < len; i++ ) { var rect = rects[i]; // skip over this rect if it was already removed if ( !rect ) { continue; } // clone rects we're testing, remove this rect var compareRects = rects.slice(0); // do not compare with self compareRects.splice( i, 1 ); // compare this rect with others var removedCount = 0; for ( var j=0, jLen = compareRects.length; j < jLen; j++ ) { var compareRect = compareRects[j]; // if this rect contains another, // remove that rect from test collection var indexAdjust = i > j ? 0 : 1; if ( rect.contains( compareRect ) ) { // console.log( 'current test rects:' + testRects.length, testRects ); // console.log( i, j, indexAdjust, rect, compareRect ); rects.splice( j + indexAdjust - removedCount, 1 ); removedCount++; } } } 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; } // -------------------------- transport -------------------------- // if ( typeof define === 'function' && define.amd ) { // AMD define( [ './rect' ], packerDefinition ); } else if ( typeof exports === 'object' ) { // CommonJS module.exports = packerDefinition( require('./rect') ); } else { // browser global var Packery = window.Packery = window.Packery || {}; Packery.Packer = packerDefinition( Packery.Rect ); } })( window ); },{"./rect":4}],3:[function(require,module,exports){ /*! * Packery v1.2.4 * bin-packing layout library * http://packery.metafizzy.co * * Commercial use requires one-time purchase of a commercial license * http://packery.metafizzy.co/license.html * * Non-commercial use is licensed under the GPL v3 License * * Copyright 2014 Metafizzy */ ( function( window ) { 'use strict'; // -------------------------- Packery -------------------------- // // used for AMD definition and requires function packeryDefinition( classie, getSize, Outlayer, Rect, Packer, Item ) { // create an Outlayer layout class var Packery = Outlayer.create('packery'); Packery.Item = Item; Packery.prototype._create = function() { // call super Outlayer.prototype._create.call( this ); // initial properties this.packer = new Packer(); // Left over from v1.0 this.stamp( this.options.stamped ); // create drag handlers var _this = this; this.handleDraggabilly = { dragStart: function( draggie ) { _this.itemDragStart( draggie.element ); }, dragMove: function( draggie ) { _this.itemDragMove( draggie.element, draggie.position.x, draggie.position.y ); }, dragEnd: function( draggie ) { _this.itemDragEnd( draggie.element ); } }; this.handleUIDraggable = { start: function handleUIDraggableStart( event ) { _this.itemDragStart( event.currentTarget ); }, drag: function handleUIDraggableDrag( event, ui ) { _this.itemDragMove( event.currentTarget, ui.position.left, ui.position.top ); }, stop: function handleUIDraggableStop( event ) { _this.itemDragEnd( event.currentTarget ); } }; }; // ----- init & layout ----- // /** * logic before any new layout */ Packery.prototype._resetLayout = function() { this.getSize(); this._getMeasurements(); // reset packer var packer = this.packer; // packer settings, if horizontal or vertical if ( this.options.isHorizontal ) { packer.width = Number.POSITIVE_INFINITY; packer.height = this.size.innerHeight + this.gutter; packer.sortDirection = 'rightwardTopToBottom'; } else { packer.width = this.size.innerWidth + this.gutter; packer.height = Number.POSITIVE_INFINITY; packer.sortDirection = 'downwardLeftToRight'; } packer.reset(); // layout this.maxY = 0; this.maxX = 0; }; /** * update columnWidth, rowHeight, & gutter * @private */ Packery.prototype._getMeasurements = function() { this._getMeasurement( 'columnWidth', 'width' ); this._getMeasurement( 'rowHeight', 'height' ); this._getMeasurement( 'gutter', 'width' ); }; Packery.prototype._getItemLayoutPosition = function( item ) { this._packItem( item ); return item.rect; }; /** * layout item in packer * @param {Packery.Item} item */ Packery.prototype._packItem = function( item ) { this._setRectSize( item.element, item.rect ); // pack the rect in the packer this.packer.pack( item.rect ); this._setMaxXY( item.rect ); }; /** * set max X and Y value, for size of container * @param {Packery.Rect} rect * @private */ Packery.prototype._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 */ Packery.prototype._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 ) { var colW = this.columnWidth + this.gutter; var rowH = this.rowHeight + this.gutter; w = this.columnWidth ? Math.ceil( w / colW ) * colW : w + this.gutter; h = this.rowHeight ? Math.ceil( h / rowH ) * rowH : h + this.gutter; } // rect must fit in packer rect.width = Math.min( w, this.packer.width ); rect.height = Math.min( h, this.packer.height ); }; Packery.prototype._getContainerSize = function() { if ( this.options.isHorizontal ) { return { width: this.maxX - this.gutter }; } else { return { height: this.maxY - this.gutter }; } }; // -------------------------- stamp -------------------------- // /** * makes space for element * @param {Element} elem */ Packery.prototype._manageStamp = function( elem ) { var item = this.getItem( elem ); var rect; if ( item && item.isPlacing ) { rect = item.placeRect; } else { var offset = this._getElementOffset( elem ); rect = new Rect({ x: this.options.isOriginLeft ? offset.left : offset.right, y: this.options.isOriginTop ? 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; } Packery.prototype.sortItemsByPosition = function() { var sorter = this.options.isHorizontal ? 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 */ Packery.prototype.fit = function( elem, x, y ) { var item = this.getItem( elem ); if ( !item ) { return; } // prepare internal properties this._getMeasurements(); // stamp item to get it out of layout this.stamp( item.element ); // required for positionPlaceRect item.getSize(); // set placing flag item.isPlacing = true; // 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 item.positionPlaceRect( x, y, true ); this._bindFitEvents( item ); item.moveTo( item.placeRect.x, item.placeRect.y ); // layout everything else this.layout(); // return back to regularly scheduled programming this.unstamp( item.element ); this.sortItemsByPosition(); // un set placing flag, back to normal item.isPlacing = false; // copy place rect position item.copyPlaceRectPosition(); }; /** * emit event when item is fit and other items are laid out * @param {Packery.Item} item * @private */ Packery.prototype._bindFitEvents = function( item ) { var _this = this; var ticks = 0; function tick() { ticks++; if ( ticks !== 2 ) { return; } _this.emitEvent( 'fitComplete', [ _this, item ] ); } // when item is laid out item.on( 'layout', function() { tick(); return true; }); // when all items are laid out this.on( 'layoutComplete', function() { tick(); return true; }); }; // -------------------------- resize -------------------------- // // debounced, layout on resize Packery.prototype.resize = function() { // don't trigger if size did not change 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; var innerSize = this.options.isHorizontal ? 'innerHeight' : 'innerWidth'; if ( hasSizes && size[ innerSize ] === this.size[ innerSize ] ) { return; } this.layout(); }; // -------------------------- drag -------------------------- // /** * handle an item drag start event * @param {Element} elem */ Packery.prototype.itemDragStart = function( elem ) { this.stamp( elem ); var item = this.getItem( elem ); if ( item ) { item.dragStart(); } }; /** * handle an item drag move event * @param {Element} elem * @param {Number} x - horizontal change in position * @param {Number} y - vertical change in position */ Packery.prototype.itemDragMove = function( elem, x, y ) { var item = this.getItem( elem ); if ( item ) { item.dragMove( x, y ); } // debounce var _this = this; // debounce triggering layout function delayed() { _this.layout(); delete _this.dragTimeout; } this.clearDragTimeout(); this.dragTimeout = setTimeout( delayed, 40 ); }; Packery.prototype.clearDragTimeout = function() { if ( this.dragTimeout ) { clearTimeout( this.dragTimeout ); } }; /** * handle an item drag end event * @param {Element} elem */ Packery.prototype.itemDragEnd = function( elem ) { var item = this.getItem( elem ); var itemDidDrag; if ( item ) { itemDidDrag = item.didDrag; item.dragStop(); } // if elem didn't move, or if it doesn't need positioning // unignore and unstamp and call it a day if ( !item || ( !itemDidDrag && !item.needsPositioning ) ) { this.unstamp( elem ); return; } // procced with dragged item item.element.classList.add('is-positioning-post-drag' ); // save this var, as it could get reset in dragStart var onLayoutComplete = this._getDragEndLayoutComplete( elem, item ); if ( item.needsPositioning ) { item.on( 'layout', onLayoutComplete ); item.moveTo( item.placeRect.x, item.placeRect.y ); } else if ( item ) { // item didn't need placement item.copyPlaceRectPosition(); } this.clearDragTimeout(); this.on( 'layoutComplete', onLayoutComplete ); this.layout(); }; /** * get drag end callback * @param {Element} elem * @param {Packery.Item} item * @returns {Function} onLayoutComplete */ Packery.prototype._getDragEndLayoutComplete = function( elem, item ) { var itemNeedsPositioning = item && item.needsPositioning; var completeCount = 0; var asyncCount = itemNeedsPositioning ? 2 : 1; var _this = this; return function onLayoutComplete() { completeCount++; // don't proceed if not complete if ( completeCount !== asyncCount ) { return true; } // reset item if ( item ) { item.element.classList.remove('is-positioning-post-drag' ); item.isPlacing = false; item.copyPlaceRectPosition(); } _this.unstamp( elem ); // only sort when item moved _this.sortItemsByPosition(); // emit item drag event now that everything is done if ( itemNeedsPositioning ) { _this.emitEvent( 'dragItemPositioned', [ _this, item ] ); } // listen once return true; }; }; /** * binds Draggabilly events * @param {Draggabilly} draggie */ Packery.prototype.bindDraggabillyEvents = function( draggie ) { draggie.on( 'dragStart', this.handleDraggabilly.dragStart ); draggie.on( 'dragMove', this.handleDraggabilly.dragMove ); draggie.on( 'dragEnd', this.handleDraggabilly.dragEnd ); }; /** * binds jQuery UI Draggable events * @param {jQuery} $elems */ Packery.prototype.bindUIDraggableEvents = function( $elems ) { $elems .on( 'dragstart', this.handleUIDraggable.start ) .on( 'drag', this.handleUIDraggable.drag ) .on( 'dragstop', this.handleUIDraggable.stop ); }; Packery.Rect = Rect; Packery.Packer = Packer; return Packery; } // -------------------------- transport -------------------------- // if ( typeof define === 'function' && define.amd ) { // AMD define( [ 'classie/classie', 'get-size/get-size', 'outlayer/outlayer', './rect', './packer', './item' ], packeryDefinition ); } else if ( typeof exports === 'object' ) { // CommonJS module.exports = packeryDefinition( require('desandro-classie'), require('get-size'), require('outlayer'), require('./rect'), require('./packer'), require('./item') ); } else { // browser global window.Packery = packeryDefinition( window.classie, window.getSize, window.Outlayer, window.Packery.Rect, window.Packery.Packer, window.Packery.Item ); } })( window ); },{"./item":1,"./packer":2,"./rect":4,"desandro-classie":5,"get-size":10,"outlayer":18}],4:[function(require,module,exports){ /** * Rect * low-level utility class for basic geometry */ ( function( window ) { "use strict"; // -------------------------- Packery -------------------------- // // global namespace var Packery = window.Packery = function() {}; function rectDefinition() { // -------------------------- 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 ]; } } // make available Packery.Rect = Rect; Rect.defaults = { x: 0, y: 0, width: 0, height: 0 }; /** * Determines whether or not this rectangle wholly encloses another rectangle or point. * @param {Rect} rect * @returns {Boolean} **/ Rect.prototype.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} **/ Rect.prototype.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 **/ Rect.prototype.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; }; Rect.prototype.canFit = function( rect ) { return this.width >= rect.width && this.height >= rect.height; }; return Rect; } // -------------------------- transport -------------------------- // if ( typeof define === 'function' && define.amd ) { // AMD define( rectDefinition ); } else if ( typeof exports === 'object' ) { // CommonJS module.exports = rectDefinition(); } else { // browser global window.Packery = window.Packery || {}; window.Packery.Rect = rectDefinition(); } })( window ); },{}],5:[function(require,module,exports){ /*! * classie v1.0.1 * class helper functions * from bonzo https://github.com/ded/bonzo * MIT license * * classie.has( elem, 'my-class' ) -> true/false * elem.classList.add('my-new-class' ) * elem.classList.remove('my-unwanted-class' ) * classie.toggle( elem, 'my-class' ) */ /*jshint browser: true, strict: true, undef: true, unused: true */ /*global define: false, module: false */ ( function( window ) { 'use strict'; // class helper functions from bonzo https://github.com/ded/bonzo function classReg( className ) { return new RegExp("(^|\\s+)" + className + "(\\s+|$)"); } // classList support for class management // altho to be fair, the api sucks because it won't accept multiple classes at once var hasClass, addClass, removeClass; if ( 'classList' in document.documentElement ) { hasClass = function( elem, c ) { return elem.classList.contains( c ); }; addClass = function( elem, c ) { elem.classList.add( c ); }; removeClass = function( elem, c ) { elem.classList.remove( c ); }; } else { hasClass = function( elem, c ) { return classReg( c ).test( elem.className ); }; addClass = function( elem, c ) { if ( !hasClass( elem, c ) ) { elem.className = elem.className + ' ' + c; } }; removeClass = function( elem, c ) { elem.className = elem.className.replace( classReg( c ), ' ' ); }; } function toggleClass( elem, c ) { var fn = hasClass( elem, c ) ? removeClass : addClass; fn( elem, c ); } var classie = { // full names hasClass: hasClass, addClass: addClass, removeClass: removeClass, toggleClass: toggleClass, // short names has: hasClass, add: addClass, remove: removeClass, toggle: toggleClass }; // transport if ( typeof define === 'function' && define.amd ) { // AMD define( classie ); } else if ( typeof exports === 'object' ) { // CommonJS module.exports = classie; } else { // browser global window.classie = classie; } })( window ); },{}],6:[function(require,module,exports){ /*! * getStyleProperty v1.0.4 * original by kangax * http://perfectionkills.com/feature-testing-css-properties/ * MIT license */ /*jshint browser: true, strict: true, undef: true */ /*global define: false, exports: false, module: false */ ( function( window ) { 'use strict'; var prefixes = 'Webkit Moz ms Ms O'.split(' '); var docElemStyle = document.documentElement.style; function getStyleProperty( propName ) { if ( !propName ) { return; } // test standard property first if ( typeof docElemStyle[ propName ] === 'string' ) { return propName; } // capitalize propName = propName.charAt(0).toUpperCase() + propName.slice(1); // test vendor specific properties var prefixed; for ( var i=0, len = prefixes.length; i < len; i++ ) { prefixed = prefixes[i] + propName; if ( typeof docElemStyle[ prefixed ] === 'string' ) { return prefixed; } } } // transport if ( typeof define === 'function' && define.amd ) { // AMD define( function() { return getStyleProperty; }); } else if ( typeof exports === 'object' ) { // CommonJS for Component module.exports = getStyleProperty; } else { // browser global window.getStyleProperty = getStyleProperty; } })( window ); },{}],7:[function(require,module,exports){ /*! * Draggabilly v1.1.1 * Make that shiz draggable * http://draggabilly.desandro.com * MIT license */ ( function( window ) { 'use strict'; // vars var document = window.document; // -------------------------- helpers -------------------------- // // extend objects function extend( a, b ) { for ( var prop in b ) { a[ prop ] = b[ prop ]; } return a; } function noop() {} // ----- get style ----- // var defView = document.defaultView; var getStyle = defView && defView.getComputedStyle ? function( elem ) { return defView.getComputedStyle( elem, null ); } : function( elem ) { return elem.currentStyle; }; // http://stackoverflow.com/a/384380/182183 var isElement = ( typeof HTMLElement === 'object' ) ? function isElementDOM2( obj ) { return obj instanceof HTMLElement; } : function isElementQuirky( obj ) { return obj && typeof obj === 'object' && obj.nodeType === 1 && typeof obj.nodeName === 'string'; }; // -------------------------- requestAnimationFrame -------------------------- // // https://gist.github.com/1866474 var lastTime = 0; var prefixes = 'webkit moz ms o'.split(' '); // get unprefixed rAF and cAF, if present var requestAnimationFrame = window.requestAnimationFrame; var cancelAnimationFrame = window.cancelAnimationFrame; // loop through vendor prefixes and get prefixed rAF and cAF var prefix; for( var i = 0; i < prefixes.length; i++ ) { if ( requestAnimationFrame && cancelAnimationFrame ) { break; } prefix = prefixes[i]; requestAnimationFrame = requestAnimationFrame || window[ prefix + 'RequestAnimationFrame' ]; cancelAnimationFrame = cancelAnimationFrame || window[ prefix + 'CancelAnimationFrame' ] || window[ prefix + 'CancelRequestAnimationFrame' ]; } // fallback to setTimeout and clearTimeout if either request/cancel is not supported if ( !requestAnimationFrame || !cancelAnimationFrame ) { requestAnimationFrame = function( callback ) { var currTime = new Date().getTime(); var timeToCall = Math.max( 0, 16 - ( currTime - lastTime ) ); var id = window.setTimeout( function() { callback( currTime + timeToCall ); }, timeToCall ); lastTime = currTime + timeToCall; return id; }; cancelAnimationFrame = function( id ) { window.clearTimeout( id ); }; } // -------------------------- definition -------------------------- // function draggabillyDefinition( classie, EventEmitter, eventie, getStyleProperty, getSize ) { // -------------------------- support -------------------------- // var transformProperty = getStyleProperty('transform'); // TODO fix quick & dirty check for 3D support var is3d = !!getStyleProperty('perspective'); // -------------------------- -------------------------- // function Draggabilly( element, options ) { // querySelector if string this.element = typeof element === 'string' ? document.querySelector( element ) : element; this.options = extend( {}, this.options ); extend( this.options, options ); this._create(); } // inherit EventEmitter methods extend( Draggabilly.prototype, EventEmitter.prototype ); Draggabilly.prototype.options = { }; Draggabilly.prototype._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 = getStyle( 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 */ Draggabilly.prototype.setHandles = function() { this.handles = this.options.handle ? this.element.querySelectorAll( this.options.handle ) : [ this.element ]; for ( var i=0, len = this.handles.length; i < len; i++ ) { var handle = this.handles[i]; // bind pointer start event if ( window.navigator.pointerEnabled ) { // W3C Pointer Events, IE11. See https://coderwall.com/p/mfreca eventie.bind( handle, 'pointerdown', this ); // disable scrolling on the element handle.style.touchAction = 'none'; } else if ( window.navigator.msPointerEnabled ) { // IE10 Pointer Events eventie.bind( handle, 'MSPointerDown', this ); // disable scrolling on the element handle.style.msTouchAction = 'none'; } else { // listen for both, for devices like Chrome Pixel // which has touch and mouse events eventie.bind( handle, 'mousedown', this ); eventie.bind( handle, 'touchstart', this ); disableImgOndragstart( handle ); } } }; // remove default dragging interaction on all images in IE8 // IE8 does its own drag thing on images, which messes stuff up function noDragStart() { return false; } // TODO replace this with a IE8 test var isIE8 = 'attachEvent' in document.documentElement; // IE8 only var disableImgOndragstart = !isIE8 ? noop : function( handle ) { if ( handle.nodeName === 'IMG' ) { handle.ondragstart = noDragStart; } var images = handle.querySelectorAll('img'); for ( var i=0, len = images.length; i < len; i++ ) { var img = images[i]; img.ondragstart = noDragStart; } }; // get left/top position from style Draggabilly.prototype._getPosition = function() { // properties var style = getStyle( this.element ); var x = parseInt( style.left, 10 ); var y = parseInt( style.top, 10 ); // 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 ); }; // add transform: translate( x, y ) to position Draggabilly.prototype._addTransformPosition = function( style ) { if ( !transformProperty ) { return; } 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 -------------------------- // // trigger handler methods for events Draggabilly.prototype.handleEvent = function( event ) { var method = 'on' + event.type; if ( this[ method ] ) { this[ method ]( event ); } }; // returns the touch that we're keeping track of Draggabilly.prototype.getTouch = function( touches ) { for ( var i=0, len = touches.length; i < len; i++ ) { var touch = touches[i]; if ( touch.identifier === this.pointerIdentifier ) { return touch; } } }; // ----- start event ----- // Draggabilly.prototype.onmousedown = function( event ) { // dismiss clicks from right or middle buttons var button = event.button; if ( button && ( button !== 0 && button !== 1 ) ) { return; } this.dragStart( event, event ); }; Draggabilly.prototype.ontouchstart = function( event ) { // disregard additional touches if ( this.isDragging ) { return; } this.dragStart( event, event.changedTouches[0] ); }; Draggabilly.prototype.onMSPointerDown = Draggabilly.prototype.onpointerdown = function( event ) { // disregard additional touches if ( this.isDragging ) { return; } this.dragStart( event, event ); }; function setPointerPoint( point, pointer ) { point.x = pointer.pageX !== undefined ? pointer.pageX : pointer.clientX; point.y = pointer.pageY !== undefined ? pointer.pageY : pointer.clientY; } // 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' ] }; /** * drag start * @param {Event} event * @param {Event or Touch} pointer */ Draggabilly.prototype.dragStart = function( event, pointer ) { if ( !this.isEnabled ) { return; } if ( event.preventDefault ) { event.preventDefault(); } else { event.returnValue = false; } // 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._getPosition(); this.measureContainment(); // point where drag began setPointerPoint( this.startPoint, pointer ); // 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; // bind move and end events this._bindEvents({ // get proper events to match start event events: postStartEvents[ event.type ], // IE8 needs to be bound to document node: event.preventDefault ? window : document }); this.element.classList.add('is-dragging' ); // reset isDragging flag this.isDragging = true; this.emitEvent( 'dragStart', [ this, event, pointer ] ); // start animation this.animate(); }; Draggabilly.prototype._bindEvents = function( args ) { for ( var i=0, len = args.events.length; i < len; i++ ) { var event = args.events[i]; eventie.bind( args.node, event, this ); } // save these arguments this._boundEvents = args; }; Draggabilly.prototype._unbindEvents = function() { var args = this._boundEvents; // IE8 can trigger dragEnd twice, check for _boundEvents if ( !args || !args.events ) { return; } for ( var i=0, len = args.events.length; i < len; i++ ) { var event = args.events[i]; eventie.unbind( args.node, event, this ); } delete this._boundEvents; }; Draggabilly.prototype.measureContainment = function() { var containment = this.options.containment; if ( !containment ) { return; } this.size = getSize( this.element ); var elemRect = this.element.getBoundingClientRect(); // 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; this.containerSize = getSize( container ); var containerRect = container.getBoundingClientRect(); this.relativeStartPosition = { x: elemRect.left - containerRect.left, y: elemRect.top - containerRect.top }; }; // ----- move event ----- // Draggabilly.prototype.onmousemove = function( event ) { this.dragMove( event, event ); }; Draggabilly.prototype.onMSPointerMove = Draggabilly.prototype.onpointermove = function( event ) { if ( event.pointerId === this.pointerIdentifier ) { this.dragMove( event, event ); } }; Draggabilly.prototype.ontouchmove = function( event ) { var touch = this.getTouch( event.changedTouches ); if ( touch ) { this.dragMove( event, touch ); } }; /** * drag move * @param {Event} event * @param {Event or Touch} pointer */ Draggabilly.prototype.dragMove = function( event, pointer ) { setPointerPoint( this.dragPoint, pointer ); var dragX = this.dragPoint.x - this.startPoint.x; var dragY = this.dragPoint.y - this.startPoint.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.emitEvent( 'dragMove', [ this, event, pointer ] ); }; function applyGrid( value, grid, method ) { method = method || 'round'; return grid ? Math[ method ]( value / grid ) * grid : value; } Draggabilly.prototype.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.containerSize[ measure ] - rel - this.size[ measure ]; max = applyGrid( max, grid, 'floor' ); return Math.min( max, Math.max( min, drag ) ); }; // ----- end event ----- // Draggabilly.prototype.onmouseup = function( event ) { this.dragEnd( event, event ); }; Draggabilly.prototype.onMSPointerUp = Draggabilly.prototype.onpointerup = function( event ) { if ( event.pointerId === this.pointerIdentifier ) { this.dragEnd( event, event ); } }; Draggabilly.prototype.ontouchend = function( event ) { var touch = this.getTouch( event.changedTouches ); if ( touch ) { this.dragEnd( event, touch ); } }; /** * drag end * @param {Event} event * @param {Event or Touch} pointer */ Draggabilly.prototype.dragEnd = function( event, pointer ) { this.isDragging = false; delete this.pointerIdentifier; // use top left position when complete if ( transformProperty ) { this.element.style[ transformProperty ] = ''; this.setLeftTop(); } // remove events this._unbindEvents(); this.element.classList.remove('is-dragging' ); this.emitEvent( 'dragEnd', [ this, event, pointer ] ); }; // ----- cancel event ----- // // coerce to end event Draggabilly.prototype.onMSPointerCancel = Draggabilly.prototype.onpointercancel = function( event ) { if ( event.pointerId === this.pointerIdentifier ) { this.dragEnd( event, event ); } }; Draggabilly.prototype.ontouchcancel = function( event ) { var touch = this.getTouch( event.changedTouches ); this.dragEnd( event, touch ); }; // -------------------------- animation -------------------------- // Draggabilly.prototype.animate = function() { // only render and animate if dragging if ( !this.isDragging ) { return; } this.positionDrag(); var _this = this; requestAnimationFrame( function animateFrame() { _this.animate(); }); }; // transform translate function var translate = is3d ? function( x, y ) { return 'translate3d( ' + x + 'px, ' + y + 'px, 0)'; } : function( x, y ) { return 'translate( ' + x + 'px, ' + y + 'px)'; }; // left/top positioning Draggabilly.prototype.setLeftTop = function() { this.element.style.left = this.position.x + 'px'; this.element.style.top = this.position.y + 'px'; }; Draggabilly.prototype.positionDrag = transformProperty ? function() { // position with transform this.element.style[ transformProperty ] = translate( this.dragPoint.x, this.dragPoint.y ); } : Draggabilly.prototype.setLeftTop; Draggabilly.prototype.enable = function() { this.isEnabled = true; }; Draggabilly.prototype.disable = function() { this.isEnabled = false; if ( this.isDragging ) { this.dragEnd(); } }; return Draggabilly; } // end definition // -------------------------- transport -------------------------- // if ( typeof define === 'function' && define.amd ) { // AMD define( [ 'classie/classie', 'eventEmitter/EventEmitter', 'eventie/eventie', 'get-style-property/get-style-property', 'get-size/get-size' ], draggabillyDefinition ); } else if ( typeof exports === 'object' ) { // CommonJS module.exports = draggabillyDefinition( require('desandro-classie'), require('wolfy87-eventemitter'), require('eventie'), require('desandro-get-style-property'), require('get-size') ); } else { // browser global window.Draggabilly = draggabillyDefinition( window.classie, window.EventEmitter, window.eventie, window.getStyleProperty, window.getSize ); } })( window ); },{"desandro-classie":5,"desandro-get-style-property":6,"eventie":8,"get-size":10,"wolfy87-eventemitter":9}],8:[function(require,module,exports){ /*! * eventie v1.0.5 * event binding helper * eventie.bind( elem, 'click', myFn ) * eventie.unbind( elem, 'click', myFn ) * MIT license */ /*jshint browser: true, undef: true, unused: true */ /*global define: false, module: false */ ( function( window ) { 'use strict'; var docElem = document.documentElement; var bind = function() {}; function getIEEvent( obj ) { var event = window.event; // add event.target event.target = event.target || event.srcElement || obj; return event; } if ( docElem.addEventListener ) { bind = function( obj, type, fn ) { obj.addEventListener( type, fn, false ); }; } else if ( docElem.attachEvent ) { bind = function( obj, type, fn ) { obj[ type + fn ] = fn.handleEvent ? function() { var event = getIEEvent( obj ); fn.handleEvent.call( fn, event ); } : function() { var event = getIEEvent( obj ); fn.call( obj, event ); }; obj.attachEvent( "on" + type, obj[ type + fn ] ); }; } var unbind = function() {}; if ( docElem.removeEventListener ) { unbind = function( obj, type, fn ) { obj.removeEventListener( type, fn, false ); }; } else if ( docElem.detachEvent ) { unbind = function( obj, type, fn ) { obj.detachEvent( "on" + type, obj[ type + fn ] ); try { delete obj[ type + fn ]; } catch ( err ) { // can't delete window object properties obj[ type + fn ] = undefined; } }; } var eventie = { bind: bind, unbind: unbind }; // ----- module definition ----- // if ( typeof define === 'function' && define.amd ) { // AMD define( eventie ); } else if ( typeof exports === 'object' ) { // CommonJS module.exports = eventie; } else { // browser global window.eventie = eventie; } })( this ); },{}],9:[function(require,module,exports){ /*! * EventEmitter v4.2.9 - git.io/ee * Oliver Caldwell * MIT license * @preserve */ (function () { 'use strict'; /** * Class for managing events. * Can be extended to provide event functionality in other classes. * * @class EventEmitter Manages event registering and emitting. */ function EventEmitter() {} // Shortcuts to improve speed and size var proto = EventEmitter.prototype; var exports = this; var originalGlobalValue = exports.EventEmitter; /** * Finds the index of the listener for the event in its storage array. * * @param {Function[]} listeners Array of listeners to search through. * @param {Function} listener Method to look for. * @return {Number} Index of the specified listener, -1 if not found * @api private */ function indexOfListener(listeners, listener) { var i = listeners.length; while (i--) { if (listeners[i].listener === listener) { return i; } } return -1; } /** * Alias a method while keeping the context correct, to allow for overwriting of target method. * * @param {String} name The name of the target method. * @return {Function} The aliased method * @api private */ function alias(name) { return function aliasClosure() { return this[name].apply(this, arguments); }; } /** * Returns the listener array for the specified event. * Will initialise the event object and listener arrays if required. * Will return an object if you use a regex search. The object contains keys for each matched event. So /ba[rz]/ might return an object containing bar and baz. But only if you have either defined them with defineEvent or added some listeners to them. * Each property in the object response is an array of listener functions. * * @param {String|RegExp} evt Name of the event to return the listeners from. * @return {Function[]|Object} All listener functions for the event. */ proto.getListeners = function getListeners(evt) { var events = this._getEvents(); var response; var key; // Return a concatenated array of all matching events if // the selector is a regular expression. if (evt instanceof RegExp) { response = {}; for (key in events) { if (events.hasOwnProperty(key) && evt.test(key)) { response[key] = events[key]; } } } else { response = events[evt] || (events[evt] = []); } return response; }; /** * Takes a list of listener objects and flattens it into a list of listener functions. * * @param {Object[]} listeners Raw listener objects. * @return {Function[]} Just the listener functions. */ proto.flattenListeners = function flattenListeners(listeners) { var fla