text: /**
   * Muuri v0.7.1
   * https://github.com/haltu/muuri
   * Copyright (c) 2015-present, Haltu Oy
   * Released under the MIT license
   * https://github.com/haltu/muuri/blob/master/LICENSE.md
   * @license MIT
   *
   * Muuri Packer
   * Copyright (c) 2016-present, Niklas Rämö <inramo@gmail.com>
   * @license MIT
   *
   * Muuri Ticker / Muuri Emitter / Muuri Queue
   * Copyright (c) 2018-present, Niklas Rämö <inramo@gmail.com>
   * @license MIT
   */
  
  (function (global, factory) {
    if (typeof exports === 'object' && typeof module !== 'undefined') {
      var Hammer;
      try { Hammer = require('hammerjs') } catch (e) {}
      module.exports = factory(Hammer);
    } else {
      global.Muuri = factory(global.Hammer);
    }
  }(this, (function (Hammer) {
    'use strict';
  
    /**
     * Event emitter constructor.
     *
     * @class
     */
    function Emitter() {
      this._events = {};
      this._queue = [];
      this._counter = 0;
      this._isDestroyed = false;
    }
  
    /**
     * Public prototype methods
     * ************************
     */
  
    /**
     * Bind an event listener.
     *
     * @public
     * @memberof Emitter.prototype
     * @param {String} event
     * @param {Function} listener
     * @returns {Emitter}
     */
    Emitter.prototype.on = function(event, listener) {
      if (this._isDestroyed) return this;
  
      // Get listeners queue and create it if it does not exist.
      var listeners = this._events[event];
      if (!listeners) listeners = this._events[event] = [];
  
      // Add the listener to the queue.
      listeners.push(listener);
  
      return this;
    };
  
    /**
     * Bind an event listener that is triggered only once.
     *
     * @public
     * @memberof Emitter.prototype
     * @param {String} event
     * @param {Function} listener
     * @returns {Emitter}
     */
    Emitter.prototype.once = function(event, listener) {
      if (this._isDestroyed) return this;
  
      var callback = function() {
        this.off(event, callback);
        listener.apply(null, arguments);
      }.bind(this);
  
      return this.on(event, callback);
    };
  
    /**
     * Unbind all event listeners that match the provided listener function.
     *
     * @public
     * @memberof Emitter.prototype
     * @param {String} event
     * @param {Function} [listener]
     * @returns {Emitter}
     */
    Emitter.prototype.off = function(event, listener) {
      if (this._isDestroyed) return this;
  
      // Get listeners and return immediately if none is found.
      var listeners = this._events[event];
      if (!listeners || !listeners.length) return this;
  
      // If no specific listener is provided remove all listeners.
      if (!listener) {
        listeners.length = 0;
        return this;
      }
  
      // Remove all matching listeners.
      var i = listeners.length;
      while (i--) {
        if (listener === listeners[i]) listeners.splice(i, 1);
      }
  
      return this;
    };
  
    /**
     * Emit all listeners in a specified event with the provided arguments.
     *
     * @public
     * @memberof Emitter.prototype
     * @param {String} event
     * @param {*} [arg1]
     * @param {*} [arg2]
     * @param {*} [arg3]
     * @returns {Emitter}
     */
    Emitter.prototype.emit = function(event, arg1, arg2, arg3) {
      if (this._isDestroyed) return this;
  
      // Get event listeners and quit early if there's no listeners.
      var listeners = this._events[event];
      if (!listeners || !listeners.length) return this;
  
      var queue = this._queue;
      var qLength = queue.length;
      var aLength = arguments.length - 1;
      var i;
  
      // Add the current listeners to the callback queue before we process them.
      // This is necessary to guarantee that all of the listeners are called in
      // correct order even if new event listeners are removed/added during
      // processing and/or events are emitted during processing.
      for (i = 0; i < listeners.length; i++) {
        queue.push(listeners[i]);
      }
  
      // Increment queue counter. This is needed for the scenarios where emit is
      // triggered while the queue is already processing. We need to keep track of
      // how many "queue processors" there are active so that we can safely reset
      // the queue in the end when the last queue processor is finished.
      ++this._counter;
  
      // Process the queue (the specific part of it for this emit).
      for (i = qLength, qLength = queue.length; i < qLength; i++) {
        // prettier-ignore
        aLength === 0 ? queue[i]() :
        aLength === 1 ? queue[i](arg1) :
        aLength === 2 ? queue[i](arg1, arg2) :
                        queue[i](arg1, arg2, arg3);
  
        // Stop processing if the emitter is destroyed.
        if (this._isDestroyed) return this;
      }
  
      // Decrement queue process counter.
      --this._counter;
  
      // Reset the queue if there are no more queue processes running.
      if (!this._counter) queue.length = 0;
  
      return this;
    };
  
    /**
     * Destroy emitter instance. Basically just removes all bound listeners.
     *
     * @public
     * @memberof Emitter.prototype
     * @returns {Emitter}
     */
    Emitter.prototype.destroy = function() {
      if (this._isDestroyed) return this;
  
      var events = this._events;
      var event;
  
      // Flag as destroyed.
      this._isDestroyed = true;
  
      // Reset queue (if queue is currently processing this will also stop that).
      this._queue.length = this._counter = 0;
  
      // Remove all listeners.
      for (event in events) {
        if (events[event]) {
          events[event].length = 0;
          events[event] = undefined;
        }
      }
  
      return this;
    };
  
    var namespace = 'Muuri';
    var gridInstances = {};
  
    var eventSynchronize = 'synchronize';
    var eventLayoutStart = 'layoutStart';
    var eventLayoutEnd = 'layoutEnd';
    var eventAdd = 'add';
    var eventRemove = 'remove';
    var eventShowStart = 'showStart';
    var eventShowEnd = 'showEnd';
    var eventHideStart = 'hideStart';
    var eventHideEnd = 'hideEnd';
    var eventFilter = 'filter';
    var eventSort = 'sort';
    var eventMove = 'move';
    var eventSend = 'send';
    var eventBeforeSend = 'beforeSend';
    var eventReceive = 'receive';
    var eventBeforeReceive = 'beforeReceive';
    var eventDragInit = 'dragInit';
    var eventDragStart = 'dragStart';
    var eventDragMove = 'dragMove';
    var eventDragScroll = 'dragScroll';
    var eventDragEnd = 'dragEnd';
    var eventDragReleaseStart = 'dragReleaseStart';
    var eventDragReleaseEnd = 'dragReleaseEnd';
    var eventDestroy = 'destroy';
  
    // Set up the default export values.
    var isTransformSupported = false;
    var transformStyle = 'transform';
    var transformProp = 'transform';
  
    // Find the supported transform prop and style names.
    var style = 'transform';
    var styleCap = 'Transform';
    ['', 'Webkit', 'Moz', 'O', 'ms'].forEach(function(prefix) {
      if (isTransformSupported) return;
      var propName = prefix ? prefix + styleCap : style;
      if (document.documentElement.style[propName] !== undefined) {
        prefix = prefix.toLowerCase();
        transformStyle = prefix ? '-' + prefix + '-' + style : style;
        transformProp = propName;
        isTransformSupported = true;
      }
    });
  
    var stylesCache = typeof WeakMap === 'function' ? new WeakMap() : null;
  
    /**
     * Returns the computed value of an element's style property as a string.
     *
     * @param {HTMLElement} element
     * @param {String} style
     * @returns {String}
     */
    function getStyle(element, style) {
      var styles = stylesCache && stylesCache.get(element);
      if (!styles) {
        styles = window.getComputedStyle(element, null);
        stylesCache && stylesCache.set(element, styles);
      }
      return styles.getPropertyValue(style === 'transform' ? transformStyle : style);
    }
  
    var styleNameRegEx = /([A-Z])/g;
  
    /**
     * Transforms a camel case style property to kebab case style property.
     *
     * @param {String} string
     * @returns {String}
     */
    function getStyleName(string) {
      return string.replace(styleNameRegEx, '-$1').toLowerCase();
    }
  
    /**
     * Set inline styles to an element.
     *
     * @param {HTMLElement} element
     * @param {Object} styles
     */
    function setStyles(element, styles) {
      for (var prop in styles) {
        element.style[prop === 'transform' ? transformProp : prop] = styles[prop];
      }
    }
  
    /**
     * Item animation handler powered by Web Animations API.
     *
     * @class
     * @param {HTMLElement} element
     */
    function ItemAnimate(element) {
      this._element = element;
      this._animation = null;
      this._callback = null;
      this._props = [];
      this._values = [];
      this._keyframes = [];
      this._options = {};
      this._isDestroyed = false;
      this._onFinish = this._onFinish.bind(this);
    }
  
    /**
     * Public prototype methods
     * ************************
     */
  
    /**
     * Start instance's animation. Automatically stops current animation if it is
     * running.
     *
     * @public
     * @memberof ItemAnimate.prototype
     * @param {Object} propsFrom
     * @param {Object} propsTo
     * @param {Object} [options]
     * @param {Number} [options.duration=300]
     * @param {String} [options.easing='ease']
     * @param {Function} [options.onFinish]
     */
    ItemAnimate.prototype.start = function(propsFrom, propsTo, options) {
      if (this._isDestroyed) return;
  
      var animation = this._animation;
      var currentProps = this._props;
      var currentValues = this._values;
      var opts = options || 0;
      var cancelAnimation = false;
  
      // If we have an existing animation running, let's check if it needs to be
      // cancelled or if it can continue running.
      if (animation) {
        var propCount = 0;
        var propIndex;
  
        // Check if the requested animation target props and values match with the
        // current props and values.
        for (var propName in propsTo) {
          ++propCount;
          propIndex = currentProps.indexOf(propName);
          if (propIndex === -1 || propsTo[propName] !== currentValues[propIndex]) {
            cancelAnimation = true;
            break;
          }
        }
  
        // Check if the target props count matches current props count. This is
        // needed for the edge case scenario where target props contain the same
        // styles as current props, but the current props have some additional
        // props.
        if (!cancelAnimation && propCount !== currentProps.length) {
          cancelAnimation = true;
        }
      }
  
      // Cancel animation (if required).
      if (cancelAnimation) animation.cancel();
  
      // Store animation callback.
      this._callback = typeof opts.onFinish === 'function' ? opts.onFinish : null;
  
      // If we have a running animation that does not need to be cancelled, let's
      // call it a day here and let it run.
      if (animation && !cancelAnimation) return;
  
      // Store target props and values to instance.
      currentProps.length = currentValues.length = 0;
      for (propName in propsTo) {
        currentProps.push(propName);
        currentValues.push(propsTo[propName]);
      }
  
      // Set up keyframes.
      var animKeyframes = this._keyframes;
      animKeyframes[0] = propsFrom;
      animKeyframes[1] = propsTo;
  
      // Set up options.
      var animOptions = this._options;
      animOptions.duration = opts.duration || 300;
      animOptions.easing = opts.easing || 'ease';
  
      // Start the animation
      var element = this._element;
      animation = element.animate(animKeyframes, animOptions);
      animation.onfinish = this._onFinish;
      this._animation = animation;
  
      // Set the end styles. This makes sure that the element stays at the end
      // values after animation is finished.
      setStyles(element, propsTo);
    };
  
    /**
     * Stop instance's current animation if running.
     *
     * @public
     * @memberof ItemAnimate.prototype
     * @param {Object} [styles]
     */
    ItemAnimate.prototype.stop = function(styles) {
      if (this._isDestroyed || !this._animation) return;
  
      var element = this._element;
      var currentProps = this._props;
      var currentValues = this._values;
      var propName;
      var propValue;
      var i;
  
      // Calculate (if not provided) and set styles.
      if (!styles) {
        for (i = 0; i < currentProps.length; i++) {
          propName = currentProps[i];
          propValue = getStyle(element, getStyleName(propName));
          element.style[propName === 'transform' ? transformProp : propName] = propValue;
        }
      } else {
        setStyles(element, styles);
      }
  
      //  Cancel animation.
      this._animation.cancel();
      this._animation = this._callback = null;
  
      // Reset current props and values.
      currentProps.length = currentValues.length = 0;
    };
  
    /**
     * Check if the item is being animated currently.
     *
     * @public
     * @memberof ItemAnimate.prototype
     * @return {Boolean}
     */
    ItemAnimate.prototype.isAnimating = function() {
      return !!this._animation;
    };
  
    /**
     * Destroy the instance and stop current animation if it is running.
     *
     * @public
     * @memberof ItemAnimate.prototype
     */
    ItemAnimate.prototype.destroy = function() {
      if (this._isDestroyed) return;
      this.stop();
      this._element = this._options = this._keyframes = null;
      this._isDestroyed = true;
    };
  
    /**
     * Private prototype methods
     * *************************
     */
  
    /**
     * Animation end handler.
     *
     * @private
     * @memberof ItemAnimate.prototype
     */
    ItemAnimate.prototype._onFinish = function() {
      var callback = this._callback;
      this._animation = this._callback = null;
      this._props.length = this._values.length = 0;
      callback && callback();
    };
  
    var raf = (
      window.requestAnimationFrame ||
      window.webkitRequestAnimationFrame ||
      window.mozRequestAnimationFrame ||
      window.msRequestAnimationFrame ||
      rafFallback
    ).bind(window);
  
    function rafFallback(cb) {
      return window.setTimeout(cb, 16);
    }
  
    /**
     * A ticker system for handling DOM reads and writes in an efficient way.
     * Contains a read queue and a write queue that are processed on the next
     * animation frame when needed.
     *
     * @class
     */
    function Ticker() {
      this._nextTick = null;
  
      this._queue = [];
      this._reads = {};
      this._writes = {};
  
      this._batch = [];
      this._batchReads = {};
      this._batchWrites = {};
  
      this._flush = this._flush.bind(this);
    }
  
    Ticker.prototype.add = function(id, readCallback, writeCallback, isImportant) {
      // First, let's check if an item has been added to the queues with the same id
      // and if so -> remove it.
      var currentIndex = this._queue.indexOf(id);
      if (currentIndex > -1) this._queue[currentIndex] = undefined;
  
      // Add all important callbacks to the beginning of the queue and other
      // callbacks to the end of the queue.
      isImportant ? this._queue.unshift(id) : this._queue.push(id);
  
      // Store callbacks.
      this._reads[id] = readCallback;
      this._writes[id] = writeCallback;
  
      // Finally, let's kick-start the next tick if it is not running yet.
      if (!this._nextTick) this._nextTick = raf(this._flush);
    };
  
    Ticker.prototype.cancel = function(id) {
      var currentIndex = this._queue.indexOf(id);
      if (currentIndex > -1) {
        this._queue[currentIndex] = undefined;
        this._reads[id] = undefined;
        this._writes[id] = undefined;
      }
    };
  
    Ticker.prototype._flush = function() {
      var queue = this._queue;
      var reads = this._reads;
      var writes = this._writes;
      var batch = this._batch;
      var batchReads = this._batchReads;
      var batchWrites = this._batchWrites;
      var length = queue.length;
      var id;
      var i;
  
      // Reset ticker.
      this._nextTick = null;
  
      // Setup queues and callback placeholders.
      for (i = 0; i < length; i++) {
        id = queue[i];
        if (!id) continue;
  
        batch.push(id);
  
        batchReads[id] = reads[id];
        reads[id] = undefined;
  
        batchWrites[id] = writes[id];
        writes[id] = undefined;
      }
  
      // Reset queue.
      queue.length = 0;
  
      // Process read callbacks.
      for (i = 0; i < length; i++) {
        id = batch[i];
        if (batchReads[id]) {
          batchReads[id]();
          batchReads[id] = undefined;
        }
      }
  
      // Process write callbacks.
      for (i = 0; i < length; i++) {
        id = batch[i];
        if (batchWrites[id]) {
          batchWrites[id]();
          batchWrites[id] = undefined;
        }
      }
  
      // Reset batch.
      batch.length = 0;
  
      // Restart the ticker if needed.
      if (!this._nextTick && queue.length) {
        this._nextTick = raf(this._flush);
      }
    };
  
    var ticker = new Ticker();
  
    var layoutTick = 'layout';
    var visibilityTick = 'visibility';
    var moveTick = 'move';
    var scrollTick = 'scroll';
  
    function addLayoutTick(itemId, readCallback, writeCallback) {
      return ticker.add(itemId + layoutTick, readCallback, writeCallback);
    }
  
    function cancelLayoutTick(itemId) {
      return ticker.cancel(itemId + layoutTick);
    }
  
    function addVisibilityTick(itemId, readCallback, writeCallback) {
      return ticker.add(itemId + visibilityTick, readCallback, writeCallback);
    }
  
    function cancelVisibilityTick(itemId) {
      return ticker.cancel(itemId + visibilityTick);
    }
  
    function addMoveTick(itemId, readCallback, writeCallback) {
      return ticker.add(itemId + moveTick, readCallback, writeCallback, true);
    }
  
    function cancelMoveTick(itemId) {
      return ticker.cancel(itemId + moveTick);
    }
  
    function addScrollTick(itemId, readCallback, writeCallback) {
      return ticker.add(itemId + scrollTick, readCallback, writeCallback, true);
    }
  
    function cancelScrollTick(itemId) {
      return ticker.cancel(itemId + scrollTick);
    }
  
    var proto = Element.prototype;
    var matches =
      proto.matches ||
      proto.matchesSelector ||
      proto.webkitMatchesSelector ||
      proto.mozMatchesSelector ||
      proto.msMatchesSelector ||
      proto.oMatchesSelector;
  
    /**
     * Check if element matches a CSS selector.
     *
     * @param {*} val
     * @returns {Boolean}
     */
    function elementMatches(el, selector) {
      return matches.call(el, selector);
    }
  
    /**
     * Add class to an element.
     *
     * @param {HTMLElement} element
     * @param {String} className
     */
    function addClassModern(element, className) {
      element.classList.add(className);
    }
  
    /**
     * Add class to an element (legacy version, for IE9 support).
     *
     * @param {HTMLElement} element
     * @param {String} className
     */
    function addClassLegacy(element, className) {
      if (!elementMatches(element, '.' + className)) {
        element.className += ' ' + className;
      }
    }
  
    var addClass = ('classList' in Element.prototype ? addClassModern : addClassLegacy);
  
    /**
     * Normalize array index. Basically this function makes sure that the provided
     * array index is within the bounds of the provided array and also transforms
     * negative index to the matching positive index.
     *
     * @param {Array} array
     * @param {Number} index
     * @param {Boolean} isMigration
     */
    function normalizeArrayIndex(array, index, isMigration) {
      var length = array.length;
      var maxIndex = Math.max(0, isMigration ? length : length - 1);
      return index > maxIndex ? maxIndex : index < 0 ? Math.max(maxIndex + index + 1, 0) : index;
    }
  
    /**
     * Move array item to another index.
     *
     * @param {Array} array
     * @param {Number} fromIndex
     *   - Index (positive or negative) of the item that will be moved.
     * @param {Number} toIndex
     *   - Index (positive or negative) where the item should be moved to.
     */
    function arrayMove(array, fromIndex, toIndex) {
      // Make sure the array has two or more items.
      if (array.length < 2) return;
  
      // Normalize the indices.
      var from = normalizeArrayIndex(array, fromIndex);
      var to = normalizeArrayIndex(array, toIndex);
  
      // Add target item to the new position.
      if (from !== to) {
        array.splice(to, 0, array.splice(from, 1)[0]);
      }
    }
  
    /**
     * Swap array items.
     *
     * @param {Array} array
     * @param {Number} index
     *   - Index (positive or negative) of the item that will be swapped.
     * @param {Number} withIndex
     *   - Index (positive or negative) of the other item that will be swapped.
     */
    function arraySwap(array, index, withIndex) {
      // Make sure the array has two or more items.
      if (array.length < 2) return;
  
      // Normalize the indices.
      var indexA = normalizeArrayIndex(array, index);
      var indexB = normalizeArrayIndex(array, withIndex);
      var temp;
  
      // Swap the items.
      if (indexA !== indexB) {
        temp = array[indexA];
        array[indexA] = array[indexB];
        array[indexB] = temp;
      }
    }
  
    var actionCancel = 'cancel';
    var actionFinish = 'finish';
  
    /**
     * Returns a function, that, as long as it continues to be invoked, will not
     * be triggered. The function will be called after it stops being called for
     * N milliseconds. The returned function accepts one argument which, when
     * being "finish", calls the debounce function immediately if it is currently
     * waiting to be called, and when being "cancel" cancels the currently queued
     * function call.
     *
     * @param {Function} fn
     * @param {Number} wait
     * @returns {Function}
     */
    function debounce(fn, wait) {
      var timeout;
  
      if (wait > 0) {
        return function(action) {
          if (timeout !== undefined) {
            timeout = window.clearTimeout(timeout);
            if (action === actionFinish) fn();
          }
  
          if (action !== actionCancel && action !== actionFinish) {
            timeout = window.setTimeout(function() {
              timeout = undefined;
              fn();
            }, wait);
          }
        };
      }
  
      return function(action) {
        if (action !== actionCancel) fn();
      };
    }
  
    /**
     * Returns true if element is transformed, false if not. In practice the
     * element's display value must be anything else than "none" or "inline" as
     * well as have a valid transform value applied in order to be counted as a
     * transformed element.
     *
     * Borrowed from Mezr (v0.6.1):
     * https://github.com/niklasramo/mezr/blob/0.6.1/mezr.js#L661
     *
     * @param {HTMLElement} element
     * @returns {Boolean}
     */
    function isTransformed(element) {
      var transform = getStyle(element, 'transform');
      if (!transform || transform === 'none') return false;
  
      var display = getStyle(element, 'display');
      if (display === 'inline' || display === 'none') return false;
  
      return true;
    }
  
    /**
     * Returns an absolute positioned element's containing block, which is
     * considered to be the closest ancestor element that the target element's
     * positioning is relative to. Disclaimer: this only works as intended for
     * absolute positioned elements.
     *
     * @param {HTMLElement} element
     * @param {Boolean} [includeSelf=false]
     *   - When this is set to true the containing block checking is started from
     *     the provided element. Otherwise the checking is started from the
     *     provided element's parent element.
     * @returns {(Document|Element)}
     */
    function getContainingBlock(element, includeSelf) {
      // As long as the containing block is an element, static and not
      // transformed, try to get the element's parent element and fallback to
      // document. https://github.com/niklasramo/mezr/blob/0.6.1/mezr.js#L339
      var ret = (includeSelf ? element : element.parentElement) || document;
      while (ret && ret !== document && getStyle(ret, 'position') === 'static' && !isTransformed(ret)) {
        ret = ret.parentElement || document;
      }
      return ret;
    }
  
    /**
     * Returns the computed value of an element's style property transformed into
     * a float value.
     *
     * @param {HTMLElement} el
     * @param {String} style
     * @returns {Number}
     */
    function getStyleAsFloat(el, style) {
      return parseFloat(getStyle(el, style)) || 0;
    }
  
    var offsetA = {};
    var offsetB = {};
    var offsetDiff = {};
  
    /**
     * Returns the element's document offset, which in practice means the vertical
     * and horizontal distance between the element's northwest corner and the
     * document's northwest corner. Note that this function always returns the same
     * object so be sure to read the data from it instead using it as a reference.
     *
     * @param {(Document|Element|Window)} element
     * @param {Object} [offsetData]
     *   - Optional data object where the offset data will be inserted to. If not
     *     provided a new object will be created for the return data.
     * @returns {Object}
     */
    function getOffset(element, offsetData) {
      var ret = offsetData || {};
      var rect;
  
      // Set up return data.
      ret.left = 0;
      ret.top = 0;
  
      // Document's offsets are always 0.
      if (element === document) return ret;
  
      // Add viewport scroll left/top to the respective offsets.
      ret.left = window.pageXOffset || 0;
      ret.top = window.pageYOffset || 0;
  
      // Window's offsets are the viewport scroll left/top values.
      if (element.self === window.self) return ret;
  
      // Add element's client rects to the offsets.
      rect = element.getBoundingClientRect();
      ret.left += rect.left;
      ret.top += rect.top;
  
      // Exclude element's borders from the offset.
      ret.left += getStyleAsFloat(element, 'border-left-width');
      ret.top += getStyleAsFloat(element, 'border-top-width');
  
      return ret;
    }
  
    /**
     * Calculate the offset difference two elements.
     *
     * @param {HTMLElement} elemA
     * @param {HTMLElement} elemB
     * @param {Boolean} [compareContainingBlocks=false]
     *   - When this is set to true the containing blocks of the provided elements
     *     will be used for calculating the difference. Otherwise the provided
     *     elements will be compared directly.
     * @returns {Object}
     */
    function getOffsetDiff(elemA, elemB, compareContainingBlocks) {
      offsetDiff.left = 0;
      offsetDiff.top = 0;
  
      // If elements are same let's return early.
      if (elemA === elemB) return offsetDiff;
  
      // Compare containing blocks if necessary.
      if (compareContainingBlocks) {
        elemA = getContainingBlock(elemA, true);
        elemB = getContainingBlock(elemB, true);
  
        // If containing blocks are identical, let's return early.
        if (elemA === elemB) return offsetDiff;
      }
  
      // Finally, let's calculate the offset diff.
      getOffset(elemA, offsetA);
      getOffset(elemB, offsetB);
      offsetDiff.left = offsetB.left - offsetA.left;
      offsetDiff.top = offsetB.top - offsetA.top;
  
      return offsetDiff;
    }
  
    var translateData = {};
  
    /**
     * Returns the element's computed translateX and translateY values as a floats.
     * The returned object is always the same object and updated every time this
     * function is called.
     *
     * @param {HTMLElement} element
     * @returns {Object}
     */
    function getTranslate(element) {
      translateData.x = 0;
      translateData.y = 0;
  
      var transform = getStyle(element, 'transform');
      if (!transform) return translateData;
  
      var matrixData = transform.replace('matrix(', '').split(',');
      translateData.x = parseFloat(matrixData[4]) || 0;
      translateData.y = parseFloat(matrixData[5]) || 0;
  
      return translateData;
    }
  
    /**
     * Transform translateX and translateY value into CSS transform style
     * property's value.
     *
     * @param {Number} x
     * @param {Number} y
     * @returns {String}
     */
    function getTranslateString(x, y) {
      return 'translateX(' + x + 'px) translateY(' + y + 'px)';
    }
  
    var tempArray = [];
  
    /**
     * Insert an item or an array of items to array to a specified index. Mutates
     * the array. The index can be negative in which case the items will be added
     * to the end of the array.
     *
     * @param {Array} array
     * @param {*} items
     * @param {Number} [index=-1]
     */
    function arrayInsert(array, items, index) {
      var startIndex = typeof index === 'number' ? index : -1;
      if (startIndex < 0) startIndex = array.length - startIndex + 1;
  
      array.splice.apply(array, tempArray.concat(startIndex, 0, items));
      tempArray.length = 0;
    }
  
    var objectType = '[object Object]';
    var toString = Object.prototype.toString;
  
    /**
     * Check if a value is a plain object.
     *
     * @param {*} val
     * @returns {Boolean}
     */
    function isPlainObject(val) {
      return typeof val === 'object' && toString.call(val) === objectType;
    }
  
    /**
     * Remove class from an element.
     *
     * @param {HTMLElement} element
     * @param {String} className
     */
    function removeClassModern(element, className) {
      element.classList.remove(className);
    }
  
    /**
     * Remove class from an element (legacy version, for IE9 support).
     *
     * @param {HTMLElement} element
     * @param {String} className
     */
    function removeClassLegacy(element, className) {
      if (elementMatches(element, '.' + className)) {
        element.className = (' ' + element.className + ' ').replace(' ' + className + ' ', ' ').trim();
      }
    }
  
    var removeClass = ('classList' in Element.prototype ? removeClassModern : removeClassLegacy);
  
    // To provide consistently correct dragging experience we need to know if
    // transformed elements leak fixed elements or not.
    var hasTransformLeak = checkTransformLeak();
  
    // Drag start predicate states.
    var startPredicateInactive = 0;
    var startPredicatePending = 1;
    var startPredicateResolved = 2;
    var startPredicateRejected = 3;
  
    /**
     * Bind Hammer touch interaction to an item.
     *
     * @class
     * @param {Item} item
     */
    function ItemDrag(item) {
      if (!Hammer) {
        throw new Error('[' + namespace + '] required dependency Hammer is not defined.');
      }
  
      // If we don't have a valid transform leak test result yet, let's run the
      // test on first ItemDrag init. The test needs body element to be ready and
      // here we can be sure that it is ready.
      if (hasTransformLeak === null) {
        hasTransformLeak = checkTransformLeak();
      }
  
      var drag = this;
      var element = item._element;
      var grid = item.getGrid();
      var settings = grid._settings;
      var hammer;
  
      // Start predicate private data.
      var startPredicate =
        typeof settings.dragStartPredicate === 'function'
          ? settings.dragStartPredicate
          : ItemDrag.defaultStartPredicate;
      var startPredicateState = startPredicateInactive;
      var startPredicateResult;
  
      // Protected data.
      this._item = item;
      this._gridId = grid._id;
      this._hammer = hammer = new Hammer.Manager(element);
      this._isDestroyed = false;
      this._isMigrating = false;
  
      grid.setHammer(hammer);
  
      // Setup item's initial drag data.
      this._reset();
  
      // Bind some methods that needs binding.
      this._onScroll = this._onScroll.bind(this);
      this._prepareMove = this._prepareMove.bind(this);
      this._applyMove = this._applyMove.bind(this);
      this._prepareScroll = this._prepareScroll.bind(this);
      this._applyScroll = this._applyScroll.bind(this);
      this._checkOverlap = this._checkOverlap.bind(this);
  
      // Create a private drag start resolver that can be used to resolve the drag
      // start predicate asynchronously.
      this._forceResolveStartPredicate = function(event) {
        if (!this._isDestroyed && startPredicateState === startPredicatePending) {
          startPredicateState = startPredicateResolved;
          this._onStart(event);
        }
      };
  
      // Create debounce overlap checker function.
      this._checkOverlapDebounce = debounce(this._checkOverlap, settings.dragSortInterval);
  
      // Add drag recognizer to hammer.
      hammer.add(
        new Hammer.Pan({
          event: 'drag',
          pointers: 1,
          threshold: 0,
          direction: Hammer.DIRECTION_ALL
        })
      );
  
      // Add drag init recognizer to hammer.
      hammer.add(
        new Hammer.Press({
          event: 'draginit',
          pointers: 1,
          threshold: 1000,
          time: 0
        })
      );
  
      // Configure the hammer instance.
      if (isPlainObject(settings.dragHammerSettings)) {
        hammer.set(settings.dragHammerSettings);
      }
  
      // Bind drag events.
      hammer
        .on('draginit dragstart dragmove', function(e) {
          // Let's activate drag start predicate state.
          if (startPredicateState === startPredicateInactive) {
            startPredicateState = startPredicatePending;
          }
  
          // If predicate is pending try to resolve it.
          if (startPredicateState === startPredicatePending) {
            startPredicateResult = startPredicate(drag._item, e);
            if (startPredicateResult === true) {
              startPredicateState = startPredicateResolved;
              drag._onStart(e);
            } else if (startPredicateResult === false) {
              startPredicateState = startPredicateRejected;
            }
          }
  
          // Otherwise if predicate is resolved and drag is active, move the item.
          else if (startPredicateState === startPredicateResolved && drag._isActive) {
            drag._onMove(e);
          }
        })
        .on('dragend dragcancel draginitup', function(e) {
          // Check if the start predicate was resolved during drag.
          var isResolved = startPredicateState === startPredicateResolved;
  
          // Do final predicate check to allow user to unbind stuff for the current
          // drag procedure within the predicate callback. The return value of this
          // check will have no effect to the state of the predicate.
          startPredicate(drag._item, e);
  
          // Reset start predicate state.
          startPredicateState = startPredicateInactive;
  
          // If predicate is resolved and dragging is active, call the end handler.
          if (isResolved && drag._isActive) drag._onEnd(e);
        });
  
      // Prevent native link/image dragging for the item and it's ancestors.
      element.addEventListener('dragstart', preventDefault, false);
    }
  
    /**
     * Public static methods
     * *********************
     */
  
    /**
     * Default drag start predicate handler that handles anchor elements
     * gracefully. The return value of this function defines if the drag is
     * started, rejected or pending. When true is returned the dragging is started
     * and when false is returned the dragging is rejected. If nothing is returned
     * the predicate will be called again on the next drag movement.
     *
     * @public
     * @memberof ItemDrag
     * @param {Item} item
     * @param {Object} event
     * @param {Object} [options]
     *   - An optional options object which can be used to pass the predicate
     *     it's options manually. By default the predicate retrieves the options
     *     from the grid's settings.
     * @returns {Boolean}
     */
    ItemDrag.defaultStartPredicate = function(item, event, options) {
      var drag = item._drag;
      var predicate = drag._startPredicateData || drag._setupStartPredicate(options);
  
      // Final event logic. At this stage return value does not matter anymore,
      // the predicate is either resolved or it's not and there's nothing to do
      // about it. Here we just reset data and if the item element is a link
      // we follow it (if there has only been slight movement).
      if (event.isFinal) {
        drag._finishStartPredicate(event);
        return;
      }
  
      // Find and store the handle element so we can check later on if the
      // cursor is within the handle. If we have a handle selector let's find
      // the corresponding element. Otherwise let's use the item element as the
      // handle.
      if (!predicate.handleElement) {
        predicate.handleElement = drag._getStartPredicateHandle(event);
        if (!predicate.handleElement) return false;
      }
  
      // If delay is defined let's keep track of the latest event and initiate
      // delay if it has not been done yet.
      if (predicate.delay) {
        predicate.event = event;
        if (!predicate.delayTimer) {
          predicate.delayTimer = window.setTimeout(function() {
            predicate.delay = 0;
            if (drag._resolveStartPredicate(predicate.event)) {
              drag._forceResolveStartPredicate(predicate.event);
              drag._resetStartPredicate();
            }
          }, predicate.delay);
        }
      }
  
      return drag._resolveStartPredicate(event);
    };
  
    /**
     * Default drag sort predicate.
     *
     * @public
     * @memberof ItemDrag
     * @param {Item} item
     * @param {Object} [options]
     * @param {Number} [options.threshold=50]
     * @param {String} [options.action='move']
     * @returns {(Boolean|DragSortCommand)}
     *   - Returns false if no valid index was found. Otherwise returns drag sort
     *     command.
     */
    ItemDrag.defaultSortPredicate = (function() {
      var itemRect = {};
      var targetRect = {};
      var returnData = {};
      var rootGridArray = [];
  
      function getTargetGrid(item, rootGrid, threshold) {
        var target = null;
        var dragSort = rootGrid._settings.dragSort;
        var bestScore = -1;
        var gridScore;
        var grids;
        var grid;
        var i;
  
        // Get potential target grids.
        if (dragSort === true) {
          rootGridArray[0] = rootGrid;
          grids = rootGridArray;
        } else {
          grids = dragSort.call(rootGrid, item);
        }
  
        // Return immediately if there are no grids.
        if (!Array.isArray(grids)) return target;
  
        // Loop through the grids and get the best match.
        for (i = 0; i < grids.length; i++) {
          grid = grids[i];
  
          // Filter out all destroyed grids.
          if (grid._isDestroyed) continue;
  
          // We need to update the grid's offsets and dimensions since they might
          // have changed (e.g during scrolling).
          grid._updateBoundingRect();
  
          // Check how much dragged element overlaps the container element.
          targetRect.width = grid._width;
          targetRect.height = grid._height;
          targetRect.left = grid._left;
          targetRect.top = grid._top;
          gridScore = getRectOverlapScore(itemRect, targetRect);
  
          // Check if this grid is the best match so far.
          if (gridScore > threshold && gridScore > bestScore) {
            bestScore = gridScore;
            target = grid;
          }
        }
  
        // Always reset root grid array.
        rootGridArray.length = 0;
  
        return target;
      }
  
      return function(item, options) {
        var drag = item._drag;
        var rootGrid = drag._getGrid();
  
        // Get drag sort predicate settings.
        var sortThreshold = options && typeof options.threshold === 'number' ? options.threshold : 50;
        var sortAction = options && options.action === 'swap' ? 'swap' : 'move';
  
        // Populate item rect data.
        itemRect.width = item._width;
        itemRect.height = item._height;
        itemRect.left = drag._elementClientX;
        itemRect.top = drag._elementClientY;
  
        // Calculate the target grid.
        var grid = getTargetGrid(item, rootGrid, sortThreshold);
  
        // Return early if we found no grid container element that overlaps the
        // dragged item enough.
        if (!grid) return false;
  
        var gridOffsetLeft = 0;
        var gridOffsetTop = 0;
        var matchScore = -1;
        var matchIndex;
        var hasValidTargets;
        var target;
        var score;
        var i;
  
        // If item is moved within it's originating grid adjust item's left and
        // top props. Otherwise if item is moved to/within another grid get the
        // container element's offset (from the element's content edge).
        if (grid === rootGrid) {
          itemRect.left = drag._gridX + item._marginLeft;
          itemRect.top = drag._gridY + item._marginTop;
        } else {
          grid._updateBorders(1, 0, 1, 0);
          gridOffsetLeft = grid._left + grid._borderLeft;
          gridOffsetTop = grid._top + grid._borderTop;
        }
  
        // Loop through the target grid items and try to find the best match.
        for (i = 0; i < grid._items.length; i++) {
          target = grid._items[i];
  
          // If the target item is not active or the target item is the dragged
          // item let's skip to the next item.
          if (!target._isActive || target === item) {
            continue;
          }
  
          // Mark the grid as having valid target items.
          hasValidTargets = true;
  
          // Calculate the target's overlap score with the dragged item.
          targetRect.width = target._width;
          targetRect.height = target._height;
          targetRect.left = target._left + target._marginLeft + gridOffsetLeft;
          targetRect.top = target._top + target._marginTop + gridOffsetTop;
          score = getRectOverlapScore(itemRect, targetRect);
  
          // Update best match index and score if the target's overlap score with
          // the dragged item is higher than the current best match score.
          if (score > matchScore) {
            matchIndex = i;
            matchScore = score;
          }
        }
  
        // If there is no valid match and the item is being moved into another
        // grid.
        if (matchScore < sortThreshold && item.getGrid() !== grid) {
          matchIndex = hasValidTargets ? -1 : 0;
          matchScore = Infinity;
        }
  
        // Check if the best match overlaps enough to justify a placement switch.
        if (matchScore >= sortThreshold) {
          returnData.grid = grid;
          returnData.index = matchIndex;
          returnData.action = sortAction;
          return returnData;
        }
  
        return false;
      };
    })();
  
    /**
     * Public prototype methods
     * ************************
     */
  
    /**
     * Abort dragging and reset drag data.
     *
     * @public
     * @memberof ItemDrag.prototype
     * @returns {ItemDrag}
     */
    ItemDrag.prototype.stop = function() {
      var item = this._item;
      var element = item._element;
      var grid = this._getGrid();
  
      if (!this._isActive) return this;
  
      // If the item is being dropped into another grid, finish it up and return
      // immediately.
      if (this._isMigrating) {
        this._finishMigration();
        return this;
      }
  
      // Cancel queued move and scroll ticks.
      cancelMoveTick(item._id);
      cancelScrollTick(item._id);
  
      // Remove scroll listeners.
      this._unbindScrollListeners();
  
      // Cancel overlap check.
      this._checkOverlapDebounce('cancel');
  
      // Append item element to the container if it's not it's child. Also make
      // sure the translate values are adjusted to account for the DOM shift.
      if (element.parentNode !== grid._element) {
        grid._element.appendChild(element);
        element.style[transformProp] = getTranslateString(this._gridX, this._gridY);
      }
  
      // Remove dragging class.
      removeClass(element, grid._settings.itemDraggingClass);
  
      // Reset drag data.
      this._reset();
  
      return this;
    };
  
    /**
     * Destroy instance.
     *
     * @public
     * @memberof ItemDrag.prototype
     * @returns {ItemDrag}
     */
    ItemDrag.prototype.destroy = function() {
      if (this._isDestroyed) return this;
      this.stop();
      this._hammer.destroy();
      this._item._element.removeEventListener('dragstart', preventDefault, false);
      this._isDestroyed = true;
      return this;
    };
  
    /**
     * Private prototype methods
     * *************************
     */
  
    /**
     * Get Grid instance.
     *
     * @private
     * @memberof ItemDrag.prototype
     * @returns {?Grid}
     */
    ItemDrag.prototype._getGrid = function() {
      return gridInstances[this._gridId] || null;
    };
  
    /**
     * Setup/reset drag data.
     *
     * @private
     * @memberof ItemDrag.prototype
     */
    ItemDrag.prototype._reset = function() {
      // Is item being dragged?
      this._isActive = false;
  
      // The dragged item's container element.
      this._container = null;
  
      // The dragged item's containing block.
      this._containingBlock = null;
  
      // Hammer event data.
      this._lastEvent = null;
      this._lastScrollEvent = null;
  
      // All the elements which need to be listened for scroll events during
      // dragging.
      this._scrollers = [];
  
      // The current translateX/translateY position.
      this._left = 0;
      this._top = 0;
  
      // Dragged element's current position within the grid.
      this._gridX = 0;
      this._gridY = 0;
  
      // Dragged element's current offset from window's northwest corner. Does
      // not account for element's margins.
      this._elementClientX = 0;
      this._elementClientY = 0;
  
      // Offset difference between the dragged element's temporary drag
      // container and it's original container.
      this._containerDiffX = 0;
      this._containerDiffY = 0;
    };
  
    /**
     * Bind drag scroll handlers to all scrollable ancestor elements of the
     * dragged element and the drag container element.
     *
     * @private
     * @memberof ItemDrag.prototype
     */
    ItemDrag.prototype._bindScrollListeners = function() {
      var gridContainer = this._getGrid()._element;
      var dragContainer = this._container;
      var scrollers = this._scrollers;
      var containerScrollers;
      var i;
  
      // Get dragged element's scrolling parents.
      scrollers.length = 0;
      getScrollParents(this._item._element, scrollers);
  
      // If drag container is defined and it's not the same element as grid
      // container then we need to add the grid container and it's scroll parents
      // to the elements which are going to be listener for scroll events.
      if (dragContainer !== gridContainer) {
        containerScrollers = [];
        getScrollParents(gridContainer, containerScrollers);
        containerScrollers.push(gridContainer);
        for (i = 0; i < containerScrollers.length; i++) {
          if (scrollers.indexOf(containerScrollers[i]) < 0) {
            scrollers.push(containerScrollers[i]);
          }
        }
      }
  
      // Bind scroll listeners.
      for (i = 0; i < scrollers.length; i++) {
        scrollers[i].addEventListener('scroll', this._onScroll);
      }
    };
  
    /**
     * Unbind currently bound drag scroll handlers from all scrollable ancestor
     * elements of the dragged element and the drag container element.
     *
     * @private
     * @memberof ItemDrag.prototype
     */
    ItemDrag.prototype._unbindScrollListeners = function() {
      var scrollers = this._scrollers;
      var i;
  
      for (i = 0; i < scrollers.length; i++) {
        scrollers[i].removeEventListener('scroll', this._onScroll);
      }
  
      scrollers.length = 0;
    };
  
    /**
     * Setup default start predicate.
     *
     * @private
     * @memberof ItemDrag.prototype
     * @param {Object} [options]
     * @returns {Object}
     */
    ItemDrag.prototype._setupStartPredicate = function(options) {
      var config = options || this._getGrid()._settings.dragStartPredicate || 0;
      return (this._startPredicateData = {
        distance: Math.abs(config.distance) || 0,
        delay: Math.max(config.delay, 0) || 0,
        handle: typeof config.handle === 'string' ? config.handle : false
      });
    };
  
    /**
     * Setup default start predicate handle.
     *
     * @private
     * @memberof ItemDrag.prototype
     * @param {Object} event
     * @returns {?HTMLElement}
     */
    ItemDrag.prototype._getStartPredicateHandle = function(event) {
      var predicate = this._startPredicateData;
      var element = this._item._element;
      var handleElement = element;
  
      // No handle, no hassle -> let's use the item element as the handle.
      if (!predicate.handle) return handleElement;
  
      // If there is a specific predicate handle defined, let's try to get it.
      handleElement = (event.changedPointers[0] || 0).target;
      while (handleElement && !elementMatches(handleElement, predicate.handle)) {
        handleElement = handleElement !== element ? handleElement.parentElement : null;
      }
      return handleElement || null;
    };
  
    /**
     * Unbind currently bound drag scroll handlers from all scrollable ancestor
     * elements of the dragged element and the drag container element.
     *
     * @private
     * @memberof ItemDrag.prototype
     * @param {Object} event
     * @returns {Boolean}
     */
    ItemDrag.prototype._resolveStartPredicate = function(event) {
      var predicate = this._startPredicateData;
      var pointer = event.changedPointers[0];
      var pageX = (pointer && pointer.pageX) || 0;
      var pageY = (pointer && pointer.pageY) || 0;
      var handleRect;
      var handleLeft;
      var handleTop;
      var handleWidth;
      var handleHeight;
  
      // If the moved distance is smaller than the threshold distance or there is
      // some delay left, ignore this predicate cycle.
      if (event.distance < predicate.distance || predicate.delay) return;
  
      // Get handle rect data.
      handleRect = predicate.handleElement.getBoundingClientRect();
      handleLeft = handleRect.left + (window.pageXOffset || 0);
      handleTop = handleRect.top + (window.pageYOffset || 0);
      handleWidth = handleRect.width;
      handleHeight = handleRect.height;
  
      // Reset predicate data.
      this._resetStartPredicate();
  
      // If the cursor is still within the handle let's start the drag.
      return (
        handleWidth &&
        handleHeight &&
        pageX >= handleLeft &&
        pageX < handleLeft + handleWidth &&
        pageY >= handleTop &&
        pageY < handleTop + handleHeight
      );
    };
  
    /**
     * Finalize start predicate.
     *
     * @private
     * @memberof ItemDrag.prototype
     * @param {Object} event
     */
    ItemDrag.prototype._finishStartPredicate = function(event) {
      var element = this._item._element;
  
      // Reset predicate.
      this._resetStartPredicate();
  
      // If the gesture can be interpreted as click let's try to open the element's
      // href url (if it is an anchor element).
      if (isClick(event)) openAnchorHref(element);
    };
  
    /**
     * Reset for default drag start predicate function.
     *
     * @private
     * @memberof ItemDrag.prototype
     */
    ItemDrag.prototype._resetStartPredicate = function() {
      var predicate = this._startPredicateData;
      if (predicate) {
        if (predicate.delayTimer) {
          predicate.delayTimer = window.clearTimeout(predicate.delayTimer);
        }
        this._startPredicateData = null;
      }
    };
  
    /**
     * Check (during drag) if an item is overlapping other items and based on
     * the configuration layout the items.
     *
     * @private
     * @memberof ItemDrag.prototype
     */
    ItemDrag.prototype._checkOverlap = function() {
      if (!this._isActive) return;
  
      var item = this._item;
      var settings = this._getGrid()._settings;
      var result;
      var currentGrid;
      var currentIndex;
      var targetGrid;
      var targetIndex;
      var sortAction;
      var isMigration;
  
      // Get overlap check result.
      if (typeof settings.dragSortPredicate === 'function') {
        result = settings.dragSortPredicate(item, this._lastEvent);
      } else {
        result = ItemDrag.defaultSortPredicate(item, settings.dragSortPredicate);
      }
  
      // Let's make sure the result object has a valid index before going further.
      if (!result || typeof result.index !== 'number') return;
  
      currentGrid = item.getGrid();
      targetGrid = result.grid || currentGrid;
      isMigration = currentGrid !== targetGrid;
      currentIndex = currentGrid._items.indexOf(item);
      targetIndex = normalizeArrayIndex(targetGrid._items, result.index, isMigration);
      sortAction = result.action === 'swap' ? 'swap' : 'move';
  
      // If the item was moved within it's current grid.
      if (!isMigration) {
        // Make sure the target index is not the current index.
        if (currentIndex !== targetIndex) {
          // Do the sort.
          (sortAction === 'swap' ? arraySwap : arrayMove)(
            currentGrid._items,
            currentIndex,
            targetIndex
          );
  
          // Emit move event.
          if (currentGrid._hasListeners(eventMove)) {
            currentGrid._emit(eventMove, {
              item: item,
              fromIndex: currentIndex,
              toIndex: targetIndex,
              action: sortAction
            });
          }
  
          // Layout the grid.
          currentGrid.layout();
        }
      }
  
      // If the item was moved to another grid.
      else {
        // Emit beforeSend event.
        if (currentGrid._hasListeners(eventBeforeSend)) {
          currentGrid._emit(eventBeforeSend, {
            item: item,
            fromGrid: currentGrid,
            fromIndex: currentIndex,
            toGrid: targetGrid,
            toIndex: targetIndex
          });
        }
  
        // Emit beforeReceive event.
        if (targetGrid._hasListeners(eventBeforeReceive)) {
          targetGrid._emit(eventBeforeReceive, {
            item: item,
            fromGrid: currentGrid,
            fromIndex: currentIndex,
            toGrid: targetGrid,
            toIndex: targetIndex
          });
        }
  
        // Update item's grid id reference.
        item._gridId = targetGrid._id;
  
        // Update drag instance's migrating indicator.
        this._isMigrating = item._gridId !== this._gridId;
  
        // Move item instance from current grid to target grid.
        currentGrid._items.splice(currentIndex, 1);
        arrayInsert(targetGrid._items, item, targetIndex);
  
        // Set sort data as null, which is an indicator for the item comparison
        // function that the sort data of this specific item should be fetched
        // lazily.
        item._sortData = null;
  
        // Emit send event.
        if (currentGrid._hasListeners(eventSend)) {
          currentGrid._emit(eventSend, {
            item: item,
            fromGrid: currentGrid,
            fromIndex: currentIndex,
            toGrid: targetGrid,
            toIndex: targetIndex
          });
        }
  
        // Emit receive event.
        if (targetGrid._hasListeners(eventReceive)) {
          targetGrid._emit(eventReceive, {
            item: item,
            fromGrid: currentGrid,
            fromIndex: currentIndex,
            toGrid: targetGrid,
            toIndex: targetIndex
          });
        }
  
        // Layout both grids.
        currentGrid.layout();
        targetGrid.layout();
      }
    };
  
    /**
     * If item is dragged into another grid, finish the migration process
     * gracefully.
     *
     * @private
     * @memberof ItemDrag.prototype
     */
    ItemDrag.prototype._finishMigration = function() {
      var item = this._item;
      var release = item._release;
      var element = item._element;
      var isActive = item._isActive;
      var targetGrid = item.getGrid();
      var targetGridElement = targetGrid._element;
      var targetSettings = targetGrid._settings;
      var targetContainer = targetSettings.dragContainer || targetGridElement;
      var currentSettings = this._getGrid()._settings;
      var currentContainer = element.parentNode;
      var translate;
      var offsetDiff;
  
      // Destroy current drag. Note that we need to set the migrating flag to
      // false first, because otherwise we create an infinite loop between this
      // and the drag.stop() method.
      this._isMigrating = false;
      this.destroy();
  
      // Remove current classnames.
      removeClass(element, currentSettings.itemClass);
      removeClass(element, currentSettings.itemVisibleClass);
      removeClass(element, currentSettings.itemHiddenClass);
  
      // Add new classnames.
      addClass(element, targetSettings.itemClass);
      addClass(element, isActive ? targetSettings.itemVisibleClass : targetSettings.itemHiddenClass);
  
      // Move the item inside the target container if it's different than the
      // current container.
      if (targetContainer !== currentContainer) {
        targetContainer.appendChild(element);
        offsetDiff = getOffsetDiff(currentContainer, targetContainer, true);
        translate = getTranslate(element);
        translate.x -= offsetDiff.left;
        translate.y -= offsetDiff.top;
      }
  
      // Update item's cached dimensions and sort data.
      item._refreshDimensions();
      item._refreshSortData();
  
      // Calculate the offset difference between target's drag container (if any)
      // and actual grid container element. We save it later for the release
      // process.
      offsetDiff = getOffsetDiff(targetContainer, targetGridElement, true);
      release._containerDiffX = offsetDiff.left;
      release._containerDiffY = offsetDiff.top;
  
      // Recreate item's drag handler.
      item._drag = targetSettings.dragEnabled ? new ItemDrag(item) : null;
  
      // Adjust the position of the item element if it was moved from a container
      // to another.
      if (targetContainer !== currentContainer) {
        element.style[transformProp] = getTranslateString(translate.x, translate.y);
      }
  
      // Update child element's styles to reflect the current visibility state.
      item._child.removeAttribute('style');
      setStyles(item._child, isActive ? targetSettings.visibleStyles : targetSettings.hiddenStyles);
  
      // Start the release.
      release.start();
    };
  
    /**
     * Drag start handler.
     *
     * @private
     * @memberof ItemDrag.prototype
     * @param {Object} event
     */
    ItemDrag.prototype._onStart = function(event) {
      var item = this._item;
  
      // If item is not active, don't start the drag.
      if (!item._isActive) return;
  
      var element = item._element;
      var grid = this._getGrid();
      var settings = grid._settings;
      var release = item._release;
      var migrate = item._migrate;
      var gridContainer = grid._element;
      var dragContainer = settings.dragContainer || gridContainer;
      var containingBlock = getContainingBlock(dragContainer, true);
      var translate = getTranslate(element);
      var currentLeft = translate.x;
      var currentTop = translate.y;
      var elementRect = element.getBoundingClientRect();
      var hasDragContainer = dragContainer !== gridContainer;
      var offsetDiff;
  
      // If grid container is not the drag container, we need to calculate the
      // offset difference between grid container and drag container's containing
      // element.
      if (hasDragContainer) {
        offsetDiff = getOffsetDiff(containingBlock, gridContainer);
      }
  
      // Stop current positioning animation.
      if (item.isPositioning()) {
        item._layout.stop(true, { transform: getTranslateString(currentLeft, currentTop) });
      }
  
      // Stop current migration animation.
      if (migrate._isActive) {
        currentLeft -= migrate._containerDiffX;
        currentTop -= migrate._containerDiffY;
        migrate.stop(true, { transform: getTranslateString(currentLeft, currentTop) });
      }
  
      // If item is being released reset release data.
      if (item.isReleasing()) release._reset();
  
      // Setup drag data.
      this._isActive = true;
      this._lastEvent = event;
      this._container = dragContainer;
      this._containingBlock = containingBlock;
      this._elementClientX = elementRect.left;
      this._elementClientY = elementRect.top;
      this._left = this._gridX = currentLeft;
      this._top = this._gridY = currentTop;
  
      // Emit dragInit event.
      grid._emit(eventDragInit, item, event);
  
      // If a specific drag container is set and it is different from the
      // grid's container element we need to cast some extra spells.
      if (hasDragContainer) {
        // Store the container offset diffs to drag data.
        this._containerDiffX = offsetDiff.left;
        this._containerDiffY = offsetDiff.top;
  
        // If the dragged element is a child of the drag container all we need to
        // do is setup the relative drag position data.
        if (element.parentNode === dragContainer) {
          this._gridX = currentLeft - this._containerDiffX;
          this._gridY = currentTop - this._containerDiffY;
        }
  
        // Otherwise we need to append the element inside the correct container,
        // setup the actual drag position data and adjust the element's translate
        // values to account for the DOM position shift.
        else {
          this._left = currentLeft + this._containerDiffX;
          this._top = currentTop + this._containerDiffY;
          dragContainer.appendChild(element);
          element.style[transformProp] = getTranslateString(this._left, this._top);
        }
      }
  
      // Set drag class and bind scrollers.
      addClass(element, settings.itemDraggingClass);
      this._bindScrollListeners();
  
      // Emit dragStart event.
      grid._emit(eventDragStart, item, event);
    };
  
    /**
     * Drag move handler.
     *
     * @private
     * @memberof ItemDrag.prototype
     * @param {Object} event
     */
    ItemDrag.prototype._onMove = function(event) {
      var item = this._item;
  
      // If item is not active, reset drag.
      if (!item._isActive) {
        this.stop();
        return;
      }
  
      var settings = this._getGrid()._settings;
      var axis = settings.dragAxis;
      var xDiff = event.deltaX - this._lastEvent.deltaX;
      var yDiff = event.deltaY - this._lastEvent.deltaY;
  
      // Update last event.
      this._lastEvent = event;
  
      // Update horizontal position data.
      if (axis !== 'y') {
        this._left += xDiff;
        this._gridX += xDiff;
        this._elementClientX += xDiff;
      }
  
      // Update vertical position data.
      if (axis !== 'x') {
        this._top += yDiff;
        this._gridY += yDiff;
        this._elementClientY += yDiff;
      }
  
      // Do move prepare/apply handling in the next tick.
      addMoveTick(item._id, this._prepareMove, this._applyMove);
    };
  
    /**
     * Prepare dragged item for moving.
     *
     * @private
     * @memberof ItemDrag.prototype
     */
    ItemDrag.prototype._prepareMove = function() {
      // Do nothing if item is not active.
      if (!this._item._isActive) return;
  
      // If drag sort is enabled -> check overlap.
      if (this._getGrid()._settings.dragSort) this._checkOverlapDebounce();
    };
  
    /**
     * Apply movement to dragged item.
     *
     * @private
     * @memberof ItemDrag.prototype
     */
    ItemDrag.prototype._applyMove = function() {
      var item = this._item;
  
      // Do nothing if item is not active.
      if (!item._isActive) return;
  
      // Update element's translateX/Y values.
      item._element.style[transformProp] = getTranslateString(this._left, this._top);
  
      // Emit dragMove event.
      this._getGrid()._emit(eventDragMove, item, this._lastEvent);
    };
  
    /**
     * Drag scroll handler.
     *
     * @private
     * @memberof ItemDrag.prototype
     * @param {Object} event
     */
    ItemDrag.prototype._onScroll = function(event) {
      var item = this._item;
  
      // If item is not active, reset drag.
      if (!item._isActive) {
        this.stop();
        return;
      }
  
      // Update last scroll event.
      this._lastScrollEvent = event;
  
      // Do scroll prepare/apply handling in the next tick.
      addScrollTick(item._id, this._prepareScroll, this._applyScroll);
    };
  
    /**
     * Prepare dragged item for scrolling.
     *
     * @private
     * @memberof ItemDrag.prototype
     */
    ItemDrag.prototype._prepareScroll = function() {
      var item = this._item;
  
      // If item is not active do nothing.
      if (!item._isActive) return;
  
      var element = item._element;
      var grid = this._getGrid();
      var settings = grid._settings;
      var axis = settings.dragAxis;
      var gridContainer = grid._element;
      var offsetDiff;
  
      // Calculate element's rect and x/y diff.
      var rect = element.getBoundingClientRect();
      var xDiff = this._elementClientX - rect.left;
      var yDiff = this._elementClientY - rect.top;
  
      // Update container diff.
      if (this._container !== gridContainer) {
        offsetDiff = getOffsetDiff(this._containingBlock, gridContainer);
        this._containerDiffX = offsetDiff.left;
        this._containerDiffY = offsetDiff.top;
      }
  
      // Update horizontal position data.
      if (axis !== 'y') {
        this._left += xDiff;
        this._gridX = this._left - this._containerDiffX;
      }
  
      // Update vertical position data.
      if (axis !== 'x') {
        this._top += yDiff;
        this._gridY = this._top - this._containerDiffY;
      }
  
      // Overlap handling.
      if (settings.dragSort) this._checkOverlapDebounce();
    };
  
    /**
     * Apply scroll to dragged item.
     *
     * @private
     * @memberof ItemDrag.prototype
     */
    ItemDrag.prototype._applyScroll = function() {
      var item = this._item;
  
      // If item is not active do nothing.
      if (!item._isActive) return;
  
      // Update element's translateX/Y values.
      item._element.style[transformProp] = getTranslateString(this._left, this._top);
  
      // Emit dragScroll event.
      this._getGrid()._emit(eventDragScroll, item, this._lastScrollEvent);
    };
  
    /**
     * Drag end handler.
     *
     * @private
     * @memberof ItemDrag.prototype
     * @param {Object} event
     */
    ItemDrag.prototype._onEnd = function(event) {
      var item = this._item;
      var element = item._element;
      var grid = this._getGrid();
      var settings = grid._settings;
      var release = item._release;
  
      // If item is not active, reset drag.
      if (!item._isActive) {
        this.stop();
        return;
      }
  
      // Cancel queued move and scroll ticks.
      cancelMoveTick(item._id);
      cancelScrollTick(item._id);
  
      // Finish currently queued overlap check.
      settings.dragSort && this._checkOverlapDebounce('finish');
  
      // Remove scroll listeners.
      this._unbindScrollListeners();
  
      // Setup release data.
      release._containerDiffX = this._containerDiffX;
      release._containerDiffY = this._containerDiffY;
  
      // Reset drag data.
      this._reset();
  
      // Remove drag class name from element.
      removeClass(element, settings.itemDraggingClass);
  
      // Emit dragEnd event.
      grid._emit(eventDragEnd, item, event);
  
      // Finish up the migration process or start the release process.
      this._isMigrating ? this._finishMigration() : release.start();
    };
  
    /**
     * Private helpers
     * ***************
     */
  
    /**
     * Prevent default.
     *
     * @param {Object} e
     */
    function preventDefault(e) {
      if (e.preventDefault) e.preventDefault();
    }
  
    /**
     * Calculate how many percent the intersection area of two rectangles is from
     * the maximum potential intersection area between the rectangles.
     *
     * @param {Rectangle} a
     * @param {Rectangle} b
     * @returns {Number}
     *   - A number between 0-100.
     */
    function getRectOverlapScore(a, b) {
      // Return 0 immediately if the rectangles do not overlap.
      if (
        a.left + a.width <= b.left ||
        b.left + b.width <= a.left ||
        a.top + a.height <= b.top ||
        b.top + b.height <= a.top
      ) {
        return 0;
      }
  
      // Calculate intersection area's width, height, max height and max width.
      var width = Math.min(a.left + a.width, b.left + b.width) - Math.max(a.left, b.left);
      var height = Math.min(a.top + a.height, b.top + b.height) - Math.max(a.top, b.top);
      var maxWidth = Math.min(a.width, b.width);
      var maxHeight = Math.min(a.height, b.height);
  
      return ((width * height) / (maxWidth * maxHeight)) * 100;
    }
  
    /**
     * Get element's scroll parents.
     *
     * @param {HTMLElement} element
     * @param {Array} [data]
     * @returns {HTMLElement[]}
     */
    function getScrollParents(element, data) {
      var ret = data || [];
      var parent = element.parentNode;
  
      //
      // If transformed elements leak fixed elements.
      //
  
      if (hasTransformLeak) {
        // If the element is fixed it can not have any scroll parents.
        if (getStyle(element, 'position') === 'fixed') return ret;
  
        // Find scroll parents.
        while (parent && parent !== document && parent !== document.documentElement) {
          if (isScrollable(parent)) ret.push(parent);
          parent = getStyle(parent, 'position') === 'fixed' ? null : parent.parentNode;
        }
  
        // If parent is not fixed element, add window object as the last scroll
        // parent.
        parent !== null && ret.push(window);
        return ret;
      }
  
      //
      // If fixed elements behave as defined in the W3C specification.
      //
  
      // Find scroll parents.
      while (parent && parent !== document) {
        // If the currently looped element is fixed ignore all parents that are
        // not transformed.
        if (getStyle(element, 'position') === 'fixed' && !isTransformed(parent)) {
          parent = parent.parentNode;
          continue;
        }
  
        // Add the parent element to return items if it is scrollable.
        if (isScrollable(parent)) ret.push(parent);
  
        // Update element and parent references.
        element = parent;
        parent = parent.parentNode;
      }
  
      // If the last item is the root element, replace it with window. The root
      // element scroll is propagated to the window.
      if (ret[ret.length - 1] === document.documentElement) {
        ret[ret.length - 1] = window;
      }
      // Otherwise add window as the last scroll parent.
      else {
        ret.push(window);
      }
  
      return ret;
    }
  
    /**
     * Check if an element is scrollable.
     *
     * @param {HTMLElement} element
     * @returns {Boolean}
     */
    function isScrollable(element) {
      var overflow = getStyle(element, 'overflow');
      if (overflow === 'auto' || overflow === 'scroll') return true;
  
      overflow = getStyle(element, 'overflow-x');
      if (overflow === 'auto' || overflow === 'scroll') return true;
  
      overflow = getStyle(element, 'overflow-y');
      if (overflow === 'auto' || overflow === 'scroll') return true;
  
      return false;
    }
  
    /**
     * Check if drag gesture can be interpreted as a click, based on final drag
     * event data.
     *
     * @param {Object} element
     * @returns {Boolean}
     */
    function isClick(event) {
      return Math.abs(event.deltaX) < 2 && Math.abs(event.deltaY) < 2 && event.deltaTime < 200;
    }
  
    /**
     * Check if an element is an anchor element and open the href url if possible.
     *
     * @param {HTMLElement} element
     */
    function openAnchorHref(element) {
      // Make sure the element is anchor element.
      if (element.tagName.toLowerCase() !== 'a') return;
  
      // Get href and make sure it exists.
      var href = element.getAttribute('href');
      if (!href) return;
  
      // Finally let's navigate to the link href.
      var target = element.getAttribute('target');
      if (target && target !== '_self') {
        window.open(href, target);
      } else {
        window.location.href = href;
      }
    }
  
    /**
     * Detects if transformed elements leak fixed elements. According W3C
     * transform rendering spec a transformed element should contain even fixed
     * elements. Meaning that fixed elements are positioned relative to the
     * closest transformed ancestor element instead of window. However, not every
     * browser follows the spec (IE and older Firefox). So we need to test it.
     * https://www.w3.org/TR/css3-2d-transforms/#transform-rendering
     *
     * Borrowed from Mezr (v0.6.1):
     * https://github.com/niklasramo/mezr/blob/0.6.1/mezr.js#L607
     */
    function checkTransformLeak() {
      // No transforms -> definitely leaks.
      if (!isTransformSupported) return true;
  
      // No body available -> can't check it.
      if (!document.body) return null;
  
      // Do the test.
      var elems = [0, 1].map(function(elem, isInner) {
        elem = document.createElement('div');
        elem.style.position = isInner ? 'fixed' : 'absolute';
        elem.style.display = 'block';
        elem.style.visibility = 'hidden';
        elem.style.left = isInner ? '0px' : '1px';
        elem.style[transformProp] = 'none';
        return elem;
      });
      var outer = document.body.appendChild(elems[0]);
      var inner = outer.appendChild(elems[1]);
      var left = inner.getBoundingClientRect().left;
      outer.style[transformProp] = 'scale(1)';
      var ret = left === inner.getBoundingClientRect().left;
      document.body.removeChild(outer);
      return ret;
    }
  
    /**
     * Queue constructor.
     *
     * @class
     */
    function Queue() {
      this._queue = [];
      this._isDestroyed = false;
    }
  
    /**
     * Public prototype methods
     * ************************
     */
  
    /**
     * Add callback to the queue.
     *
     * @public
     * @memberof Queue.prototype
     * @param {Function} callback
     * @returns {Queue}
     */
    Queue.prototype.add = function(callback) {
      if (this._isDestroyed) return this;
      this._queue.push(callback);
      return this;
    };
  
    /**
     * Process queue callbacks and reset the queue.
     *
     * @public
     * @memberof Queue.prototype
     * @param {*} arg1
     * @param {*} arg2
     * @returns {Queue}
     */
    Queue.prototype.flush = function(arg1, arg2) {
      if (this._isDestroyed) return this;
  
      var queue = this._queue;
      var length = queue.length;
      var i;
  
      // Quit early if the queue is empty.
      if (!length) return this;
  
      var singleCallback = length === 1;
      var snapshot = singleCallback ? queue[0] : queue.slice(0);
  
      // Reset queue.
      queue.length = 0;
  
      // If we only have a single callback let's just call it.
      if (singleCallback) {
        snapshot(arg1, arg2);
        return this;
      }
  
      // If we have multiple callbacks, let's process them.
      for (i = 0; i < length; i++) {
        snapshot[i](arg1, arg2);
        if (this._isDestroyed) break;
      }
  
      return this;
    };
  
    /**
     * Destroy Queue instance.
     *
     * @public
     * @memberof Queue.prototype
     * @returns {Queue}
     */
    Queue.prototype.destroy = function() {
      if (this._isDestroyed) return this;
  
      this._isDestroyed = true;
      this._queue.length = 0;
  
      return this;
    };
  
    /**
     * Layout manager for Item instance.
     *
     * @class
     * @param {Item} item
     */
    function ItemLayout(item) {
      this._item = item;
      this._isActive = false;
      this._isDestroyed = false;
      this._isInterrupted = false;
      this._currentStyles = {};
      this._targetStyles = {};
      this._currentLeft = 0;
      this._currentTop = 0;
      this._offsetLeft = 0;
      this._offsetTop = 0;
      this._skipNextAnimation = false;
      this._animateOptions = {
        onFinish: this._finish.bind(this)
      };
      this._queue = new Queue();
  
      // Bind animation handlers and finish method.
      this._setupAnimation = this._setupAnimation.bind(this);
      this._startAnimation = this._startAnimation.bind(this);
    }
  
    /**
     * Public prototype methods
     * ************************
     */
  
    /**
     * Start item layout based on it's current data.
     *
     * @public
     * @memberof ItemLayout.prototype
     * @param {Boolean} [instant=false]
     * @param {Function} [onFinish]
     * @returns {ItemLayout}
     */
    ItemLayout.prototype.start = function(instant, onFinish) {
      if (this._isDestroyed) return;
  
      var item = this._item;
      var element = item._element;
      var release = item._release;
      var gridSettings = item.getGrid()._settings;
      var isPositioning = this._isActive;
      var isJustReleased = release._isActive && release._isPositioningStarted === false;
      var animDuration = isJustReleased
        ? gridSettings.dragReleaseDuration
        : gridSettings.layoutDuration;
      var animEasing = isJustReleased ? gridSettings.dragReleaseEasing : gridSettings.layoutEasing;
      var animEnabled = !instant && !this._skipNextAnimation && animDuration > 0;
      var isAnimating;
  
      // If the item is currently positioning process current layout callback
      // queue with interrupted flag on.
      if (isPositioning) this._queue.flush(true, item);
  
      // Mark release positioning as started.
      if (isJustReleased) release._isPositioningStarted = true;
  
      // Push the callback to the callback queue.
      if (typeof onFinish === 'function') this._queue.add(onFinish);
  
      // If no animations are needed, easy peasy!
      if (!animEnabled) {
        this._updateOffsets();
        this._updateTargetStyles();
        isPositioning && cancelLayoutTick(item._id);
        isAnimating = item._animate.isAnimating();
        this.stop(false, this._targetStyles);
        !isAnimating && setStyles(element, this._targetStyles);
        this._skipNextAnimation = false;
        return this._finish();
      }
  
      // Set item active and store some data for the animation that is about to be
      // triggered.
      this._isActive = true;
      this._animateOptions.easing = animEasing;
      this._animateOptions.duration = animDuration;
      this._isInterrupted = isPositioning;
  
      // Start the item's layout animation in the next tick.
      addLayoutTick(item._id, this._setupAnimation, this._startAnimation);
  
      return this;
    };
  
    /**
     * Stop item's position animation if it is currently animating.
     *
     * @public
     * @memberof ItemLayout.prototype
     * @param {Boolean} [processCallbackQueue=false]
     * @param {Object} [targetStyles]
     * @returns {ItemLayout}
     */
    ItemLayout.prototype.stop = function(processCallbackQueue, targetStyles) {
      if (this._isDestroyed || !this._isActive) return this;
  
      var item = this._item;
  
      // Cancel animation init.
      cancelLayoutTick(item._id);
  
      // Stop animation.
      item._animate.stop(targetStyles);
  
      // Remove positioning class.
      removeClass(item._element, item.getGrid()._settings.itemPositioningClass);
  
      // Reset active state.
      this._isActive = false;
  
      // Process callback queue if needed.
      if (processCallbackQueue) this._queue.flush(true, item);
  
      return this;
    };
  
    /**
     * Destroy the instance and stop current animation if it is running.
     *
     * @public
     * @memberof ItemLayout.prototype
     * @returns {ItemLayout}
     */
    ItemLayout.prototype.destroy = function() {
      if (this._isDestroyed) return this;
      this.stop(true, {});
      this._queue.destroy();
      this._item = this._currentStyles = this._targetStyles = this._animateOptions = null;
      this._isDestroyed = true;
      return this;
    };
  
    /**
     * Private prototype methods
     * *************************
     */
  
    /**
     * Calculate and update item's current layout offset data.
     *
     * @private
     * @memberof ItemLayout.prototype
     */
    ItemLayout.prototype._updateOffsets = function() {
      if (this._isDestroyed) return;
  
      var item = this._item;
      var migrate = item._migrate;
      var release = item._release;
  
      this._offsetLeft = release._isActive
        ? release._containerDiffX
        : migrate._isActive
          ? migrate._containerDiffX
          : 0;
  
      this._offsetTop = release._isActive
        ? release._containerDiffY
        : migrate._isActive
          ? migrate._containerDiffY
          : 0;
    };
  
    /**
     * Calculate and update item's layout target styles.
     *
     * @private
     * @memberof ItemLayout.prototype
     */
    ItemLayout.prototype._updateTargetStyles = function() {
      if (this._isDestroyed) return;
  
      var item = this._item;
  
      this._targetStyles.transform = getTranslateString(
        item._left + this._offsetLeft,
        item._top + this._offsetTop
      );
    };
  
    /**
     * Finish item layout procedure.
     *
     * @private
     * @memberof ItemLayout.prototype
     */
    ItemLayout.prototype._finish = function() {
      if (this._isDestroyed) return;
  
      var item = this._item;
      var migrate = item._migrate;
      var release = item._release;
  
      // Mark the item as inactive and remove positioning classes.
      if (this._isActive) {
        this._isActive = false;
        removeClass(item._element, item.getGrid()._settings.itemPositioningClass);
      }
  
      // Finish up release and migration.
      if (release._isActive) release.stop();
      if (migrate._isActive) migrate.stop();
  
      // Process the callback queue.
      this._queue.flush(false, item);
    };
  
    /**
     * Prepare item for layout animation.
     *
     * @private
     * @memberof ItemLayout.prototype
     */
    ItemLayout.prototype._setupAnimation = function() {
      var element = this._item._element;
      var translate = getTranslate(element);
      this._currentLeft = translate.x;
      this._currentTop = translate.y;
    };
  
    /**
     * Start layout animation.
     *
     * @private
     * @memberof ItemLayout.prototype
     */
    ItemLayout.prototype._startAnimation = function() {
      var item = this._item;
      var element = item._element;
      var grid = item.getGrid();
      var settings = grid._settings;
  
      // Let's update the offset data and target styles.
      this._updateOffsets();
      this._updateTargetStyles();
  
      // If the item is already in correct position let's quit early.
      if (
        item._left === this._currentLeft - this._offsetLeft &&
        item._top === this._currentTop - this._offsetTop
      ) {
        if (this._isInterrupted) this.stop(false, this._targetStyles);
        this._isActive = false;
        this._finish();
        return;
      }
  
      // Set item's positioning class if needed.
      !this._isInterrupted && addClass(element, settings.itemPositioningClass);
  
      // Get current styles for animation.
      this._currentStyles.transform = getTranslateString(this._currentLeft, this._currentTop);
  
      // Animate.
      item._animate.start(this._currentStyles, this._targetStyles, this._animateOptions);
    };
  
    var tempStyles = {};
  
    /**
     * The migrate process handler constructor.
     *
     * @class
     * @param {Item} item
     */
    function ItemMigrate(item) {
      // Private props.
      this._item = item;
      this._isActive = false;
      this._isDestroyed = false;
      this._container = false;
      this._containerDiffX = 0;
      this._containerDiffY = 0;
    }
  
    /**
     * Public prototype methods
     * ************************
     */
  
    /**
     * Start the migrate process of an item.
     *
     * @public
     * @memberof ItemMigrate.prototype
     * @param {Grid} targetGrid
     * @param {GridSingleItemQuery} position
     * @param {HTMLElement} [container]
     * @returns {ItemMigrate}
     */
    ItemMigrate.prototype.start = function(targetGrid, position, container) {
      if (this._isDestroyed) return this;
  
      var item = this._item;
      var element = item._element;
      var isVisible = item.isVisible();
      var grid = item.getGrid();
      var settings = grid._settings;
      var targetSettings = targetGrid._settings;
      var targetElement = targetGrid._element;
      var targetItems = targetGrid._items;
      var currentIndex = grid._items.indexOf(item);
      var targetContainer = container || document.body;
      var targetIndex;
      var targetItem;
      var currentContainer;
      var offsetDiff;
      var containerDiff;
      var translate;
      var translateX;
      var translateY;
  
      // Get target index.
      if (typeof position === 'number') {
        targetIndex = normalizeArrayIndex(targetItems, position, true);
      } else {
        targetItem = targetGrid._getItem(position);
        /** @todo Consider throwing an error here instead of silently failing. */
        if (!targetItem) return this;
        targetIndex = targetItems.indexOf(targetItem);
      }
  
      // Get current translateX and translateY values if needed.
      if (item.isPositioning() || this._isActive || item.isReleasing()) {
        translate = getTranslate(element);
        translateX = translate.x;
        translateY = translate.y;
      }
  
      // Abort current positioning.
      if (item.isPositioning()) {
        item._layout.stop(true, { transform: getTranslateString(translateX, translateY) });
      }
  
      // Abort current migration.
      if (this._isActive) {
        translateX -= this._containerDiffX;
        translateY -= this._containerDiffY;
        this.stop(true, { transform: getTranslateString(translateX, translateY) });
      }
  
      // Abort current release.
      if (item.isReleasing()) {
        translateX -= item._release._containerDiffX;
        translateY -= item._release._containerDiffY;
        item._release.stop(true, { transform: getTranslateString(translateX, translateY) });
      }
  
      // Stop current visibility animations.
      item._visibility._stopAnimation();
  
      // Destroy current drag.
      if (item._drag) item._drag.destroy();
  
      // Process current visibility animation queue.
      item._visibility._queue.flush(true, item);
  
      // Emit beforeSend event.
      if (grid._hasListeners(eventBeforeSend)) {
        grid._emit(eventBeforeSend, {
          item: item,
          fromGrid: grid,
          fromIndex: currentIndex,
          toGrid: targetGrid,
          toIndex: targetIndex
        });
      }
  
      // Emit beforeReceive event.
      if (targetGrid._hasListeners(eventBeforeReceive)) {
        targetGrid._emit(eventBeforeReceive, {
          item: item,
          fromGrid: grid,
          fromIndex: currentIndex,
          toGrid: targetGrid,
          toIndex: targetIndex
        });
      }
  
      // Remove current classnames.
      removeClass(element, settings.itemClass);
      removeClass(element, settings.itemVisibleClass);
      removeClass(element, settings.itemHiddenClass);
  
      // Add new classnames.
      addClass(element, targetSettings.itemClass);
      addClass(element, isVisible ? targetSettings.itemVisibleClass : targetSettings.itemHiddenClass);
  
      // Move item instance from current grid to target grid.
      grid._items.splice(currentIndex, 1);
      arrayInsert(targetItems, item, targetIndex);
  
      // Update item's grid id reference.
      item._gridId = targetGrid._id;
  
      // Get current container.
      currentContainer = element.parentNode;
  
      // Move the item inside the target container if it's different than the
      // current container.
      if (targetContainer !== currentContainer) {
        targetContainer.appendChild(element);
        offsetDiff = getOffsetDiff(targetContainer, currentContainer, true);
        if (!translate) {
          translate = getTranslate(element);
          translateX = translate.x;
          translateY = translate.y;
        }
        element.style[transformProp] = getTranslateString(
          translateX + offsetDiff.left,
          translateY + offsetDiff.top
        );
      }
  
      // Update child element's styles to reflect the current visibility state.
      item._child.removeAttribute('style');
      setStyles(item._child, isVisible ? targetSettings.visibleStyles : targetSettings.hiddenStyles);
  
      // Update display style.
      element.style.display = isVisible ? 'block' : 'hidden';
  
      // Get offset diff for the migration data.
      containerDiff = getOffsetDiff(targetContainer, targetElement, true);
  
      // Update item's cached dimensions and sort data.
      item._refreshDimensions();
      item._refreshSortData();
  
      // Create new drag handler.
      item._drag = targetSettings.dragEnabled ? new ItemDrag(item) : null;
  
      // Setup migration data.
      this._isActive = true;
      this._container = targetContainer;
      this._containerDiffX = containerDiff.left;
      this._containerDiffY = containerDiff.top;
  
      // Emit send event.
      if (grid._hasListeners(eventSend)) {
        grid._emit(eventSend, {
          item: item,
          fromGrid: grid,
          fromIndex: currentIndex,
          toGrid: targetGrid,
          toIndex: targetIndex
        });
      }
  
      // Emit receive event.
      if (targetGrid._hasListeners(eventReceive)) {
        targetGrid._emit(eventReceive, {
          item: item,
          fromGrid: grid,
          fromIndex: currentIndex,
          toGrid: targetGrid,
          toIndex: targetIndex
        });
      }
  
      return this;
    };
  
    /**
     * End the migrate process of an item. This method can be used to abort an
     * ongoing migrate process (animation) or finish the migrate process.
     *
     * @public
     * @memberof ItemMigrate.prototype
     * @param {Boolean} [abort=false]
     *  - Should the migration be aborted?
     * @param {Object} [currentStyles]
     *  - Optional current translateX and translateY styles.
     * @returns {ItemMigrate}
     */
    ItemMigrate.prototype.stop = function(abort, currentStyles) {
      if (this._isDestroyed || !this._isActive) return this;
  
      var item = this._item;
      var element = item._element;
      var grid = item.getGrid();
      var gridElement = grid._element;
      var translate;
  
      if (this._container !== gridElement) {
        if (!currentStyles) {
          if (abort) {
            translate = getTranslate(element);
            tempStyles.transform = getTranslateString(
              translate.x - this._containerDiffX,
              translate.y - this._containerDiffY
            );
          } else {
            tempStyles.transform = getTranslateString(item._left, item._top);
          }
          currentStyles = tempStyles;
        }
        gridElement.appendChild(element);
        setStyles(element, currentStyles);
      }
  
      this._isActive = false;
      this._container = null;
      this._containerDiffX = 0;
      this._containerDiffY = 0;
  
      return this;
    };
  
    /**
     * Destroy instance.
     *
     * @public
     * @memberof ItemMigrate.prototype
     * @returns {ItemMigrate}
     */
    ItemMigrate.prototype.destroy = function() {
      if (this._isDestroyed) return this;
      this.stop(true);
      this._item = null;
      this._isDestroyed = true;
      return this;
    };
  
    var tempStyles$1 = {};
  
    /**
     * The release process handler constructor. Although this might seem as proper
     * fit for the drag process this needs to be separated into it's own logic
     * because there might be a scenario where drag is disabled, but the release
     * process still needs to be implemented (dragging from a grid to another).
     *
     * @class
     * @param {Item} item
     */
    function ItemRelease(item) {
      this._item = item;
      this._isActive = false;
      this._isDestroyed = false;
      this._isPositioningStarted = false;
      this._containerDiffX = 0;
      this._containerDiffY = 0;
    }
  
    /**
     * Public prototype methods
     * ************************
     */
  
    /**
     * Start the release process of an item.
     *
     * @public
     * @memberof ItemRelease.prototype
     * @returns {ItemRelease}
     */
    ItemRelease.prototype.start = function() {
      if (this._isDestroyed || this._isActive) return this;
  
      var item = this._item;
      var grid = item.getGrid();
  
      // Flag release as active.
      this._isActive = true;
  
      // Add release class name to the released element.
      addClass(item._element, grid._settings.itemReleasingClass);
  
      // Emit dragReleaseStart event.
      grid._emit(eventDragReleaseStart, item);
  
      // Position the released item.
      item._layout.start(false);
  
      return this;
    };
  
    /**
     * End the release process of an item. This method can be used to abort an
     * ongoing release process (animation) or finish the release process.
     *
     * @public
     * @memberof ItemRelease.prototype
     * @param {Boolean} [abort=false]
     *  - Should the release be aborted? When true, the release end event won't be
     *    emitted. Set to true only when you need to abort the release process
     *    while the item is animating to it's position.
     * @param {Object} [currentStyles]
     *  - Optional current translateX and translateY styles.
     * @returns {ItemRelease}
     */
    ItemRelease.prototype.stop = function(abort, currentStyles) {
      if (this._isDestroyed || !this._isActive) return this;
  
      var item = this._item;
      var element = item._element;
      var grid = item.getGrid();
      var container = grid._element;
      var translate;
  
      // Reset data and remove releasing class name from the element.
      this._reset();
  
      // If the released element is outside the grid's container element put it
      // back there and adjust position accordingly.
      if (element.parentNode !== container) {
        if (!currentStyles) {
          if (abort) {
            translate = getTranslate(element);
            tempStyles$1.transform = getTranslateString(
              translate.x - this._containerDiffX,
              translate.y - this._containerDiffY
            );
          } else {
            tempStyles$1.transform = getTranslateString(item._left, item._top);
          }
          currentStyles = tempStyles$1;
        }
        container.appendChild(element);
        setStyles(element, currentStyles);
      }
  
      // Emit dragReleaseEnd event.
      if (!abort) grid._emit(eventDragReleaseEnd, item);
  
      return this;
    };
  
    /**
     * Destroy instance.
     *
     * @public
     * @memberof ItemRelease.prototype
     * @returns {ItemRelease}
     */
    ItemRelease.prototype.destroy = function() {
      if (this._isDestroyed) return this;
      this.stop(true);
      this._item = null;
      this._isDestroyed = true;
      return this;
    };
  
    /**
     * Private prototype methods
     * *************************
     */
  
    /**
     * Reset public data and remove releasing class.
     *
     * @private
     * @memberof ItemRelease.prototype
     */
    ItemRelease.prototype._reset = function() {
      if (this._isDestroyed) return;
      var item = this._item;
      this._isActive = false;
      this._isPositioningStarted = false;
      this._containerDiffX = 0;
      this._containerDiffY = 0;
      removeClass(item._element, item.getGrid()._settings.itemReleasingClass);
    };
  
    /**
     * Get current values of the provided styles definition object.
     *
     * @param {HTMLElement} element
     * @param {Object} styles
     * @return {Object}
     */
    function getCurrentStyles(element, styles) {
      var current = {};
      for (var prop in styles) {
        current[prop] = getStyle(element, getStyleName(prop));
      }
      return current;
    }
  
    /**
     * Visibility manager for Item instance.
     *
     * @class
     * @param {Item} item
     */
    function ItemVisibility(item) {
      var isActive = item._isActive;
      var element = item._element;
      var settings = item.getGrid()._settings;
  
      this._item = item;
      this._isDestroyed = false;
  
      // Set up visibility states.
      this._isHidden = !isActive;
      this._isHiding = false;
      this._isShowing = false;
  
      // Callback queue.
      this._queue = new Queue();
  
      // Bind show/hide finishers.
      this._finishShow = this._finishShow.bind(this);
      this._finishHide = this._finishHide.bind(this);
  
      // Force item to be either visible or hidden on init.
      element.style.display = isActive ? 'block' : 'none';
  
      // Set visible/hidden class.
      addClass(element, isActive ? settings.itemVisibleClass : settings.itemHiddenClass);
  
      // Set initial styles for the child element.
      setStyles(item._child, isActive ? settings.visibleStyles : settings.hiddenStyles);
    }
  
    /**
     * Public prototype methods
     * ************************
     */
  
    /**
     * Show item.
     *
     * @public
     * @memberof ItemVisibility.prototype
     * @param {Boolean} instant
     * @param {Function} [onFinish]
     * @returns {ItemVisibility}
     */
    ItemVisibility.prototype.show = function(instant, onFinish) {
      if (this._isDestroyed) return this;
  
      var item = this._item;
      var element = item._element;
      var queue = this._queue;
      var callback = typeof onFinish === 'function' ? onFinish : null;
      var grid = item.getGrid();
      var settings = grid._settings;
  
      // If item is visible call the callback and be done with it.
      if (!this._isShowing && !this._isHidden) {
        callback && callback(false, item);
        return this;
      }
  
      // If item is showing and does not need to be shown instantly, let's just
      // push callback to the callback queue and be done with it.
      if (this._isShowing && !instant) {
        callback && queue.add(callback);
        return this;
      }
  
      // If the item is hiding or hidden process the current visibility callback
      // queue with the interrupted flag active, update classes and set display
      // to block if necessary.
      if (!this._isShowing) {
        queue.flush(true, item);
        removeClass(element, settings.itemHiddenClass);
        addClass(element, settings.itemVisibleClass);
        if (!this._isHiding) element.style.display = 'block';
      }
  
      // Push callback to the callback queue.
      callback && queue.add(callback);
  
      // Update visibility states.
      item._isActive = this._isShowing = true;
      this._isHiding = this._isHidden = false;
  
      // Finally let's start show animation.
      this._startAnimation(true, instant, this._finishShow);
  
      return this;
    };
  
    /**
     * Hide item.
     *
     * @public
     * @memberof ItemVisibility.prototype
     * @param {Boolean} instant
     * @param {Function} [onFinish]
     * @returns {ItemVisibility}
     */
    ItemVisibility.prototype.hide = function(instant, onFinish) {
      if (this._isDestroyed) return this;
  
      var item = this._item;
      var element = item._element;
      var queue = this._queue;
      var callback = typeof onFinish === 'function' ? onFinish : null;
      var grid = item.getGrid();
      var settings = grid._settings;
  
      // If item is already hidden call the callback and be done with it.
      if (!this._isHiding && this._isHidden) {
        callback && callback(false, item);
        return this;
      }
  
      // If item is hiding and does not need to be hidden instantly, let's just
      // push callback to the callback queue and be done with it.
      if (this._isHiding && !instant) {
        callback && queue.add(callback);
        return this;
      }
  
      // If the item is showing or visible process the current visibility callback
      // queue with the interrupted flag active, update classes and set display
      // to block if necessary.
      if (!this._isHiding) {
        queue.flush(true, item);
        addClass(element, settings.itemHiddenClass);
        removeClass(element, settings.itemVisibleClass);
      }
  
      // Push callback to the callback queue.
      callback && queue.add(callback);
  
      // Update visibility states.
      this._isHidden = this._isHiding = true;
      item._isActive = this._isShowing = false;
  
      // Finally let's start hide animation.
      this._startAnimation(false, instant, this._finishHide);
  
      return this;
    };
  
    /**
     * Destroy the instance and stop current animation if it is running.
     *
     * @public
     * @memberof ItemVisibility.prototype
     * @returns {ItemVisibility}
     */
    ItemVisibility.prototype.destroy = function() {
      if (this._isDestroyed) return this;
  
      var item = this._item;
      var element = item._element;
      var grid = item.getGrid();
      var queue = this._queue;
      var settings = grid._settings;
  
      // Stop visibility animation.
      this._stopAnimation({});
  
      // Fire all uncompleted callbacks with interrupted flag and destroy the queue.
      queue.flush(true, item).destroy();
  
      // Remove visible/hidden classes.
      removeClass(element, settings.itemVisibleClass);
      removeClass(element, settings.itemHiddenClass);
  
      // Reset state.
      this._item = null;
      this._isHiding = this._isShowing = false;
      this._isDestroyed = this._isHidden = true;
  
      return this;
    };
  
    /**
     * Private prototype methods
     * *************************
     */
  
    /**
     * Start visibility animation.
     *
     * @private
     * @memberof ItemVisibility.prototype
     * @param {Boolean} toVisible
     * @param {Boolean} [instant]
     * @param {Function} [onFinish]
     */
    ItemVisibility.prototype._startAnimation = function(toVisible, instant, onFinish) {
      if (this._isDestroyed) return;
  
      var item = this._item;
      var settings = item.getGrid()._settings;
      var targetStyles = toVisible ? settings.visibleStyles : settings.hiddenStyles;
      var duration = parseInt(toVisible ? settings.showDuration : settings.hideDuration) || 0;
      var easing = (toVisible ? settings.showEasing : settings.hideEasing) || 'ease';
      var isInstant = instant || duration <= 0;
      var currentStyles;
  
      // No target styles? Let's quit early.
      if (!targetStyles) {
        onFinish && onFinish();
        return;
      }
  
      // Cancel queued visibility tick.
      cancelVisibilityTick(item._id);
  
      // If we need to apply the styles instantly without animation.
      if (isInstant) {
        if (item._animateChild.isAnimating()) {
          item._animateChild.stop(targetStyles);
        } else {
          setStyles(item._child, targetStyles);
        }
        onFinish && onFinish();
        return;
      }
  
      // Start the animation in the next tick (to avoid layout thrashing).
      addVisibilityTick(
        item._id,
        function() {
          currentStyles = getCurrentStyles(item._child, targetStyles);
        },
        function() {
          item._animateChild.start(currentStyles, targetStyles, {
            duration: duration,
            easing: easing,
            onFinish: onFinish
          });
        }
      );
    };
  
    /**
     * Stop visibility animation.
     *
     * @private
     * @memberof ItemVisibility.prototype
     * @param {Object} [targetStyles]
     */
    ItemVisibility.prototype._stopAnimation = function(targetStyles) {
      if (this._isDestroyed) return;
      var item = this._item;
      cancelVisibilityTick(item._id);
      item._animateChild.stop(targetStyles);
    };
  
    /**
     * Finish show procedure.
     *
     * @private
     * @memberof ItemVisibility.prototype
     */
    ItemVisibility.prototype._finishShow = function() {
      if (this._isHidden) return;
      this._isShowing = false;
      this._queue.flush(false, this._item);
    };
  
    /**
     * Finish hide procedure.
     *
     * @private
     * @memberof ItemVisibility.prototype
     */
    var finishStyles = {};
    ItemVisibility.prototype._finishHide = function() {
      if (!this._isHidden) return;
      var item = this._item;
      this._isHiding = false;
      finishStyles.transform = getTranslateString(0, 0);
      item._layout.stop(true, finishStyles);
      item._element.style.display = 'none';
      this._queue.flush(false, item);
    };
  
    var id = 0;
  
    /**
     * Returns a unique numeric id (increments a base value on every call).
     * @returns {Number}
     */
    function createUid() {
      return ++id;
    }
  
    /**
     * Creates a new Item instance for a Grid instance.
     *
     * @class
     * @param {Grid} grid
     * @param {HTMLElement} element
     * @param {Boolean} [isActive]
     */
    function Item(grid, element, isActive) {
      var settings = grid._settings;
  
      // Create instance id.
      this._id = createUid();
  
      // Reference to connected Grid instance's id.
      this._gridId = grid._id;
  
      // Destroyed flag.
      this._isDestroyed = false;
  
      // Set up initial positions.
      this._left = 0;
      this._top = 0;
  
      // The elements.
      this._element = element;
      this._child = element.children[0];
  
      // If the provided item element is not a direct child of the grid container
      // element, append it to the grid container.
      if (element.parentNode !== grid._element) {
        grid._element.appendChild(element);
      }
  
      // Set item class.
      addClass(element, settings.itemClass);
  
      // If isActive is not defined, let's try to auto-detect it.
      if (typeof isActive !== 'boolean') {
        isActive = getStyle(element, 'display') !== 'none';
      }
  
      // Set up active state (defines if the item is considered part of the layout
      // or not).
      this._isActive = isActive;
  
      // Set element's initial position styles.
      element.style.left = '0';
      element.style.top = '0';
      element.style[transformProp] = getTranslateString(0, 0);
  
      // Initiate item's animation controllers.
      this._animate = new ItemAnimate(element);
      this._animateChild = new ItemAnimate(this._child);
  
      // Setup visibility handler.
      this._visibility = new ItemVisibility(this);
  
      // Set up layout handler.
      this._layout = new ItemLayout(this);
  
      // Set up migration handler data.
      this._migrate = new ItemMigrate(this);
  
      // Set up release handler
      this._release = new ItemRelease(this);
  
      // Set up drag handler.
      this._drag = settings.dragEnabled ? new ItemDrag(this) : null;
  
      // Set up the initial dimensions and sort data.
      this._refreshDimensions();
      this._refreshSortData();
    }
  
    /**
     * Public prototype methods
     * ************************
     */
  
    /**
     * Get the instance grid reference.
     *
     * @public
     * @memberof Item.prototype
     * @returns {Grid}
     */
    Item.prototype.getGrid = function() {
      return gridInstances[this._gridId];
    };
  
    /**
     * Get the instance element.
     *
     * @public
     * @memberof Item.prototype
     * @returns {HTMLElement}
     */
    Item.prototype.getElement = function() {
      return this._element;
    };
  
    /**
     * Get instance element's cached width.
     *
     * @public
     * @memberof Item.prototype
     * @returns {Number}
     */
    Item.prototype.getWidth = function() {
      return this._width;
    };
  
    /**
     * Get instance element's cached height.
     *
     * @public
     * @memberof Item.prototype
     * @returns {Number}
     */
    Item.prototype.getHeight = function() {
      return this._height;
    };
  
    /**
     * Get instance element's cached margins.
     *
     * @public
     * @memberof Item.prototype
     * @returns {Object}
     *   - The returned object contains left, right, top and bottom properties
     *     which indicate the item element's cached margins.
     */
    Item.prototype.getMargin = function() {
      return {
        left: this._marginLeft,
        right: this._marginRight,
        top: this._marginTop,
        bottom: this._marginBottom
      };
    };
  
    /**
     * Get instance element's cached position.
     *
     * @public
     * @memberof Item.prototype
     * @returns {Object}
     *   - The returned object contains left and top properties which indicate the
     *     item element's cached position in the grid.
     */
    Item.prototype.getPosition = function() {
      return {
        left: this._left,
        top: this._top
      };
    };
  
    /**
     * Is the item active?
     *
     * @public
     * @memberof Item.prototype
     * @returns {Boolean}
     */
    Item.prototype.isActive = function() {
      return this._isActive;
    };
  
    /**
     * Is the item visible?
     *
     * @public
     * @memberof Item.prototype
     * @returns {Boolean}
     */
    Item.prototype.isVisible = function() {
      return !!this._visibility && !this._visibility._isHidden;
    };
  
    /**
     * Is the item being animated to visible?
     *
     * @public
     * @memberof Item.prototype
     * @returns {Boolean}
     */
    Item.prototype.isShowing = function() {
      return !!(this._visibility && this._visibility._isShowing);
    };
  
    /**
     * Is the item being animated to hidden?
     *
     * @public
     * @memberof Item.prototype
     * @returns {Boolean}
     */
    Item.prototype.isHiding = function() {
      return !!(this._visibility && this._visibility._isHiding);
    };
  
    /**
     * Is the item positioning?
     *
     * @public
     * @memberof Item.prototype
     * @returns {Boolean}
     */
    Item.prototype.isPositioning = function() {
      return !!(this._layout && this._layout._isActive);
    };
  
    /**
     * Is the item being dragged?
     *
     * @public
     * @memberof Item.prototype
     * @returns {Boolean}
     */
    Item.prototype.isDragging = function() {
      return !!(this._drag && this._drag._isActive);
    };
  
    /**
     * Is the item being released?
     *
     * @public
     * @memberof Item.prototype
     * @returns {Boolean}
     */
    Item.prototype.isReleasing = function() {
      return !!(this._release && this._release._isActive);
    };
  
    /**
     * Is the item destroyed?
     *
     * @public
     * @memberof Item.prototype
     * @returns {Boolean}
     */
    Item.prototype.isDestroyed = function() {
      return this._isDestroyed;
    };
  
    /**
     * Private prototype methods
     * *************************
     */
  
    /**
     * Recalculate item's dimensions.
     *
     * @private
     * @memberof Item.prototype
     */
    Item.prototype._refreshDimensions = function() {
      if (this._isDestroyed || this._visibility._isHidden) return;
  
      var element = this._element;
      var rect = element.getBoundingClientRect();
  
      // Calculate width and height.
      this._width = rect.width;
      this._height = rect.height;
  
      // Calculate margins (ignore negative margins).
      this._marginLeft = Math.max(0, getStyleAsFloat(element, 'margin-left'));
      this._marginRight = Math.max(0, getStyleAsFloat(element, 'margin-right'));
      this._marginTop = Math.max(0, getStyleAsFloat(element, 'margin-top'));
      this._marginBottom = Math.max(0, getStyleAsFloat(element, 'margin-bottom'));
    };
  
    /**
     * Fetch and store item's sort data.
     *
     * @private
     * @memberof Item.prototype
     */
    Item.prototype._refreshSortData = function() {
      if (this._isDestroyed) return;
  
      var data = (this._sortData = {});
      var getters = this.getGrid()._settings.sortData;
      var prop;
  
      for (prop in getters) {
        data[prop] = getters[prop](this, this._element);
      }
    };
  
    /**
     * Destroy item instance.
     *
     * @private
     * @memberof Item.prototype
     * @param {Boolean} [removeElement=false]
     */
    Item.prototype._destroy = function(removeElement) {
      if (this._isDestroyed) return;
  
      var element = this._element;
      var grid = this.getGrid();
      var settings = grid._settings;
      var index = grid._items.indexOf(this);
  
      // Destroy handlers.
      this._release.destroy();
      this._migrate.destroy();
      this._layout.destroy();
      this._visibility.destroy();
      this._animate.destroy();
      this._animateChild.destroy();
      this._drag && this._drag.destroy();
  
      // Remove all inline styles.
      element.removeAttribute('style');
      this._child.removeAttribute('style');
  
      // Remove item class.
      removeClass(element, settings.itemClass);
  
      // Remove item from Grid instance if it still exists there.
      index > -1 && grid._items.splice(index, 1);
  
      // Remove element from DOM.
      removeElement && element.parentNode.removeChild(element);
  
      // Reset state.
      this._isActive = false;
      this._isDestroyed = true;
    };
  
    /**
     * This is the default layout algorithm for Muuri. Based on MAXRECTS approach
     * as described by Jukka Jylänki in his survey: "A Thousand Ways to Pack the
     * Bin - A Practical Approach to Two-Dimensional Rectangle Bin Packing.".
     *
     * @class
     */
    function Packer() {
      this._slots = [];
      this._slotSizes = [];
      this._freeSlots = [];
      this._newSlots = [];
      this._rectItem = {};
      this._rectStore = [];
      this._rectId = 0;
  
      // The layout return data, which will be populated in getLayout.
      this._layout = {
        slots: null,
        setWidth: false,
        setHeight: false,
        width: false,
        height: false
      };
  
      // Bind sort handlers.
      this._sortRectsLeftTop = this._sortRectsLeftTop.bind(this);
      this._sortRectsTopLeft = this._sortRectsTopLeft.bind(this);
    }
  
    /**
     * @public
     * @memberof Packer.prototype
     * @param {Item[]} items
     * @param {Number} width
     * @param {Number} height
     * @param {Number[]} [slots]
     * @param {Object} [options]
     * @param {Boolean} [options.fillGaps=false]
     * @param {Boolean} [options.horizontal=false]
     * @param {Boolean} [options.alignRight=false]
     * @param {Boolean} [options.alignBottom=false]
     * @returns {LayoutData}
     */
    Packer.prototype.getLayout = function(items, width, height, slots, options) {
      var layout = this._layout;
      var fillGaps = !!(options && options.fillGaps);
      var isHorizontal = !!(options && options.horizontal);
      var alignRight = !!(options && options.alignRight);
      var alignBottom = !!(options && options.alignBottom);
      var rounding = !!(options && options.rounding);
      var slotSizes = this._slotSizes;
      var i;
  
      // Reset layout data.
      layout.slots = slots ? slots : this._slots;
      layout.width = isHorizontal ? 0 : rounding ? Math.round(width) : width;
      layout.height = !isHorizontal ? 0 : rounding ? Math.round(height) : height;
      layout.setWidth = isHorizontal;
      layout.setHeight = !isHorizontal;
  
      // Make sure slots and slot size arrays are reset.
      layout.slots.length = 0;
      slotSizes.length = 0;
  
      // No need to go further if items do not exist.
      if (!items.length) return layout;
  
      // Find slots for items.
      for (i = 0; i < items.length; i++) {
        this._addSlot(items[i], isHorizontal, fillGaps, rounding, alignRight || alignBottom);
      }
  
      // If the alignment is set to right we need to adjust the results.
      if (alignRight) {
        for (i = 0; i < layout.slots.length; i = i + 2) {
          layout.slots[i] = layout.width - (layout.slots[i] + slotSizes[i]);
        }
      }
  
      // If the alignment is set to bottom we need to adjust the results.
      if (alignBottom) {
        for (i = 1; i < layout.slots.length; i = i + 2) {
          layout.slots[i] = layout.height - (layout.slots[i] + slotSizes[i]);
        }
      }
  
      // Reset slots arrays and rect id.
      slotSizes.length = 0;
      this._freeSlots.length = 0;
      this._newSlots.length = 0;
      this._rectId = 0;
  
      return layout;
    };
  
    /**
     * Calculate position for the layout item. Returns the left and top position
     * of the item in pixels.
     *
     * @private
     * @memberof Packer.prototype
     * @param {Item} item
     * @param {Boolean} isHorizontal
     * @param {Boolean} fillGaps
     * @param {Boolean} rounding
     * @returns {Array}
     */
    Packer.prototype._addSlot = (function() {
      var leeway = 0.001;
      var itemSlot = {};
      return function(item, isHorizontal, fillGaps, rounding, trackSize) {
        var layout = this._layout;
        var freeSlots = this._freeSlots;
        var newSlots = this._newSlots;
        var rect;
        var rectId;
        var potentialSlots;
        var ignoreCurrentSlots;
        var i;
        var ii;
  
        // Reset new slots.
        newSlots.length = 0;
  
        // Set item slot initial data.
        itemSlot.left = null;
        itemSlot.top = null;
        itemSlot.width = item._width + item._marginLeft + item._marginRight;
        itemSlot.height = item._height + item._marginTop + item._marginBottom;
  
        // Round item slot width and height if needed.
        if (rounding) {
          itemSlot.width = Math.round(itemSlot.width);
          itemSlot.height = Math.round(itemSlot.height);
        }
  
        // Try to find a slot for the item.
        for (i = 0; i < freeSlots.length; i++) {
          rectId = freeSlots[i];
          if (!rectId) continue;
          rect = this._getRect(rectId);
          if (itemSlot.width <= rect.width + leeway && itemSlot.height <= rect.height + leeway) {
            itemSlot.left = rect.left;
            itemSlot.top = rect.top;
            break;
          }
        }
  
        // If no slot was found for the item.
        if (itemSlot.left === null) {
          // Position the item in to the bottom left (vertical mode) or top right
          // (horizontal mode) of the grid.
          itemSlot.left = !isHorizontal ? 0 : layout.width;
          itemSlot.top = !isHorizontal ? layout.height : 0;
  
          // If gaps don't needs filling do not add any current slots to the new
          // slots array.
          if (!fillGaps) {
            ignoreCurrentSlots = true;
          }
        }
  
        // In vertical mode, if the item's bottom overlaps the grid's bottom.
        if (!isHorizontal && itemSlot.top + itemSlot.height > layout.height) {
          // If item is not aligned to the left edge, create a new slot.
          if (itemSlot.left > 0) {
            newSlots.push(this._addRect(0, layout.height, itemSlot.left, Infinity));
          }
  
          // If item is not aligned to the right edge, create a new slot.
          if (itemSlot.left + itemSlot.width < layout.width) {
            newSlots.push(
              this._addRect(
                itemSlot.left + itemSlot.width,
                layout.height,
                layout.width - itemSlot.left - itemSlot.width,
                Infinity
              )
            );
          }
  
          // Update grid height.
          layout.height = itemSlot.top + itemSlot.height;
        }
  
        // In horizontal mode, if the item's right overlaps the grid's right edge.
        if (isHorizontal && itemSlot.left + itemSlot.width > layout.width) {
          // If item is not aligned to the top, create a new slot.
          if (itemSlot.top > 0) {
            newSlots.push(this._addRect(layout.width, 0, Infinity, itemSlot.top));
          }
  
          // If item is not aligned to the bottom, create a new slot.
          if (itemSlot.top + itemSlot.height < layout.height) {
            newSlots.push(
              this._addRect(
                layout.width,
                itemSlot.top + itemSlot.height,
                Infinity,
                layout.height - itemSlot.top - itemSlot.height
              )
            );
          }
  
          // Update grid width.
          layout.width = itemSlot.left + itemSlot.width;
        }
  
        // Clean up the current slots making sure there are no old slots that
        // overlap with the item. If an old slot overlaps with the item, split it
        // into smaller slots if necessary.
        for (i = fillGaps ? 0 : ignoreCurrentSlots ? freeSlots.length : i; i < freeSlots.length; i++) {
          rectId = freeSlots[i];
          if (!rectId) continue;
          rect = this._getRect(rectId);
          potentialSlots = this._splitRect(rect, itemSlot);
          for (ii = 0; ii < potentialSlots.length; ii++) {
            rectId = potentialSlots[ii];
            rect = this._getRect(rectId);
            // Let's make sure here that we have a big enough slot
            // (width/height > 0.49px) and also let's make sure that the slot is
            // within the boundaries of the grid.
            if (
              rect.width > 0.49 &&
              rect.height > 0.49 &&
              ((!isHorizontal && rect.top < layout.height) ||
                (isHorizontal && rect.left < layout.width))
            ) {
              newSlots.push(rectId);
            }
          }
        }
  
        // Sanitize new slots.
        if (newSlots.length) {
          this._purgeRects(newSlots).sort(
            isHorizontal ? this._sortRectsLeftTop : this._sortRectsTopLeft
          );
        }
  
        // Update layout width/height.
        if (isHorizontal) {
          layout.width = Math.max(layout.width, itemSlot.left + itemSlot.width);
        } else {
          layout.height = Math.max(layout.height, itemSlot.top + itemSlot.height);
        }
  
        // Add item slot data to layout slots (and store the slot size for later
        // usage too if necessary).
        layout.slots.push(itemSlot.left, itemSlot.top);
        if (trackSize) this._slotSizes.push(itemSlot.width, itemSlot.height);
  
        // Free/new slots switcheroo!
        this._freeSlots = newSlots;
        this._newSlots = freeSlots;
      };
    })();
  
    /**
     * Add a new rectangle to the rectangle store. Returns the id of the new
     * rectangle.
     *
     * @private
     * @memberof Packer.prototype
     * @param {Number} left
     * @param {Number} top
     * @param {Number} width
     * @param {Number} height
     * @returns {RectId}
     */
    Packer.prototype._addRect = function(left, top, width, height) {
      var rectId = ++this._rectId;
      var rectStore = this._rectStore;
  
      rectStore[rectId] = left || 0;
      rectStore[++this._rectId] = top || 0;
      rectStore[++this._rectId] = width || 0;
      rectStore[++this._rectId] = height || 0;
  
      return rectId;
    };
  
    /**
     * Get rectangle data from the rectangle store by id. Optionally you can
     * provide a target object where the rectangle data will be written in. By
     * default an internal object is reused as a target object.
     *
     * @private
     * @memberof Packer.prototype
     * @param {RectId} id
     * @param {Object} [target]
     * @returns {Object}
     */
    Packer.prototype._getRect = function(id, target) {
      var rectItem = target ? target : this._rectItem;
      var rectStore = this._rectStore;
  
      rectItem.left = rectStore[id] || 0;
      rectItem.top = rectStore[++id] || 0;
      rectItem.width = rectStore[++id] || 0;
      rectItem.height = rectStore[++id] || 0;
  
      return rectItem;
    };
  
    /**
     * Punch a hole into a rectangle and split the remaining area into smaller
     * rectangles (4 at max).
     *
     * @private
     * @memberof Packer.prototype
     * @param {Rectangle} rect
     * @param {Rectangle} hole
     * @returns {RectId[]}
     */
    Packer.prototype._splitRect = (function() {
      var results = [];
      return function(rect, hole) {
        // Reset old results.
        results.length = 0;
  
        // If the rect does not overlap with the hole add rect to the return data
        // as is.
        if (!this._doRectsOverlap(rect, hole)) {
          results.push(this._addRect(rect.left, rect.top, rect.width, rect.height));
          return results;
        }
  
        // Left split.
        if (rect.left < hole.left) {
          results.push(this._addRect(rect.left, rect.top, hole.left - rect.left, rect.height));
        }
  
        // Right split.
        if (rect.left + rect.width > hole.left + hole.width) {
          results.push(
            this._addRect(
              hole.left + hole.width,
              rect.top,
              rect.left + rect.width - (hole.left + hole.width),
              rect.height
            )
          );
        }
  
        // Top split.
        if (rect.top < hole.top) {
          results.push(this._addRect(rect.left, rect.top, rect.width, hole.top - rect.top));
        }
  
        // Bottom split.
        if (rect.top + rect.height > hole.top + hole.height) {
          results.push(
            this._addRect(
              rect.left,
              hole.top + hole.height,
              rect.width,
              rect.top + rect.height - (hole.top + hole.height)
            )
          );
        }
  
        return results;
      };
    })();
  
    /**
     * Check if two rectangles overlap.
     *
     * @private
     * @memberof Packer.prototype
     * @param {Rectangle} a
     * @param {Rectangle} b
     * @returns {Boolean}
     */
    Packer.prototype._doRectsOverlap = function(a, b) {
      return !(
        a.left + a.width <= b.left ||
        b.left + b.width <= a.left ||
        a.top + a.height <= b.top ||
        b.top + b.height <= a.top
      );
    };
  
    /**
     * Check if a rectangle is fully within another rectangle.
     *
     * @private
     * @memberof Packer.prototype
     * @param {Rectangle} a
     * @param {Rectangle} b
     * @returns {Boolean}
     */
    Packer.prototype._isRectWithinRect = function(a, b) {
      return (
        a.left >= b.left &&
        a.top >= b.top &&
        a.left + a.width <= b.left + b.width &&
        a.top + a.height <= b.top + b.height
      );
    };
  
    /**
     * Loops through an array of rectangle ids and resets all that are fully
     * within another rectangle in the array. Resetting in this case means that
     * the rectangle id value is replaced with zero.
     *
     * @private
     * @memberof Packer.prototype
     * @param {RectId[]} rectIds
     * @returns {RectId[]}
     */
    Packer.prototype._purgeRects = (function() {
      var rectA = {};
      var rectB = {};
      return function(rectIds) {
        var i = rectIds.length;
        var ii;
  
        while (i--) {
          ii = rectIds.length;
          if (!rectIds[i]) continue;
          this._getRect(rectIds[i], rectA);
          while (ii--) {
            if (!rectIds[ii] || i === ii) continue;
            if (this._isRectWithinRect(rectA, this._getRect(rectIds[ii], rectB))) {
              rectIds[i] = 0;
              break;
            }
          }
        }
  
        return rectIds;
      };
    })();
  
    /**
     * Sort rectangles with top-left gravity.
     *
     * @private
     * @memberof Packer.prototype
     * @param {RectId} aId
     * @param {RectId} bId
     * @returns {Number}
     */
    Packer.prototype._sortRectsTopLeft = (function() {
      var rectA = {};
      var rectB = {};
      return function(aId, bId) {
        this._getRect(aId, rectA);
        this._getRect(bId, rectB);
        // prettier-ignore
        return rectA.top < rectB.top ? -1 :
               rectA.top > rectB.top ? 1 :
               rectA.left < rectB.left ? -1 :
               rectA.left > rectB.left ? 1 : 0;
      };
    })();
  
    /**
     * Sort rectangles with left-top gravity.
     *
     * @private
     * @memberof Packer.prototype
     * @param {RectId} aId
     * @param {RectId} bId
     * @returns {Number}
     */
    Packer.prototype._sortRectsLeftTop = (function() {
      var rectA = {};
      var rectB = {};
      return function(aId, bId) {
        this._getRect(aId, rectA);
        this._getRect(bId, rectB);
        // prettier-ignore
        return rectA.left < rectB.left ? -1 :
               rectA.left > rectB.left ? 1 :
               rectA.top < rectB.top ? -1 :
               rectA.top > rectB.top ? 1 : 0;
      };
    })();
  
    var htmlCollectionType = '[object HTMLCollection]';
    var nodeListType = '[object NodeList]';
  
    /**
     * Check if a value is a node list
     *
     * @param {*} val
     * @returns {Boolean}
     */
    function isNodeList(val) {
      var type = Object.prototype.toString.call(val);
      return type === htmlCollectionType || type === nodeListType;
    }
  
    /**
     * Converts a value to an array or clones an array.
     *
     * @param {*} target
     * @returns {Array}
     */
    function toArray(target) {
      return isNodeList(target) ? Array.prototype.slice.call(target) : Array.prototype.concat(target);
    }
  
    var packer = new Packer();
    var noop = function() {};
  
    /**
     * Creates a new Grid instance.
     *
     * @class
     * @param {(HTMLElement|String)} element
     * @param {Object} [options]
     * @param {(?HTMLElement[]|NodeList|String)} [options.items]
     * @param {Number} [options.showDuration=300]
     * @param {String} [options.showEasing="ease"]
     * @param {Object} [options.visibleStyles]
     * @param {Number} [options.hideDuration=300]
     * @param {String} [options.hideEasing="ease"]
     * @param {Object} [options.hiddenStyles]
     * @param {(Function|Object)} [options.layout]
     * @param {Boolean} [options.layout.fillGaps=false]
     * @param {Boolean} [options.layout.horizontal=false]
     * @param {Boolean} [options.layout.alignRight=false]
     * @param {Boolean} [options.layout.alignBottom=false]
     * @param {Boolean} [options.layout.rounding=true]
     * @param {(Boolean|Number)} [options.layoutOnResize=100]
     * @param {Boolean} [options.layoutOnInit=true]
     * @param {Number} [options.layoutDuration=300]
     * @param {String} [options.layoutEasing="ease"]
     * @param {?Object} [options.sortData=null]
     * @param {Boolean} [options.dragEnabled=false]
     * @param {?HtmlElement} [options.dragContainer=null]
     * @param {?Function} [options.dragStartPredicate]
     * @param {Number} [options.dragStartPredicate.distance=0]
     * @param {Number} [options.dragStartPredicate.delay=0]
     * @param {(Boolean|String)} [options.dragStartPredicate.handle=false]
     * @param {?String} [options.dragAxis]
     * @param {(Boolean|Function)} [options.dragSort=true]
     * @param {Number} [options.dragSortInterval=100]
     * @param {(Function|Object)} [options.dragSortPredicate]
     * @param {Number} [options.dragSortPredicate.threshold=50]
     * @param {String} [options.dragSortPredicate.action="move"]
     * @param {Number} [options.dragReleaseDuration=300]
     * @param {String} [options.dragReleaseEasing="ease"]
     * @param {Object} [options.dragHammerSettings={touchAction: "none"}]
     * @param {String} [options.containerClass="muuri"]
     * @param {String} [options.itemClass="muuri-item"]
     * @param {String} [options.itemVisibleClass="muuri-item-visible"]
     * @param {String} [options.itemHiddenClass="muuri-item-hidden"]
     * @param {String} [options.itemPositioningClass="muuri-item-positioning"]
     * @param {String} [options.itemDraggingClass="muuri-item-dragging"]
     * @param {String} [options.itemReleasingClass="muuri-item-releasing"]
     */
    function Grid(element, options) {
      var inst = this;
      var settings;
      var items;
      var layoutOnResize;
  
      // Allow passing element as selector string. Store element for instance.
      element = this._element = typeof element === 'string' ? document.querySelector(element) : element;
  
      // Throw an error if the container element is not body element or does not
      // exist within the body element.
      if (!document.body.contains(element)) {
        throw new Error('Container element must be an existing DOM element');
      }
  
      // Create instance settings by merging the options with default options.
      settings = this._settings = mergeSettings(Grid.defaultOptions, options);
  
      // Sanitize dragSort setting.
      if (typeof settings.dragSort !== 'function') {
        settings.dragSort = !!settings.dragSort;
      }
  
      // Create instance id and store it to the grid instances collection.
      this._id = createUid();
      gridInstances[this._id] = inst;
  
      // Destroyed flag.
      this._isDestroyed = false;
      this._hammer = null;
  
      // The layout object (mutated on every layout).
      this._layout = {
        id: 0,
        items: [],
        slots: [],
        setWidth: false,
        setHeight: false,
        width: 0,
        height: 0
      };
  
      // Create private Emitter instance.
      this._emitter = new Emitter();
  
      // Add container element's class name.
      addClass(element, settings.containerClass);
  
      // Create initial items.
      this._items = [];
      items = settings.items;
      if (typeof items === 'string') {
        toArray(element.children).forEach(function(itemElement) {
          if (items === '*' || elementMatches(itemElement, items)) {
            inst._items.push(new Item(inst, itemElement));
          }
        });
      } else if (Array.isArray(items) || isNodeList(items)) {
        this._items = toArray(items).map(function(itemElement) {
          return new Item(inst, itemElement);
        });
      }
  
      // If layoutOnResize option is a valid number sanitize it and bind the resize
      // handler.
      layoutOnResize = settings.layoutOnResize;
      if (typeof layoutOnResize !== 'number') {
        layoutOnResize = layoutOnResize === true ? 0 : -1;
      }
      if (layoutOnResize >= 0) {
        window.addEventListener(
          'resize',
          (inst._resizeHandler = debounce(function() {
            inst.refreshItems().layout();
          }, layoutOnResize))
        );
      }
  
      // Layout on init if necessary.
      if (settings.layoutOnInit) {
        this.layout(true);
      }
    }
  
    /**
     * Public properties
     * *****************
     */
  
    /**
     * @see Item
     */
    Grid.Item = Item;
  
    /**
     * @see ItemLayout
     */
    Grid.ItemLayout = ItemLayout;
  
    /**
     * @see ItemVisibility
     */
    Grid.ItemVisibility = ItemVisibility;
  
    /**
     * @see ItemRelease
     */
    Grid.ItemRelease = ItemRelease;
  
    /**
     * @see ItemMigrate
     */
    Grid.ItemMigrate = ItemMigrate;
  
    /**
     * @see ItemAnimate
     */
    Grid.ItemAnimate = ItemAnimate;
  
    /**
     * @see ItemDrag
     */
    Grid.ItemDrag = ItemDrag;
  
    /**
     * @see Emitter
     */
    Grid.Emitter = Emitter;
  
    /**
     * Default options for Grid instance.
     *
     * @public
     * @memberof Grid
     */
    Grid.defaultOptions = {
      // Item elements
      items: '*',
  
      // Default show animation
      showDuration: 300,
      showEasing: 'ease',
  
      // Default hide animation
      hideDuration: 300,
      hideEasing: 'ease',
  
      // Item's visible/hidden state styles
      visibleStyles: {
        opacity: '1',
        transform: 'scale(1)'
      },
      hiddenStyles: {
        opacity: '0',
        transform: 'scale(0.5)'
      },
  
      // Layout
      layout: {
        fillGaps: false,
        horizontal: false,
        alignRight: false,
        alignBottom: false,
        rounding: true
      },
      layoutOnResize: 100,
      layoutOnInit: true,
      layoutDuration: 300,
      layoutEasing: 'ease',
  
      // Sorting
      sortData: null,
  
      // Drag & Drop
      dragEnabled: false,
      dragContainer: null,
      dragStartPredicate: {
        distance: 0,
        delay: 0,
        handle: false
      },
      dragAxis: null,
      dragSort: true,
      dragSortInterval: 100,
      dragSortPredicate: {
        threshold: 50,
        action: 'move'
      },
      dragReleaseDuration: 300,
      dragReleaseEasing: 'ease',
      dragHammerSettings: {
        touchAction: 'none'
      },
  
      // Classnames
      containerClass: 'muuri',
      itemClass: 'muuri-item',
      itemVisibleClass: 'muuri-item-shown',
      itemHiddenClass: 'muuri-item-hidden',
      itemPositioningClass: 'muuri-item-positioning',
      itemDraggingClass: 'muuri-item-dragging',
      itemReleasingClass: 'muuri-item-releasing'
    };
  
    /**
     * Public prototype methods
     * ************************
     */
  
    /**
     * Bind an event listener.
     *
     * @public
     * @memberof Grid.prototype
     * @param {String} event
     * @param {Function} listener
     * @returns {Grid}
     */
    Grid.prototype.on = function(event, listener) {
      this._emitter.on(event, listener);
      return this;
    };
  
    /**
     * Bind an event listener that is triggered only once.
     *
     * @public
     * @memberof Grid.prototype
     * @param {String} event
     * @param {Function} listener
     * @returns {Grid}
     */
    Grid.prototype.once = function(event, listener) {
      this._emitter.once(event, listener);
      return this;
    };
  
    /**
     * Unbind an event listener.
     *
     * @public
     * @memberof Grid.prototype
     * @param {String} event
     * @param {Function} listener
     * @returns {Grid}
     */
    Grid.prototype.off = function(event, listener) {
      this._emitter.off(event, listener);
      return this;
    };
  
    /**
     * Get the container element.
     *
     * @public
     * @memberof Grid.prototype
     * @returns {HTMLElement}
     */
    Grid.prototype.getElement = function() {
      return this._element;
    };
  
    /**
     * Get all items. Optionally you can provide specific targets (elements and
     * indices). Note that the returned array is not the same object used by the
     * instance so modifying it will not affect instance's items. All items that
     * are not found are omitted from the returned array.
     *
     * @public
     * @memberof Grid.prototype
     * @param {GridMultiItemQuery} [targets]
     * @returns {Item[]}
     */
    Grid.prototype.getItems = function(targets) {
      // Return all items immediately if no targets were provided or if the
      // instance is destroyed.
      if (this._isDestroyed || (!targets && targets !== 0)) {
        return this._items.slice(0);
      }
  
      var ret = [];
      var targetItems = toArray(targets);
      var item;
      var i;
  
      // If target items are defined return filtered results.
      for (i = 0; i < targetItems.length; i++) {
        item = this._getItem(targetItems[i]);
        item && ret.push(item);
      }
  
      return ret;
    };
  
    /**
     * Update the cached dimensions of the instance's items.
     *
     * @public
     * @memberof Grid.prototype
     * @param {GridMultiItemQuery} [items]
     * @returns {Grid}
     */
    Grid.prototype.refreshItems = function(items) {
      if (this._isDestroyed) return this;
  
      var targets = this.getItems(items);
      var i;
  
      for (i = 0; i < targets.length; i++) {
        targets[i]._refreshDimensions();
      }
  
      return this;
    };
  
    /**
     * Update the sort data of the instance's items.
     *
     * @public
     * @memberof Grid.prototype
     * @param {GridMultiItemQuery} [items]
     * @returns {Grid}
     */
    Grid.prototype.refreshSortData = function(items) {
      if (this._isDestroyed) return this;
  
      var targetItems = this.getItems(items);
      var i;
  
      for (i = 0; i < targetItems.length; i++) {
        targetItems[i]._refreshSortData();
      }
  
      return this;
    };
  
    /**
     * Synchronize the item elements to match the order of the items in the DOM.
     * This comes handy if you need to keep the DOM structure matched with the
     * order of the items. Note that if an item's element is not currently a child
     * of the container element (if it is dragged for example) it is ignored and
     * left untouched.
     *
     * @public
     * @memberof Grid.prototype
     * @returns {Grid}
     */
    Grid.prototype.synchronize = function() {
      if (this._isDestroyed) return this;
  
      var container = this._element;
      var items = this._items;
      var fragment;
      var element;
      var i;
  
      // Append all elements in order to the container element.
      if (items.length) {
        for (i = 0; i < items.length; i++) {
          element = items[i]._element;
          if (element.parentNode === container) {
            fragment = fragment || document.createDocumentFragment();
            fragment.appendChild(element);
          }
        }
  
        if (fragment) container.appendChild(fragment);
      }
  
      // Emit synchronize event.
      this._emit(eventSynchronize);
  
      return this;
    };
  
    /**
     * Calculate and apply item positions.
     *
     * @public
     * @memberof Grid.prototype
     * @param {Boolean} [instant=false]
     * @param {LayoutCallback} [onFinish]
     * @returns {Grid}
     */
    Grid.prototype.layout = function(instant, onFinish) {
      if (this._isDestroyed) return this;
  
      var inst = this;
      var element = this._element;
      var layout = this._updateLayout();
      var layoutId = layout.id;
      var itemsLength = layout.items.length;
      var counter = itemsLength;
      var callback = typeof instant === 'function' ? instant : onFinish;
      var isCallbackFunction = typeof callback === 'function';
      var callbackItems = isCallbackFunction ? layout.items.slice(0) : null;
      var isBorderBox;
      var item;
      var i;
  
      // The finish function, which will be used for checking if all the items
      // have laid out yet. After all items have finished their animations call
      // callback and emit layoutEnd event. Only emit layoutEnd event if there
      // hasn't been a new layout call during this layout.
      function tryFinish() {
        if (--counter > 0) return;
        var hasLayoutChanged = inst._layout.id !== layoutId;
        isCallbackFunction && callback(hasLayoutChanged, callbackItems);
        if (!hasLayoutChanged && inst._hasListeners(eventLayoutEnd)) {
          inst._emit(eventLayoutEnd, layout.items.slice(0));
        }
      }
  
      // If grid's width or height was modified, we need to update it's cached
      // dimensions. Also keep in mind that grid's cached width/height should
      // always equal to what elem.getBoundingClientRect() would return, so
      // therefore we need to add the grid element's borders to the dimensions if
      // it's box-sizing is border-box.
      if (
        (layout.setHeight && typeof layout.height === 'number') ||
        (layout.setWidth && typeof layout.width === 'number')
      ) {
        isBorderBox = getStyle(element, 'box-sizing') === 'border-box';
      }
      if (layout.setHeight) {
        if (typeof layout.height === 'number') {
          element.style.height =
            (isBorderBox ? layout.height + this._borderTop + this._borderBottom : layout.height) + 'px';
        } else {
          element.style.height = layout.height;
        }
      }
      if (layout.setWidth) {
        if (typeof layout.width === 'number') {
          element.style.width =
            (isBorderBox ? layout.width + this._borderLeft + this._borderRight : layout.width) + 'px';
        } else {
          element.style.width = layout.width;
        }
      }
  
      // Emit layoutStart event. Note that this is intentionally emitted after the
      // container element's dimensions are set, because otherwise there would be
      // no hook for reacting to container dimension changes.
      if (this._hasListeners(eventLayoutStart)) {
        this._emit(eventLayoutStart, layout.items.slice(0));
      }
  
      // If there are no items let's finish quickly.
      if (!itemsLength) {
        tryFinish();
        return this;
      }
  
      // If there are items let's position them.
      for (i = 0; i < itemsLength; i++) {
        item = layout.items[i];
        if (!item) continue;
  
        // Update item's position.
        item._left = layout.slots[i * 2];
        item._top = layout.slots[i * 2 + 1];
  
        // Layout item if it is not dragged.
        item.isDragging() ? tryFinish() : item._layout.start(instant === true, tryFinish);
      }
  
      return this;
    };
  
    /**
     * Add new items by providing the elements you wish to add to the instance and
     * optionally provide the index where you want the items to be inserted into.
     * All elements that are not already children of the container element will be
     * automatically appended to the container element. If an element has it's CSS
     * display property set to "none" it will be marked as inactive during the
     * initiation process. As long as the item is inactive it will not be part of
     * the layout, but it will retain it's index. You can activate items at any
     * point with grid.show() method. This method will automatically call
     * grid.layout() if one or more of the added elements are visible. If only
     * hidden items are added no layout will be called. All the new visible items
     * are positioned without animation during their first layout.
     *
     * @public
     * @memberof Grid.prototype
     * @param {(HTMLElement|HTMLElement[])} elements
     * @param {Object} [options]
     * @param {Number} [options.index=-1]
     * @param {Boolean} [options.isActive]
     * @param {(Boolean|LayoutCallback|String)} [options.layout=true]
     * @returns {Item[]}
     */
    Grid.prototype.add = function(elements, options) {
      if (this._isDestroyed || !elements) return [];
  
      var newItems = toArray(elements);
      if (!newItems.length) return newItems;
  
      var opts = options || 0;
      var layout = opts.layout ? opts.layout : opts.layout === undefined;
      var items = this._items;
      var needsLayout = false;
      var item;
      var i;
  
      // Map provided elements into new grid items.
      for (i = 0; i < newItems.length; i++) {
        item = new Item(this, newItems[i], opts.isActive);
        newItems[i] = item;
  
        // If the item to be added is active, we need to do a layout. Also, we
        // need to mark the item with the skipNextAnimation flag to make it
        // position instantly (without animation) during the next layout. Without
        // the hack the item would animate to it's new position from the northwest
        // corner of the grid, which feels a bit buggy (imho).
        if (item._isActive) {
          needsLayout = true;
          item._layout._skipNextAnimation = true;
        }
      }
  
      // Add the new items to the items collection to correct index.
      arrayInsert(items, newItems, opts.index);
  
      // Emit add event.
      if (this._hasListeners(eventAdd)) {
        this._emit(eventAdd, newItems.slice(0));
      }
  
      // If layout is needed.
      if (needsLayout && layout) {
        this.layout(layout === 'instant', typeof layout === 'function' ? layout : undefined);
      }
  
      return newItems;
    };
  
    /**
     * Remove items from the instance.
     *
     * @public
     * @memberof Grid.prototype
     * @param {GridMultiItemQuery} items
     * @param {Object} [options]
     * @param {Boolean} [options.removeElements=false]
     * @param {(Boolean|LayoutCallback|String)} [options.layout=true]
     * @returns {Item[]}
     */
    Grid.prototype.remove = function(items, options) {
      if (this._isDestroyed) return this;
  
      var opts = options || 0;
      var layout = opts.layout ? opts.layout : opts.layout === undefined;
      var needsLayout = false;
      var allItems = this.getItems();
      var targetItems = this.getItems(items);
      var indices = [];
      var item;
      var i;
  
      // Remove the individual items.
      for (i = 0; i < targetItems.length; i++) {
        item = targetItems[i];
        indices.push(allItems.indexOf(item));
        if (item._isActive) needsLayout = true;
        item._destroy(opts.removeElements);
      }
  
      // Emit remove event.
      if (this._hasListeners(eventRemove)) {
        this._emit(eventRemove, targetItems.slice(0), indices);
      }
  
      // If layout is needed.
      if (needsLayout && layout) {
        this.layout(layout === 'instant', typeof layout === 'function' ? layout : undefined);
      }
  
      return targetItems;
    };
  
    /**
     * Show instance items.
     *
     * @public
     * @memberof Grid.prototype
     * @param {GridMultiItemQuery} items
     * @param {Object} [options]
     * @param {Boolean} [options.instant=false]
     * @param {ShowCallback} [options.onFinish]
     * @param {(Boolean|LayoutCallback|String)} [options.layout=true]
     * @returns {Grid}
     */
    Grid.prototype.show = function(items, options) {
      if (this._isDestroyed) return this;
      this._setItemsVisibility(items, true, options);
      return this;
    };
  
    /**
     * Hide instance items.
     *
     * @public
     * @memberof Grid.prototype
     * @param {GridMultiItemQuery} items
     * @param {Object} [options]
     * @param {Boolean} [options.instant=false]
     * @param {HideCallback} [options.onFinish]
     * @param {(Boolean|LayoutCallback|String)} [options.layout=true]
     * @returns {Grid}
     */
    Grid.prototype.hide = function(items, options) {
      if (this._isDestroyed) return this;
      this._setItemsVisibility(items, false, options);
      return this;
    };
  
    /**
     * Filter items. Expects at least one argument, a predicate, which should be
     * either a function or a string. The predicate callback is executed for every
     * item in the instance. If the return value of the predicate is truthy the
     * item in question will be shown and otherwise hidden. The predicate callback
     * receives the item instance as it's argument. If the predicate is a string
     * it is considered to be a selector and it is checked against every item
     * element in the instance with the native element.matches() method. All the
     * matching items will be shown and others hidden.
     *
     * @public
     * @memberof Grid.prototype
     * @param {(Function|String)} predicate
     * @param {Object} [options]
     * @param {Boolean} [options.instant=false]
     * @param {FilterCallback} [options.onFinish]
     * @param {(Boolean|LayoutCallback|String)} [options.layout=true]
     * @returns {Grid}
     */
    Grid.prototype.filter = function(predicate, options) {
      if (this._isDestroyed || !this._items.length) return this;
  
      var itemsToShow = [];
      var itemsToHide = [];
      var isPredicateString = typeof predicate === 'string';
      var isPredicateFn = typeof predicate === 'function';
      var opts = options || 0;
      var isInstant = opts.instant === true;
      var layout = opts.layout ? opts.layout : opts.layout === undefined;
      var onFinish = typeof opts.onFinish === 'function' ? opts.onFinish : null;
      var tryFinishCounter = -1;
      var tryFinish = noop;
      var item;
      var i;
  
      // If we have onFinish callback, let's create proper tryFinish callback.
      if (onFinish) {
        tryFinish = function() {
          ++tryFinishCounter && onFinish(itemsToShow.slice(0), itemsToHide.slice(0));
        };
      }
  
      // Check which items need to be shown and which hidden.
      if (isPredicateFn || isPredicateString) {
        for (i = 0; i < this._items.length; i++) {
          item = this._items[i];
          if (isPredicateFn ? predicate(item) : elementMatches(item._element, predicate)) {
            itemsToShow.push(item);
          } else {
            itemsToHide.push(item);
          }
        }
      }
  
      // Show items that need to be shown.
      if (itemsToShow.length) {
        this.show(itemsToShow, {
          instant: isInstant,
          onFinish: tryFinish,
          layout: false
        });
      } else {
        tryFinish();
      }
  
      // Hide items that need to be hidden.
      if (itemsToHide.length) {
        this.hide(itemsToHide, {
          instant: isInstant,
          onFinish: tryFinish,
          layout: false
        });
      } else {
        tryFinish();
      }
  
      // If there are any items to filter.
      if (itemsToShow.length || itemsToHide.length) {
        // Emit filter event.
        if (this._hasListeners(eventFilter)) {
          this._emit(eventFilter, itemsToShow.slice(0), itemsToHide.slice(0));
        }
  
        // If layout is needed.
        if (layout) {
          this.layout(layout === 'instant', typeof layout === 'function' ? layout : undefined);
        }
      }
  
      return this;
    };
  
    /**
     * Sort items. There are three ways to sort the items. The first is simply by
     * providing a function as the comparer which works identically to native
     * array sort. Alternatively you can sort by the sort data you have provided
     * in the instance's options. Just provide the sort data key(s) as a string
     * (separated by space) and the items will be sorted based on the provided
     * sort data keys. Lastly you have the opportunity to provide a presorted
     * array of items which will be used to sync the internal items array in the
     * same order.
     *
     * @public
     * @memberof Grid.prototype
     * @param {(Function|Item[]|String|String[])} comparer
     * @param {Object} [options]
     * @param {Boolean} [options.descending=false]
     * @param {(Boolean|LayoutCallback|String)} [options.layout=true]
     * @returns {Grid}
     */
    Grid.prototype.sort = (function() {
      var sortComparer;
      var isDescending;
      var origItems;
      var indexMap;
  
      function parseCriteria(data) {
        return data
          .trim()
          .split(' ')
          .map(function(val) {
            return val.split(':');
          });
      }
  
      function getIndexMap(items) {
        var ret = {};
        for (var i = 0; i < items.length; i++) {
          ret[items[i]._id] = i;
        }
        return ret;
      }
  
      function compareIndices(itemA, itemB) {
        var indexA = indexMap[itemA._id];
        var indexB = indexMap[itemB._id];
        return isDescending ? indexB - indexA : indexA - indexB;
      }
  
      function defaultComparer(a, b) {
        var result = 0;
        var criteriaName;
        var criteriaOrder;
        var valA;
        var valB;
  
        // Loop through the list of sort criteria.
        for (var i = 0; i < sortComparer.length; i++) {
          // Get the criteria name, which should match an item's sort data key.
          criteriaName = sortComparer[i][0];
          criteriaOrder = sortComparer[i][1];
  
          // Get items' cached sort values for the criteria. If the item has no sort
          // data let's update the items sort data (this is a lazy load mechanism).
          valA = (a._sortData ? a : a._refreshSortData())._sortData[criteriaName];
          valB = (b._sortData ? b : b._refreshSortData())._sortData[criteriaName];
  
          // Sort the items in descending order if defined so explicitly. Otherwise
          // sort items in ascending order.
          if (criteriaOrder === 'desc' || (!criteriaOrder && isDescending)) {
            result = valB < valA ? -1 : valB > valA ? 1 : 0;
          } else {
            result = valA < valB ? -1 : valA > valB ? 1 : 0;
          }
  
          // If we have -1 or 1 as the return value, let's return it immediately.
          if (result) return result;
        }
  
        // If values are equal let's compare the item indices to make sure we
        // have a stable sort.
        if (!result) {
          if (!indexMap) indexMap = getIndexMap(origItems);
          result = compareIndices(a, b);
        }
        return result;
      }
  
      function customComparer(a, b) {
        var result = sortComparer(a, b);
        // If descending let's invert the result value.
        if (isDescending && result) result = -result;
        // If we have a valid result (not zero) let's return it right away.
        if (result) return result;
        // If result is zero let's compare the item indices to make sure we have a
        // stable sort.
        if (!indexMap) indexMap = getIndexMap(origItems);
        return compareIndices(a, b);
      }
  
      return function(comparer, options) {
        if (this._isDestroyed || this._items.length < 2) return this;
  
        var items = this._items;
        var opts = options || 0;
        var layout = opts.layout ? opts.layout : opts.layout === undefined;
        var i;
  
        // Setup parent scope data.
        sortComparer = comparer;
        isDescending = !!opts.descending;
        origItems = items.slice(0);
        indexMap = null;
  
        // If function is provided do a native array sort.
        if (typeof sortComparer === 'function') {
          items.sort(customComparer);
        }
        // Otherwise if we got a string, let's sort by the sort data as provided in
        // the instance's options.
        else if (typeof sortComparer === 'string') {
          sortComparer = parseCriteria(comparer);
          items.sort(defaultComparer);
        }
        // Otherwise if we got an array, let's assume it's a presorted array of the
        // items and order the items based on it.
        else if (Array.isArray(sortComparer)) {
          if (sortComparer.length !== items.length) {
            throw new Error('[' + namespace + '] sort reference items do not match with grid items.');
          }
          for (i = 0; i < items.length; i++) {
            if (sortComparer.indexOf(items[i]) < 0) {
              throw new Error('[' + namespace + '] sort reference items do not match with grid items.');
            }
            items[i] = sortComparer[i];
          }
          if (isDescending) items.reverse();
        }
        // Otherwise let's just skip it, nothing we can do here.
        else {
          /** @todo Maybe throw an error here? */
          return this;
        }
  
        // Emit sort event.
        if (this._hasListeners(eventSort)) {
          this._emit(eventSort, items.slice(0), origItems);
        }
  
        // If layout is needed.
        if (layout) {
          this.layout(layout === 'instant', typeof layout === 'function' ? layout : undefined);
        }
  
        return this;
      };
    })();
  
    /**
     * Move item to another index or in place of another item.
     *
     * @public
     * @memberof Grid.prototype
     * @param {GridSingleItemQuery} item
     * @param {GridSingleItemQuery} position
     * @param {Object} [options]
     * @param {String} [options.action="move"]
     *   - Accepts either "move" or "swap".
     *   - "move" moves the item in place of the other item.
     *   - "swap" swaps the position of the items.
     * @param {(Boolean|LayoutCallback|String)} [options.layout=true]
     * @returns {Grid}
     */
    Grid.prototype.move = function(item, position, options) {
      if (this._isDestroyed || this._items.length < 2) return this;
  
      var items = this._items;
      var opts = options || 0;
      var layout = opts.layout ? opts.layout : opts.layout === undefined;
      var isSwap = opts.action === 'swap';
      var action = isSwap ? 'swap' : 'move';
      var fromItem = this._getItem(item);
      var toItem = this._getItem(position);
      var fromIndex;
      var toIndex;
  
      // Make sure the items exist and are not the same.
      if (fromItem && toItem && fromItem !== toItem) {
        // Get the indices of the items.
        fromIndex = items.indexOf(fromItem);
        toIndex = items.indexOf(toItem);
  
        // Do the move/swap.
        if (isSwap) {
          arraySwap(items, fromIndex, toIndex);
        } else {
          arrayMove(items, fromIndex, toIndex);
        }
  
        // Emit move event.
        if (this._hasListeners(eventMove)) {
          this._emit(eventMove, {
            item: fromItem,
            fromIndex: fromIndex,
            toIndex: toIndex,
            action: action
          });
        }
  
        // If layout is needed.
        if (layout) {
          this.layout(layout === 'instant', typeof layout === 'function' ? layout : undefined);
        }
      }
  
      return this;
    };
  
    /**
     * Send item to another Grid instance.
     *
     * @public
     * @memberof Grid.prototype
     * @param {GridSingleItemQuery} item
     * @param {Grid} grid
     * @param {GridSingleItemQuery} position
     * @param {Object} [options]
     * @param {HTMLElement} [options.appendTo=document.body]
     * @param {(Boolean|LayoutCallback|String)} [options.layoutSender=true]
     * @param {(Boolean|LayoutCallback|String)} [options.layoutReceiver=true]
     * @returns {Grid}
     */
    Grid.prototype.send = function(item, grid, position, options) {
      if (this._isDestroyed || grid._isDestroyed || this === grid) return this;
  
      // Make sure we have a valid target item.
      item = this._getItem(item);
      if (!item) return this;
  
      var opts = options || 0;
      var container = opts.appendTo || document.body;
      var layoutSender = opts.layoutSender ? opts.layoutSender : opts.layoutSender === undefined;
      var layoutReceiver = opts.layoutReceiver
        ? opts.layoutReceiver
        : opts.layoutReceiver === undefined;
  
      // Start the migration process.
      item._migrate.start(grid, position, container);
  
      // If migration was started successfully and the item is active, let's layout
      // the grids.
      if (item._migrate._isActive && item._isActive) {
        if (layoutSender) {
          this.layout(
            layoutSender === 'instant',
            typeof layoutSender === 'function' ? layoutSender : undefined
          );
        }
        if (layoutReceiver) {
          grid.layout(
            layoutReceiver === 'instant',
            typeof layoutReceiver === 'function' ? layoutReceiver : undefined
          );
        }
      }
  
      return this;
    };
  
    Grid.prototype.setHammer = function(instance) {
      this._hammer = instance;
    };
  
    /**
     * Destroy the instance.
     *
     * @public
     * @memberof Grid.prototype
     * @param {Boolean} [removeElements=false]
     * @returns {Grid}
     */
    Grid.prototype.destroy = function(removeElements) {
      if (this._isDestroyed) return this;
  
      var container = this._element;
      var items = this._items.slice(0);
      var i;
  
      // Unbind window resize event listener.
      if (this._resizeHandler) {
        window.removeEventListener('resize', this._resizeHandler);
      }
  
      // Destroy items.
      for (i = 0; i < items.length; i++) {
        items[i]._destroy(removeElements);
      }
  
      if (this._hammer) {
        this._hammer.destroy();
      }
  
      this._hammer = null;
  
      // Restore container.
      removeClass(container, this._settings.containerClass);
      container.style.height = '';
      container.style.width = '';
  
      // Emit destroy event and unbind all events.
      this._emit(eventDestroy);
      this._emitter.destroy();
  
      // Remove reference from the grid instances collection.
      gridInstances[this._id] = undefined;
  
      // Flag instance as destroyed.
      this._isDestroyed = true;
  
      return this;
    };
  
    /**
     * Private prototype methods
     * *************************
     */
  
    /**
     * Get instance's item by element or by index. Target can also be an Item
     * instance in which case the function returns the item if it exists within
     * related Grid instance. If nothing is found with the provided target, null
     * is returned.
     *
     * @private
     * @memberof Grid.prototype
     * @param {GridSingleItemQuery} [target]
     * @returns {?Item}
     */
    Grid.prototype._getItem = function(target) {
      // If no target is specified or the instance is destroyed, return null.
      if (this._isDestroyed || (!target && target !== 0)) {
        return null;
      }
  
      // If target is number return the item in that index. If the number is lower
      // than zero look for the item starting from the end of the items array. For
      // example -1 for the last item, -2 for the second last item, etc.
      if (typeof target === 'number') {
        return this._items[target > -1 ? target : this._items.length + target] || null;
      }
  
      // If the target is an instance of Item return it if it is attached to this
      // Grid instance, otherwise return null.
      if (target instanceof Item) {
        return target._gridId === this._id ? target : null;
      }
  
      // In other cases let's assume that the target is an element, so let's try
      // to find an item that matches the element and return it. If item is not
      // found return null.
      /** @todo This could be made a lot faster by using Map/WeakMap of elements. */
      for (var i = 0; i < this._items.length; i++) {
        if (this._items[i]._element === target) {
          return this._items[i];
        }
      }
  
      return null;
    };
  
    /**
     * Recalculates and updates instance's layout data.
     *
     * @private
     * @memberof Grid.prototype
     * @returns {LayoutData}
     */
    Grid.prototype._updateLayout = function() {
      var layout = this._layout;
      var settings = this._settings.layout;
      var width;
      var height;
      var newLayout;
      var i;
  
      // Let's increment layout id.
      ++layout.id;
  
      // Let's update layout items
      layout.items.length = 0;
      for (i = 0; i < this._items.length; i++) {
        if (this._items[i]._isActive) layout.items.push(this._items[i]);
      }
  
      // Let's make sure we have the correct container dimensions.
      this._refreshDimensions();
  
      // Calculate container width and height (without borders).
      width = this._width - this._borderLeft - this._borderRight;
      height = this._height - this._borderTop - this._borderBottom;
  
      // Calculate new layout.
      if (typeof settings === 'function') {
        newLayout = settings(layout.items, width, height);
      } else {
        newLayout = packer.getLayout(layout.items, width, height, layout.slots, settings);
      }
  
      // Let's update the grid's layout.
      layout.slots = newLayout.slots;
      layout.setWidth = Boolean(newLayout.setWidth);
      layout.setHeight = Boolean(newLayout.setHeight);
      layout.width = newLayout.width;
      layout.height = newLayout.height;
  
      return layout;
    };
  
    /**
     * Emit a grid event.
     *
     * @private
     * @memberof Grid.prototype
     * @param {String} event
     * @param {...*} [arg]
     */
    Grid.prototype._emit = function() {
      if (this._isDestroyed) return;
      this._emitter.emit.apply(this._emitter, arguments);
    };
  
    /**
     * Check if there are any events listeners for an event.
     *
     * @private
     * @memberof Grid.prototype
     * @param {String} event
     * @returns {Boolean}
     */
    Grid.prototype._hasListeners = function(event) {
      var listeners = this._emitter._events[event];
      return !!(listeners && listeners.length);
    };
  
    /**
     * Update container's width, height and offsets.
     *
     * @private
     * @memberof Grid.prototype
     */
    Grid.prototype._updateBoundingRect = function() {
      var element = this._element;
      var rect = element.getBoundingClientRect();
      this._width = rect.width;
      this._height = rect.height;
      this._left = rect.left;
      this._top = rect.top;
    };
  
    /**
     * Update container's border sizes.
     *
     * @private
     * @memberof Grid.prototype
     * @param {Boolean} left
     * @param {Boolean} right
     * @param {Boolean} top
     * @param {Boolean} bottom
     */
    Grid.prototype._updateBorders = function(left, right, top, bottom) {
      var element = this._element;
      if (left) this._borderLeft = getStyleAsFloat(element, 'border-left-width');
      if (right) this._borderRight = getStyleAsFloat(element, 'border-right-width');
      if (top) this._borderTop = getStyleAsFloat(element, 'border-top-width');
      if (bottom) this._borderBottom = getStyleAsFloat(element, 'border-bottom-width');
    };
  
    /**
     * Refresh all of container's internal dimensions and offsets.
     *
     * @private
     * @memberof Grid.prototype
     */
    Grid.prototype._refreshDimensions = function() {
      this._updateBoundingRect();
      this._updateBorders(1, 1, 1, 1);
    };
  
    /**
     * Show or hide Grid instance's items.
     *
     * @private
     * @memberof Grid.prototype
     * @param {GridMultiItemQuery} items
     * @param {Boolean} toVisible
     * @param {Object} [options]
     * @param {Boolean} [options.instant=false]
     * @param {(ShowCallback|HideCallback)} [options.onFinish]
     * @param {(Boolean|LayoutCallback|String)} [options.layout=true]
     */
    Grid.prototype._setItemsVisibility = function(items, toVisible, options) {
      var grid = this;
      var targetItems = this.getItems(items);
      var opts = options || 0;
      var isInstant = opts.instant === true;
      var callback = opts.onFinish;
      var layout = opts.layout ? opts.layout : opts.layout === undefined;
      var counter = targetItems.length;
      var startEvent = toVisible ? eventShowStart : eventHideStart;
      var endEvent = toVisible ? eventShowEnd : eventHideEnd;
      var method = toVisible ? 'show' : 'hide';
      var needsLayout = false;
      var completedItems = [];
      var hiddenItems = [];
      var item;
      var i;
  
      // If there are no items call the callback, but don't emit any events.
      if (!counter) {
        if (typeof callback === 'function') callback(targetItems);
        return;
      }
  
      // Emit showStart/hideStart event.
      if (this._hasListeners(startEvent)) {
        this._emit(startEvent, targetItems.slice(0));
      }
  
      // Show/hide items.
      for (i = 0; i < targetItems.length; i++) {
        item = targetItems[i];
  
        // If inactive item is shown or active item is hidden we need to do
        // layout.
        if ((toVisible && !item._isActive) || (!toVisible && item._isActive)) {
          needsLayout = true;
        }
  
        // If inactive item is shown we also need to do a little hack to make the
        // item not animate it's next positioning (layout).
        if (toVisible && !item._isActive) {
          item._layout._skipNextAnimation = true;
        }
  
        // If a hidden item is being shown we need to refresh the item's
        // dimensions.
        if (toVisible && item._visibility._isHidden) {
          hiddenItems.push(item);
        }
  
        // Show/hide the item.
        item._visibility[method](isInstant, function(interrupted, item) {
          // If the current item's animation was not interrupted add it to the
          // completedItems array.
          if (!interrupted) completedItems.push(item);
  
          // If all items have finished their animations call the callback
          // and emit showEnd/hideEnd event.
          if (--counter < 1) {
            if (typeof callback === 'function') callback(completedItems.slice(0));
            if (grid._hasListeners(endEvent)) grid._emit(endEvent, completedItems.slice(0));
          }
        });
      }
  
      // Refresh hidden items.
      if (hiddenItems.length) this.refreshItems(hiddenItems);
  
      // Layout if needed.
      if (needsLayout && layout) {
        this.layout(layout === 'instant', typeof layout === 'function' ? layout : undefined);
      }
    };
  
    /**
     * Private helpers
     * ***************
     */
  
    /**
     * Merge default settings with user settings. The returned object is a new
     * object with merged values. The merging is a deep merge meaning that all
     * objects and arrays within the provided settings objects will be also merged
     * so that modifying the values of the settings object will have no effect on
     * the returned object.
     *
     * @param {Object} defaultSettings
     * @param {Object} [userSettings]
     * @returns {Object} Returns a new object.
     */
    function mergeSettings(defaultSettings, userSettings) {
      // Create a fresh copy of default settings.
      var ret = mergeObjects({}, defaultSettings);
  
      // Merge user settings to default settings.
      if (userSettings) {
        ret = mergeObjects(ret, userSettings);
      }
  
      // Handle visible/hidden styles manually so that the whole object is
      // overridden instead of the props.
      ret.visibleStyles = (userSettings || 0).visibleStyles || (defaultSettings || 0).visibleStyles;
      ret.hiddenStyles = (userSettings || 0).hiddenStyles || (defaultSettings || 0).hiddenStyles;
  
      return ret;
    }
  
    /**
     * Merge two objects recursively (deep merge). The source object's properties
     * are merged to the target object.
     *
     * @param {Object} target
     *   - The target object.
     * @param {Object} source
     *   - The source object.
     * @returns {Object} Returns the target object.
     */
    function mergeObjects(target, source) {
      var sourceKeys = Object.keys(source);
      var length = sourceKeys.length;
      var isSourceObject;
      var propName;
      var i;
  
      for (i = 0; i < length; i++) {
        propName = sourceKeys[i];
        isSourceObject = isPlainObject(source[propName]);
  
        // If target and source values are both objects, merge the objects and
        // assign the merged value to the target property.
        if (isPlainObject(target[propName]) && isSourceObject) {
          target[propName] = mergeObjects(mergeObjects({}, target[propName]), source[propName]);
          continue;
        }
  
        // If source's value is object and target's is not let's clone the object as
        // the target's value.
        if (isSourceObject) {
          target[propName] = mergeObjects({}, source[propName]);
          continue;
        }
  
        // If source's value is an array let's clone the array as the target's
        // value.
        if (Array.isArray(source[propName])) {
          target[propName] = source[propName].slice(0);
          continue;
        }
  
        // In all other cases let's just directly assign the source's value as the
        // target's value.
        target[propName] = source[propName];
      }
  
      return target;
    }
  
    return Grid;
  
  })));
  