packery
Version:
Gapless, draggable grid layouts
1,946 lines (1,613 loc) • 402 kB
JavaScript
(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