UNPKG

muuri

Version:

Responsive, sortable, filterable and draggable grid layouts.

284 lines (238 loc) 7.46 kB
/** * Copyright (c) 2015-present, Haltu Oy * Released under the MIT license * https://github.com/haltu/muuri/blob/master/LICENSE.md */ import { addLayoutTick, cancelLayoutTick } from '../ticker.js'; import Queue from '../Queue/Queue.js'; import addClass from '../utils/addClass.js'; import getTranslate from '../utils/getTranslate.js'; import getTranslateString from '../utils/getTranslateString.js'; import removeClass from '../utils/removeClass.js'; import setStyles from '../utils/setStyles.js'; /** * 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); }; export default ItemLayout;