(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
typeof define === 'function' && define.amd ? define(factory) :
(global.Bricks = factory());
}(this, (function () { 'use strict';
var _extends = Object.assign || function (target) {
for (var i = 1; i < arguments.length; i++) {
var source = arguments[i];
for (var key in source) {
if (Object.prototype.hasOwnProperty.call(source, key)) {
target[key] = source[key];
}
}
}
return target;
};
var knot = function knot() {
var extended = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
var events = Object.create(null);
function on(name, handler) {
events[name] = events[name] || [];
events[name].push(handler);
return this;
}
function once(name, handler) {
handler._once = true;
on(name, handler);
return this;
}
function off(name) {
var handler = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
handler ? events[name].splice(events[name].indexOf(handler), 1) : delete events[name];
return this;
}
function emit(name) {
var _this = this;
for (var _len = arguments.length, args = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
args[_key - 1] = arguments[_key];
}
// cache the events, to avoid consequences of mutation
var cache = events[name] && events[name].slice();
// only fire handlers if they exist
cache && cache.forEach(function (handler) {
// remove handlers added with 'once'
handler._once && off(name, handler);
// set 'this' context, pass args to handlers
handler.apply(_this, args);
});
return this;
}
return _extends({}, extended, {
on: on,
once: once,
off: off,
emit: emit
});
};
var bricks = function bricks() {
var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
// privates
var persist = void 0; // packing new elements, or all elements?
var ticking = void 0; // for debounced resize
var sizeIndex = void 0;
var sizeDetail = void 0;
var columnTarget = void 0;
var columnHeights = void 0;
var nodeTop = void 0;
var nodeLeft = void 0;
var nodeWidth = void 0;
var nodeHeight = void 0;
var nodes = void 0;
var nodesWidths = void 0;
var nodesHeights = void 0;
// resolve options
var packed = options.packed.indexOf('data-') === 0 ? options.packed : 'data-' + options.packed;
var sizes = options.sizes.slice().reverse();
var position = options.position !== false;
var container = options.container.nodeType ? options.container : document.querySelector(options.container);
var selectors = {
all: function all() {
return toArray(container.children);
},
new: function _new() {
return toArray(container.children).filter(function (node) {
return !node.hasAttribute('' + packed);
});
}
};
// series
var setup = [setSizeIndex, setSizeDetail, setColumns];
var run = [setNodes, setNodesDimensions, setNodesStyles, setContainerStyles];
// instance
var instance = knot({
pack: pack,
update: update,
resize: resize
});
return instance;
// general helpers
function runSeries(functions) {
functions.forEach(function (func) {
return func();
});
}
// array helpers
function toArray(input) {
var scope = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : document;
return Array.prototype.slice.call(input);
}
function fillArray(length) {
return Array.apply(null, Array(length)).map(function () {
return 0;
});
}
// size helpers
function getSizeIndex() {
// find index of widest matching media query
return sizes.map(function (size) {
return size.mq && window.matchMedia('(min-width: ' + size.mq + ')').matches;
}).indexOf(true);
}
function setSizeIndex() {
sizeIndex = getSizeIndex();
}
function setSizeDetail() {
// if no media queries matched, use the base case
sizeDetail = sizeIndex === -1 ? sizes[sizes.length - 1] : sizes[sizeIndex];
}
// column helpers
function setColumns() {
columnHeights = fillArray(sizeDetail.columns);
}
// node helpers
function setNodes() {
nodes = selectors[persist ? 'new' : 'all']();
}
function setNodesDimensions() {
// exit if empty container
if (nodes.length === 0) {
return;
}
nodesWidths = nodes.map(function (element) {
return element.clientWidth;
});
nodesHeights = nodes.map(function (element) {
return element.clientHeight;
});
}
function setNodesStyles() {
nodes.forEach(function (element, index) {
columnTarget = columnHeights.indexOf(Math.min.apply(Math, columnHeights));
element.style.position = 'absolute';
nodeTop = columnHeights[columnTarget] + 'px';
nodeLeft = columnTarget * nodesWidths[index] + columnTarget * sizeDetail.gutter + 'px';
// support positioned elements (default) or transformed elements
if (position) {
element.style.top = nodeTop;
element.style.left = nodeLeft;
} else {
element.style.transform = 'translate3d(' + nodeLeft + ', ' + nodeTop + ', 0)';
}
element.setAttribute(packed, '');
// ignore nodes with no width and/or height
nodeWidth = nodesWidths[index];
nodeHeight = nodesHeights[index];
if (nodeWidth && nodeHeight) {
columnHeights[columnTarget] += nodeHeight + sizeDetail.gutter;
}
});
}
// container helpers
function setContainerStyles() {
container.style.position = 'relative';
container.style.width = sizeDetail.columns * nodeWidth + (sizeDetail.columns - 1) * sizeDetail.gutter + 'px';
container.style.height = Math.max.apply(Math, columnHeights) - sizeDetail.gutter + 'px';
}
// resize helpers
function resizeFrame() {
if (!ticking) {
window.requestAnimationFrame(resizeHandler);
ticking = true;
}
}
function resizeHandler() {
if (sizeIndex !== getSizeIndex()) {
pack();
instance.emit('resize', sizeDetail);
}
ticking = false;
}
// API
function pack() {
persist = false;
runSeries(setup.concat(run));
return instance.emit('pack');
}
function update() {
persist = true;
runSeries(run);
return instance.emit('update');
}
function resize() {
var flag = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : true;
var action = flag ? 'addEventListener' : 'removeEventListener';
window[action]('resize', resizeFrame);
return instance;
}
};
return bricks;
})));