github.com/Azareal/Gosora@v0.0.0-20210729070923-553e66b59003/public/Sortable-1.4.0/Sortable.js (about)

     1  /**!
     2   * Sortable
     3   * @author	RubaXa   <trash@rubaxa.org>
     4   * @license MIT
     5   */
     6  
     7  
     8  (function (factory) {
     9  	"use strict";
    10  
    11  	if (typeof define === "function" && define.amd) {
    12  		define(factory);
    13  	}
    14  	else if (typeof module != "undefined" && typeof module.exports != "undefined") {
    15  		module.exports = factory();
    16  	}
    17  	else if (typeof Package !== "undefined") {
    18  		Sortable = factory();  // export for Meteor.js
    19  	}
    20  	else {
    21  		/* jshint sub:true */
    22  		window["Sortable"] = factory();
    23  	}
    24  })(function () {
    25  	"use strict";
    26  
    27  	var dragEl,
    28  		parentEl,
    29  		ghostEl,
    30  		cloneEl,
    31  		rootEl,
    32  		nextEl,
    33  
    34  		scrollEl,
    35  		scrollParentEl,
    36  
    37  		lastEl,
    38  		lastCSS,
    39  		lastParentCSS,
    40  
    41  		oldIndex,
    42  		newIndex,
    43  
    44  		activeGroup,
    45  		autoScroll = {},
    46  
    47  		tapEvt,
    48  		touchEvt,
    49  
    50  		moved,
    51  
    52  		/** @const */
    53  		RSPACE = /\s+/g,
    54  
    55  		expando = 'Sortable' + (new Date).getTime(),
    56  
    57  		win = window,
    58  		document = win.document,
    59  		parseInt = win.parseInt,
    60  
    61  		supportDraggable = !!('draggable' in document.createElement('div')),
    62  		supportCssPointerEvents = (function (el) {
    63  			el = document.createElement('x');
    64  			el.style.cssText = 'pointer-events:auto';
    65  			return el.style.pointerEvents === 'auto';
    66  		})(),
    67  
    68  		_silent = false,
    69  
    70  		abs = Math.abs,
    71  		slice = [].slice,
    72  
    73  		touchDragOverListeners = [],
    74  
    75  		_autoScroll = _throttle(function (/**Event*/evt, /**Object*/options, /**HTMLElement*/rootEl) {
    76  			// Bug: https://bugzilla.mozilla.org/show_bug.cgi?id=505521
    77  			if (rootEl && options.scroll) {
    78  				var el,
    79  					rect,
    80  					sens = options.scrollSensitivity,
    81  					speed = options.scrollSpeed,
    82  
    83  					x = evt.clientX,
    84  					y = evt.clientY,
    85  
    86  					winWidth = window.innerWidth,
    87  					winHeight = window.innerHeight,
    88  
    89  					vx,
    90  					vy
    91  				;
    92  
    93  				// Delect scrollEl
    94  				if (scrollParentEl !== rootEl) {
    95  					scrollEl = options.scroll;
    96  					scrollParentEl = rootEl;
    97  
    98  					if (scrollEl === true) {
    99  						scrollEl = rootEl;
   100  
   101  						do {
   102  							if ((scrollEl.offsetWidth < scrollEl.scrollWidth) ||
   103  								(scrollEl.offsetHeight < scrollEl.scrollHeight)
   104  							) {
   105  								break;
   106  							}
   107  							/* jshint boss:true */
   108  						} while (scrollEl = scrollEl.parentNode);
   109  					}
   110  				}
   111  
   112  				if (scrollEl) {
   113  					el = scrollEl;
   114  					rect = scrollEl.getBoundingClientRect();
   115  					vx = (abs(rect.right - x) <= sens) - (abs(rect.left - x) <= sens);
   116  					vy = (abs(rect.bottom - y) <= sens) - (abs(rect.top - y) <= sens);
   117  				}
   118  
   119  
   120  				if (!(vx || vy)) {
   121  					vx = (winWidth - x <= sens) - (x <= sens);
   122  					vy = (winHeight - y <= sens) - (y <= sens);
   123  
   124  					/* jshint expr:true */
   125  					(vx || vy) && (el = win);
   126  				}
   127  
   128  
   129  				if (autoScroll.vx !== vx || autoScroll.vy !== vy || autoScroll.el !== el) {
   130  					autoScroll.el = el;
   131  					autoScroll.vx = vx;
   132  					autoScroll.vy = vy;
   133  
   134  					clearInterval(autoScroll.pid);
   135  
   136  					if (el) {
   137  						autoScroll.pid = setInterval(function () {
   138  							if (el === win) {
   139  								win.scrollTo(win.pageXOffset + vx * speed, win.pageYOffset + vy * speed);
   140  							} else {
   141  								vy && (el.scrollTop += vy * speed);
   142  								vx && (el.scrollLeft += vx * speed);
   143  							}
   144  						}, 24);
   145  					}
   146  				}
   147  			}
   148  		}, 30),
   149  
   150  		_prepareGroup = function (options) {
   151  			var group = options.group;
   152  
   153  			if (!group || typeof group != 'object') {
   154  				group = options.group = {name: group};
   155  			}
   156  
   157  			['pull', 'put'].forEach(function (key) {
   158  				if (!(key in group)) {
   159  					group[key] = true;
   160  				}
   161  			});
   162  
   163  			options.groups = ' ' + group.name + (group.put.join ? ' ' + group.put.join(' ') : '') + ' ';
   164  		}
   165  	;
   166  
   167  
   168  
   169  	/**
   170  	 * @class  Sortable
   171  	 * @param  {HTMLElement}  el
   172  	 * @param  {Object}       [options]
   173  	 */
   174  	function Sortable(el, options) {
   175  		if (!(el && el.nodeType && el.nodeType === 1)) {
   176  			throw 'Sortable: `el` must be HTMLElement, and not ' + {}.toString.call(el);
   177  		}
   178  
   179  		this.el = el; // root element
   180  		this.options = options = _extend({}, options);
   181  
   182  
   183  		// Export instance
   184  		el[expando] = this;
   185  
   186  
   187  		// Default options
   188  		var defaults = {
   189  			group: Math.random(),
   190  			sort: true,
   191  			disabled: false,
   192  			store: null,
   193  			handle: null,
   194  			scroll: true,
   195  			scrollSensitivity: 30,
   196  			scrollSpeed: 10,
   197  			draggable: /[uo]l/i.test(el.nodeName) ? 'li' : '>*',
   198  			ghostClass: 'sortable-ghost',
   199  			chosenClass: 'sortable-chosen',
   200  			ignore: 'a, img',
   201  			filter: null,
   202  			animation: 0,
   203  			setData: function (dataTransfer, dragEl) {
   204  				dataTransfer.setData('Text', dragEl.textContent);
   205  			},
   206  			dropBubble: false,
   207  			dragoverBubble: false,
   208  			dataIdAttr: 'data-id',
   209  			delay: 0,
   210  			forceFallback: false,
   211  			fallbackClass: 'sortable-fallback',
   212  			fallbackOnBody: false
   213  		};
   214  
   215  
   216  		// Set default options
   217  		for (var name in defaults) {
   218  			!(name in options) && (options[name] = defaults[name]);
   219  		}
   220  
   221  		_prepareGroup(options);
   222  
   223  		// Bind all private methods
   224  		for (var fn in this) {
   225  			if (fn.charAt(0) === '_') {
   226  				this[fn] = this[fn].bind(this);
   227  			}
   228  		}
   229  
   230  		// Setup drag mode
   231  		this.nativeDraggable = options.forceFallback ? false : supportDraggable;
   232  
   233  		// Bind events
   234  		_on(el, 'mousedown', this._onTapStart);
   235  		_on(el, 'touchstart', this._onTapStart);
   236  
   237  		if (this.nativeDraggable) {
   238  			_on(el, 'dragover', this);
   239  			_on(el, 'dragenter', this);
   240  		}
   241  
   242  		touchDragOverListeners.push(this._onDragOver);
   243  
   244  		// Restore sorting
   245  		options.store && this.sort(options.store.get(this));
   246  	}
   247  
   248  
   249  	Sortable.prototype = /** @lends Sortable.prototype */ {
   250  		constructor: Sortable,
   251  
   252  		_onTapStart: function (/** Event|TouchEvent */evt) {
   253  			var _this = this,
   254  				el = this.el,
   255  				options = this.options,
   256  				type = evt.type,
   257  				touch = evt.touches && evt.touches[0],
   258  				target = (touch || evt).target,
   259  				originalTarget = target,
   260  				filter = options.filter;
   261  
   262  
   263  			if (type === 'mousedown' && evt.button !== 0 || options.disabled) {
   264  				return; // only left button or enabled
   265  			}
   266  
   267  			target = _closest(target, options.draggable, el);
   268  
   269  			if (!target) {
   270  				return;
   271  			}
   272  
   273  			// get the index of the dragged element within its parent
   274  			oldIndex = _index(target);
   275  
   276  			// Check filter
   277  			if (typeof filter === 'function') {
   278  				if (filter.call(this, evt, target, this)) {
   279  					_dispatchEvent(_this, originalTarget, 'filter', target, el, oldIndex);
   280  					evt.preventDefault();
   281  					return; // cancel dnd
   282  				}
   283  			}
   284  			else if (filter) {
   285  				filter = filter.split(',').some(function (criteria) {
   286  					criteria = _closest(originalTarget, criteria.trim(), el);
   287  
   288  					if (criteria) {
   289  						_dispatchEvent(_this, criteria, 'filter', target, el, oldIndex);
   290  						return true;
   291  					}
   292  				});
   293  
   294  				if (filter) {
   295  					evt.preventDefault();
   296  					return; // cancel dnd
   297  				}
   298  			}
   299  
   300  
   301  			if (options.handle && !_closest(originalTarget, options.handle, el)) {
   302  				return;
   303  			}
   304  
   305  
   306  			// Prepare `dragstart`
   307  			this._prepareDragStart(evt, touch, target);
   308  		},
   309  
   310  		_prepareDragStart: function (/** Event */evt, /** Touch */touch, /** HTMLElement */target) {
   311  			var _this = this,
   312  				el = _this.el,
   313  				options = _this.options,
   314  				ownerDocument = el.ownerDocument,
   315  				dragStartFn;
   316  
   317  			if (target && !dragEl && (target.parentNode === el)) {
   318  				tapEvt = evt;
   319  
   320  				rootEl = el;
   321  				dragEl = target;
   322  				parentEl = dragEl.parentNode;
   323  				nextEl = dragEl.nextSibling;
   324  				activeGroup = options.group;
   325  
   326  				dragStartFn = function () {
   327  					// Delayed drag has been triggered
   328  					// we can re-enable the events: touchmove/mousemove
   329  					_this._disableDelayedDrag();
   330  
   331  					// Make the element draggable
   332  					dragEl.draggable = true;
   333  
   334  					// Chosen item
   335  					_toggleClass(dragEl, _this.options.chosenClass, true);
   336  
   337  					// Bind the events: dragstart/dragend
   338  					_this._triggerDragStart(touch);
   339  				};
   340  
   341  				// Disable "draggable"
   342  				options.ignore.split(',').forEach(function (criteria) {
   343  					_find(dragEl, criteria.trim(), _disableDraggable);
   344  				});
   345  
   346  				_on(ownerDocument, 'mouseup', _this._onDrop);
   347  				_on(ownerDocument, 'touchend', _this._onDrop);
   348  				_on(ownerDocument, 'touchcancel', _this._onDrop);
   349  
   350  				if (options.delay) {
   351  					// If the user moves the pointer or let go the click or touch
   352  					// before the delay has been reached:
   353  					// disable the delayed drag
   354  					_on(ownerDocument, 'mouseup', _this._disableDelayedDrag);
   355  					_on(ownerDocument, 'touchend', _this._disableDelayedDrag);
   356  					_on(ownerDocument, 'touchcancel', _this._disableDelayedDrag);
   357  					_on(ownerDocument, 'mousemove', _this._disableDelayedDrag);
   358  					_on(ownerDocument, 'touchmove', _this._disableDelayedDrag);
   359  
   360  					_this._dragStartTimer = setTimeout(dragStartFn, options.delay);
   361  				} else {
   362  					dragStartFn();
   363  				}
   364  			}
   365  		},
   366  
   367  		_disableDelayedDrag: function () {
   368  			var ownerDocument = this.el.ownerDocument;
   369  
   370  			clearTimeout(this._dragStartTimer);
   371  			_off(ownerDocument, 'mouseup', this._disableDelayedDrag);
   372  			_off(ownerDocument, 'touchend', this._disableDelayedDrag);
   373  			_off(ownerDocument, 'touchcancel', this._disableDelayedDrag);
   374  			_off(ownerDocument, 'mousemove', this._disableDelayedDrag);
   375  			_off(ownerDocument, 'touchmove', this._disableDelayedDrag);
   376  		},
   377  
   378  		_triggerDragStart: function (/** Touch */touch) {
   379  			if (touch) {
   380  				// Touch device support
   381  				tapEvt = {
   382  					target: dragEl,
   383  					clientX: touch.clientX,
   384  					clientY: touch.clientY
   385  				};
   386  
   387  				this._onDragStart(tapEvt, 'touch');
   388  			}
   389  			else if (!this.nativeDraggable) {
   390  				this._onDragStart(tapEvt, true);
   391  			}
   392  			else {
   393  				_on(dragEl, 'dragend', this);
   394  				_on(rootEl, 'dragstart', this._onDragStart);
   395  			}
   396  
   397  			try {
   398  				if (document.selection) {
   399  					document.selection.empty();
   400  				} else {
   401  					window.getSelection().removeAllRanges();
   402  				}
   403  			} catch (err) {
   404  			}
   405  		},
   406  
   407  		_dragStarted: function () {
   408  			if (rootEl && dragEl) {
   409  				// Apply effect
   410  				_toggleClass(dragEl, this.options.ghostClass, true);
   411  
   412  				Sortable.active = this;
   413  
   414  				// Drag start event
   415  				_dispatchEvent(this, rootEl, 'start', dragEl, rootEl, oldIndex);
   416  			}
   417  		},
   418  
   419  		_emulateDragOver: function () {
   420  			if (touchEvt) {
   421  				if (this._lastX === touchEvt.clientX && this._lastY === touchEvt.clientY) {
   422  					return;
   423  				}
   424  
   425  				this._lastX = touchEvt.clientX;
   426  				this._lastY = touchEvt.clientY;
   427  
   428  				if (!supportCssPointerEvents) {
   429  					_css(ghostEl, 'display', 'none');
   430  				}
   431  
   432  				var target = document.elementFromPoint(touchEvt.clientX, touchEvt.clientY),
   433  					parent = target,
   434  					groupName = ' ' + this.options.group.name + '',
   435  					i = touchDragOverListeners.length;
   436  
   437  				if (parent) {
   438  					do {
   439  						if (parent[expando] && parent[expando].options.groups.indexOf(groupName) > -1) {
   440  							while (i--) {
   441  								touchDragOverListeners[i]({
   442  									clientX: touchEvt.clientX,
   443  									clientY: touchEvt.clientY,
   444  									target: target,
   445  									rootEl: parent
   446  								});
   447  							}
   448  
   449  							break;
   450  						}
   451  
   452  						target = parent; // store last element
   453  					}
   454  					/* jshint boss:true */
   455  					while (parent = parent.parentNode);
   456  				}
   457  
   458  				if (!supportCssPointerEvents) {
   459  					_css(ghostEl, 'display', '');
   460  				}
   461  			}
   462  		},
   463  
   464  
   465  		_onTouchMove: function (/**TouchEvent*/evt) {
   466  			if (tapEvt) {
   467  				// only set the status to dragging, when we are actually dragging
   468  				if (!Sortable.active) {
   469  					this._dragStarted();
   470  				}
   471  
   472  				// as well as creating the ghost element on the document body
   473  				this._appendGhost();
   474  
   475  				var touch = evt.touches ? evt.touches[0] : evt,
   476  					dx = touch.clientX - tapEvt.clientX,
   477  					dy = touch.clientY - tapEvt.clientY,
   478  					translate3d = evt.touches ? 'translate3d(' + dx + 'px,' + dy + 'px,0)' : 'translate(' + dx + 'px,' + dy + 'px)';
   479  
   480  				moved = true;
   481  				touchEvt = touch;
   482  
   483  				_css(ghostEl, 'webkitTransform', translate3d);
   484  				_css(ghostEl, 'mozTransform', translate3d);
   485  				_css(ghostEl, 'msTransform', translate3d);
   486  				_css(ghostEl, 'transform', translate3d);
   487  
   488  				evt.preventDefault();
   489  			}
   490  		},
   491  
   492  		_appendGhost: function () {
   493  			if (!ghostEl) {
   494  				var rect = dragEl.getBoundingClientRect(),
   495  					css = _css(dragEl),
   496  					options = this.options,
   497  					ghostRect;
   498  
   499  				ghostEl = dragEl.cloneNode(true);
   500  
   501  				_toggleClass(ghostEl, options.ghostClass, false);
   502  				_toggleClass(ghostEl, options.fallbackClass, true);
   503  
   504  				_css(ghostEl, 'top', rect.top - parseInt(css.marginTop, 10));
   505  				_css(ghostEl, 'left', rect.left - parseInt(css.marginLeft, 10));
   506  				_css(ghostEl, 'width', rect.width);
   507  				_css(ghostEl, 'height', rect.height);
   508  				_css(ghostEl, 'opacity', '0.8');
   509  				_css(ghostEl, 'position', 'fixed');
   510  				_css(ghostEl, 'zIndex', '100000');
   511  				_css(ghostEl, 'pointerEvents', 'none');
   512  
   513  				options.fallbackOnBody && document.body.appendChild(ghostEl) || rootEl.appendChild(ghostEl);
   514  
   515  				// Fixing dimensions.
   516  				ghostRect = ghostEl.getBoundingClientRect();
   517  				_css(ghostEl, 'width', rect.width * 2 - ghostRect.width);
   518  				_css(ghostEl, 'height', rect.height * 2 - ghostRect.height);
   519  			}
   520  		},
   521  
   522  		_onDragStart: function (/**Event*/evt, /**boolean*/useFallback) {
   523  			var dataTransfer = evt.dataTransfer,
   524  				options = this.options;
   525  
   526  			this._offUpEvents();
   527  
   528  			if (activeGroup.pull == 'clone') {
   529  				cloneEl = dragEl.cloneNode(true);
   530  				_css(cloneEl, 'display', 'none');
   531  				rootEl.insertBefore(cloneEl, dragEl);
   532  			}
   533  
   534  			if (useFallback) {
   535  
   536  				if (useFallback === 'touch') {
   537  					// Bind touch events
   538  					_on(document, 'touchmove', this._onTouchMove);
   539  					_on(document, 'touchend', this._onDrop);
   540  					_on(document, 'touchcancel', this._onDrop);
   541  				} else {
   542  					// Old brwoser
   543  					_on(document, 'mousemove', this._onTouchMove);
   544  					_on(document, 'mouseup', this._onDrop);
   545  				}
   546  
   547  				this._loopId = setInterval(this._emulateDragOver, 50);
   548  			}
   549  			else {
   550  				if (dataTransfer) {
   551  					dataTransfer.effectAllowed = 'move';
   552  					options.setData && options.setData.call(this, dataTransfer, dragEl);
   553  				}
   554  
   555  				_on(document, 'drop', this);
   556  				setTimeout(this._dragStarted, 0);
   557  			}
   558  		},
   559  
   560  		_onDragOver: function (/**Event*/evt) {
   561  			var el = this.el,
   562  				target,
   563  				dragRect,
   564  				revert,
   565  				options = this.options,
   566  				group = options.group,
   567  				groupPut = group.put,
   568  				isOwner = (activeGroup === group),
   569  				canSort = options.sort;
   570  
   571  			if (evt.preventDefault !== void 0) {
   572  				evt.preventDefault();
   573  				!options.dragoverBubble && evt.stopPropagation();
   574  			}
   575  
   576  			moved = true;
   577  
   578  			if (activeGroup && !options.disabled &&
   579  				(isOwner
   580  					? canSort || (revert = !rootEl.contains(dragEl)) // Reverting item into the original list
   581  					: activeGroup.pull && groupPut && (
   582  						(activeGroup.name === group.name) || // by Name
   583  						(groupPut.indexOf && ~groupPut.indexOf(activeGroup.name)) // by Array
   584  					)
   585  				) &&
   586  				(evt.rootEl === void 0 || evt.rootEl === this.el) // touch fallback
   587  			) {
   588  				// Smart auto-scrolling
   589  				_autoScroll(evt, options, this.el);
   590  
   591  				if (_silent) {
   592  					return;
   593  				}
   594  
   595  				target = _closest(evt.target, options.draggable, el);
   596  				dragRect = dragEl.getBoundingClientRect();
   597  
   598  				if (revert) {
   599  					_cloneHide(true);
   600  
   601  					if (cloneEl || nextEl) {
   602  						rootEl.insertBefore(dragEl, cloneEl || nextEl);
   603  					}
   604  					else if (!canSort) {
   605  						rootEl.appendChild(dragEl);
   606  					}
   607  
   608  					return;
   609  				}
   610  
   611  
   612  				if ((el.children.length === 0) || (el.children[0] === ghostEl) ||
   613  					(el === evt.target) && (target = _ghostIsLast(el, evt))
   614  				) {
   615  
   616  					if (target) {
   617  						if (target.animated) {
   618  							return;
   619  						}
   620  
   621  						targetRect = target.getBoundingClientRect();
   622  					}
   623  
   624  					_cloneHide(isOwner);
   625  
   626  					if (_onMove(rootEl, el, dragEl, dragRect, target, targetRect) !== false) {
   627  						if (!dragEl.contains(el)) {
   628  							el.appendChild(dragEl);
   629  							parentEl = el; // actualization
   630  						}
   631  
   632  						this._animate(dragRect, dragEl);
   633  						target && this._animate(targetRect, target);
   634  					}
   635  				}
   636  				else if (target && !target.animated && target !== dragEl && (target.parentNode[expando] !== void 0)) {
   637  					if (lastEl !== target) {
   638  						lastEl = target;
   639  						lastCSS = _css(target);
   640  						lastParentCSS = _css(target.parentNode);
   641  					}
   642  
   643  
   644  					var targetRect = target.getBoundingClientRect(),
   645  						width = targetRect.right - targetRect.left,
   646  						height = targetRect.bottom - targetRect.top,
   647  						floating = /left|right|inline/.test(lastCSS.cssFloat + lastCSS.display)
   648  							|| (lastParentCSS.display == 'flex' && lastParentCSS['flex-direction'].indexOf('row') === 0),
   649  						isWide = (target.offsetWidth > dragEl.offsetWidth),
   650  						isLong = (target.offsetHeight > dragEl.offsetHeight),
   651  						halfway = (floating ? (evt.clientX - targetRect.left) / width : (evt.clientY - targetRect.top) / height) > 0.5,
   652  						nextSibling = target.nextElementSibling,
   653  						moveVector = _onMove(rootEl, el, dragEl, dragRect, target, targetRect),
   654  						after
   655  					;
   656  
   657  					if (moveVector !== false) {
   658  						_silent = true;
   659  						setTimeout(_unsilent, 30);
   660  
   661  						_cloneHide(isOwner);
   662  
   663  						if (moveVector === 1 || moveVector === -1) {
   664  							after = (moveVector === 1);
   665  						}
   666  						else if (floating) {
   667  							var elTop = dragEl.offsetTop,
   668  								tgTop = target.offsetTop;
   669  
   670  							if (elTop === tgTop) {
   671  								after = (target.previousElementSibling === dragEl) && !isWide || halfway && isWide;
   672  							} else {
   673  								after = tgTop > elTop;
   674  							}
   675  						} else {
   676  							after = (nextSibling !== dragEl) && !isLong || halfway && isLong;
   677  						}
   678  
   679  						if (!dragEl.contains(el)) {
   680  							if (after && !nextSibling) {
   681  								el.appendChild(dragEl);
   682  							} else {
   683  								target.parentNode.insertBefore(dragEl, after ? nextSibling : target);
   684  							}
   685  						}
   686  
   687  						parentEl = dragEl.parentNode; // actualization
   688  
   689  						this._animate(dragRect, dragEl);
   690  						this._animate(targetRect, target);
   691  					}
   692  				}
   693  			}
   694  		},
   695  
   696  		_animate: function (prevRect, target) {
   697  			var ms = this.options.animation;
   698  
   699  			if (ms) {
   700  				var currentRect = target.getBoundingClientRect();
   701  
   702  				_css(target, 'transition', 'none');
   703  				_css(target, 'transform', 'translate3d('
   704  					+ (prevRect.left - currentRect.left) + 'px,'
   705  					+ (prevRect.top - currentRect.top) + 'px,0)'
   706  				);
   707  
   708  				target.offsetWidth; // repaint
   709  
   710  				_css(target, 'transition', 'all ' + ms + 'ms');
   711  				_css(target, 'transform', 'translate3d(0,0,0)');
   712  
   713  				clearTimeout(target.animated);
   714  				target.animated = setTimeout(function () {
   715  					_css(target, 'transition', '');
   716  					_css(target, 'transform', '');
   717  					target.animated = false;
   718  				}, ms);
   719  			}
   720  		},
   721  
   722  		_offUpEvents: function () {
   723  			var ownerDocument = this.el.ownerDocument;
   724  
   725  			_off(document, 'touchmove', this._onTouchMove);
   726  			_off(ownerDocument, 'mouseup', this._onDrop);
   727  			_off(ownerDocument, 'touchend', this._onDrop);
   728  			_off(ownerDocument, 'touchcancel', this._onDrop);
   729  		},
   730  
   731  		_onDrop: function (/**Event*/evt) {
   732  			var el = this.el,
   733  				options = this.options;
   734  
   735  			clearInterval(this._loopId);
   736  			clearInterval(autoScroll.pid);
   737  			clearTimeout(this._dragStartTimer);
   738  
   739  			// Unbind events
   740  			_off(document, 'mousemove', this._onTouchMove);
   741  
   742  			if (this.nativeDraggable) {
   743  				_off(document, 'drop', this);
   744  				_off(el, 'dragstart', this._onDragStart);
   745  			}
   746  
   747  			this._offUpEvents();
   748  
   749  			if (evt) {
   750  				if (moved) {
   751  					evt.preventDefault();
   752  					!options.dropBubble && evt.stopPropagation();
   753  				}
   754  
   755  				ghostEl && ghostEl.parentNode.removeChild(ghostEl);
   756  
   757  				if (dragEl) {
   758  					if (this.nativeDraggable) {
   759  						_off(dragEl, 'dragend', this);
   760  					}
   761  
   762  					_disableDraggable(dragEl);
   763  
   764  					// Remove class's
   765  					_toggleClass(dragEl, this.options.ghostClass, false);
   766  					_toggleClass(dragEl, this.options.chosenClass, false);
   767  
   768  					if (rootEl !== parentEl) {
   769  						newIndex = _index(dragEl);
   770  
   771  						if (newIndex >= 0) {
   772  							// drag from one list and drop into another
   773  							_dispatchEvent(null, parentEl, 'sort', dragEl, rootEl, oldIndex, newIndex);
   774  							_dispatchEvent(this, rootEl, 'sort', dragEl, rootEl, oldIndex, newIndex);
   775  
   776  							// Add event
   777  							_dispatchEvent(null, parentEl, 'add', dragEl, rootEl, oldIndex, newIndex);
   778  
   779  							// Remove event
   780  							_dispatchEvent(this, rootEl, 'remove', dragEl, rootEl, oldIndex, newIndex);
   781  						}
   782  					}
   783  					else {
   784  						// Remove clone
   785  						cloneEl && cloneEl.parentNode.removeChild(cloneEl);
   786  
   787  						if (dragEl.nextSibling !== nextEl) {
   788  							// Get the index of the dragged element within its parent
   789  							newIndex = _index(dragEl);
   790  
   791  							if (newIndex >= 0) {
   792  								// drag & drop within the same list
   793  								_dispatchEvent(this, rootEl, 'update', dragEl, rootEl, oldIndex, newIndex);
   794  								_dispatchEvent(this, rootEl, 'sort', dragEl, rootEl, oldIndex, newIndex);
   795  							}
   796  						}
   797  					}
   798  
   799  					if (Sortable.active) {
   800  						if (newIndex === null || newIndex === -1) {
   801  							newIndex = oldIndex;
   802  						}
   803  
   804  						_dispatchEvent(this, rootEl, 'end', dragEl, rootEl, oldIndex, newIndex);
   805  
   806  						// Save sorting
   807  						this.save();
   808  					}
   809  				}
   810  
   811  				// Nulling
   812  				rootEl =
   813  				dragEl =
   814  				parentEl =
   815  				ghostEl =
   816  				nextEl =
   817  				cloneEl =
   818  
   819  				scrollEl =
   820  				scrollParentEl =
   821  
   822  				tapEvt =
   823  				touchEvt =
   824  
   825  				moved =
   826  				newIndex =
   827  
   828  				lastEl =
   829  				lastCSS =
   830  
   831  				activeGroup =
   832  				Sortable.active = null;
   833  			}
   834  		},
   835  
   836  
   837  		handleEvent: function (/**Event*/evt) {
   838  			var type = evt.type;
   839  
   840  			if (type === 'dragover' || type === 'dragenter') {
   841  				if (dragEl) {
   842  					this._onDragOver(evt);
   843  					_globalDragOver(evt);
   844  				}
   845  			}
   846  			else if (type === 'drop' || type === 'dragend') {
   847  				this._onDrop(evt);
   848  			}
   849  		},
   850  
   851  
   852  		/**
   853  		 * Serializes the item into an array of string.
   854  		 * @returns {String[]}
   855  		 */
   856  		toArray: function () {
   857  			var order = [],
   858  				el,
   859  				children = this.el.children,
   860  				i = 0,
   861  				n = children.length,
   862  				options = this.options;
   863  
   864  			for (; i < n; i++) {
   865  				el = children[i];
   866  				if (_closest(el, options.draggable, this.el)) {
   867  					order.push(el.getAttribute(options.dataIdAttr) || _generateId(el));
   868  				}
   869  			}
   870  
   871  			return order;
   872  		},
   873  
   874  
   875  		/**
   876  		 * Sorts the elements according to the array.
   877  		 * @param  {String[]}  order  order of the items
   878  		 */
   879  		sort: function (order) {
   880  			var items = {}, rootEl = this.el;
   881  
   882  			this.toArray().forEach(function (id, i) {
   883  				var el = rootEl.children[i];
   884  
   885  				if (_closest(el, this.options.draggable, rootEl)) {
   886  					items[id] = el;
   887  				}
   888  			}, this);
   889  
   890  			order.forEach(function (id) {
   891  				if (items[id]) {
   892  					rootEl.removeChild(items[id]);
   893  					rootEl.appendChild(items[id]);
   894  				}
   895  			});
   896  		},
   897  
   898  
   899  		/**
   900  		 * Save the current sorting
   901  		 */
   902  		save: function () {
   903  			var store = this.options.store;
   904  			store && store.set(this);
   905  		},
   906  
   907  
   908  		/**
   909  		 * For each element in the set, get the first element that matches the selector by testing the element itself and traversing up through its ancestors in the DOM tree.
   910  		 * @param   {HTMLElement}  el
   911  		 * @param   {String}       [selector]  default: `options.draggable`
   912  		 * @returns {HTMLElement|null}
   913  		 */
   914  		closest: function (el, selector) {
   915  			return _closest(el, selector || this.options.draggable, this.el);
   916  		},
   917  
   918  
   919  		/**
   920  		 * Set/get option
   921  		 * @param   {string} name
   922  		 * @param   {*}      [value]
   923  		 * @returns {*}
   924  		 */
   925  		option: function (name, value) {
   926  			var options = this.options;
   927  
   928  			if (value === void 0) {
   929  				return options[name];
   930  			} else {
   931  				options[name] = value;
   932  
   933  				if (name === 'group') {
   934  					_prepareGroup(options);
   935  				}
   936  			}
   937  		},
   938  
   939  
   940  		/**
   941  		 * Destroy
   942  		 */
   943  		destroy: function () {
   944  			var el = this.el;
   945  
   946  			el[expando] = null;
   947  
   948  			_off(el, 'mousedown', this._onTapStart);
   949  			_off(el, 'touchstart', this._onTapStart);
   950  
   951  			if (this.nativeDraggable) {
   952  				_off(el, 'dragover', this);
   953  				_off(el, 'dragenter', this);
   954  			}
   955  
   956  			// Remove draggable attributes
   957  			Array.prototype.forEach.call(el.querySelectorAll('[draggable]'), function (el) {
   958  				el.removeAttribute('draggable');
   959  			});
   960  
   961  			touchDragOverListeners.splice(touchDragOverListeners.indexOf(this._onDragOver), 1);
   962  
   963  			this._onDrop();
   964  
   965  			this.el = el = null;
   966  		}
   967  	};
   968  
   969  
   970  	function _cloneHide(state) {
   971  		if (cloneEl && (cloneEl.state !== state)) {
   972  			_css(cloneEl, 'display', state ? 'none' : '');
   973  			!state && cloneEl.state && rootEl.insertBefore(cloneEl, dragEl);
   974  			cloneEl.state = state;
   975  		}
   976  	}
   977  
   978  
   979  	function _closest(/**HTMLElement*/el, /**String*/selector, /**HTMLElement*/ctx) {
   980  		if (el) {
   981  			ctx = ctx || document;
   982  			selector = selector.split('.');
   983  
   984  			var tag = selector.shift().toUpperCase(),
   985  				re = new RegExp('\\s(' + selector.join('|') + ')(?=\\s)', 'g');
   986  
   987  			do {
   988  				if (
   989  					(tag === '>*' && el.parentNode === ctx) || (
   990  						(tag === '' || el.nodeName.toUpperCase() == tag) &&
   991  						(!selector.length || ((' ' + el.className + ' ').match(re) || []).length == selector.length)
   992  					)
   993  				) {
   994  					return el;
   995  				}
   996  			}
   997  			while (el !== ctx && (el = el.parentNode));
   998  		}
   999  
  1000  		return null;
  1001  	}
  1002  
  1003  
  1004  	function _globalDragOver(/**Event*/evt) {
  1005  		if (evt.dataTransfer) {
  1006  			evt.dataTransfer.dropEffect = 'move';
  1007  		}
  1008  		evt.preventDefault();
  1009  	}
  1010  
  1011  
  1012  	function _on(el, event, fn) {
  1013  		el.addEventListener(event, fn, false);
  1014  	}
  1015  
  1016  
  1017  	function _off(el, event, fn) {
  1018  		el.removeEventListener(event, fn, false);
  1019  	}
  1020  
  1021  
  1022  	function _toggleClass(el, name, state) {
  1023  		if (el) {
  1024  			if (el.classList) {
  1025  				el.classList[state ? 'add' : 'remove'](name);
  1026  			}
  1027  			else {
  1028  				var className = (' ' + el.className + ' ').replace(RSPACE, ' ').replace(' ' + name + ' ', ' ');
  1029  				el.className = (className + (state ? ' ' + name : '')).replace(RSPACE, ' ');
  1030  			}
  1031  		}
  1032  	}
  1033  
  1034  
  1035  	function _css(el, prop, val) {
  1036  		var style = el && el.style;
  1037  
  1038  		if (style) {
  1039  			if (val === void 0) {
  1040  				if (document.defaultView && document.defaultView.getComputedStyle) {
  1041  					val = document.defaultView.getComputedStyle(el, '');
  1042  				}
  1043  				else if (el.currentStyle) {
  1044  					val = el.currentStyle;
  1045  				}
  1046  
  1047  				return prop === void 0 ? val : val[prop];
  1048  			}
  1049  			else {
  1050  				if (!(prop in style)) {
  1051  					prop = '-webkit-' + prop;
  1052  				}
  1053  
  1054  				style[prop] = val + (typeof val === 'string' ? '' : 'px');
  1055  			}
  1056  		}
  1057  	}
  1058  
  1059  
  1060  	function _find(ctx, tagName, iterator) {
  1061  		if (ctx) {
  1062  			var list = ctx.getElementsByTagName(tagName), i = 0, n = list.length;
  1063  
  1064  			if (iterator) {
  1065  				for (; i < n; i++) {
  1066  					iterator(list[i], i);
  1067  				}
  1068  			}
  1069  
  1070  			return list;
  1071  		}
  1072  
  1073  		return [];
  1074  	}
  1075  
  1076  
  1077  
  1078  	function _dispatchEvent(sortable, rootEl, name, targetEl, fromEl, startIndex, newIndex) {
  1079  		var evt = document.createEvent('Event'),
  1080  			options = (sortable || rootEl[expando]).options,
  1081  			onName = 'on' + name.charAt(0).toUpperCase() + name.substr(1);
  1082  
  1083  		evt.initEvent(name, true, true);
  1084  
  1085  		evt.to = rootEl;
  1086  		evt.from = fromEl || rootEl;
  1087  		evt.item = targetEl || rootEl;
  1088  		evt.clone = cloneEl;
  1089  
  1090  		evt.oldIndex = startIndex;
  1091  		evt.newIndex = newIndex;
  1092  
  1093  		rootEl.dispatchEvent(evt);
  1094  
  1095  		if (options[onName]) {
  1096  			options[onName].call(sortable, evt);
  1097  		}
  1098  	}
  1099  
  1100  
  1101  	function _onMove(fromEl, toEl, dragEl, dragRect, targetEl, targetRect) {
  1102  		var evt,
  1103  			sortable = fromEl[expando],
  1104  			onMoveFn = sortable.options.onMove,
  1105  			retVal;
  1106  
  1107  		evt = document.createEvent('Event');
  1108  		evt.initEvent('move', true, true);
  1109  
  1110  		evt.to = toEl;
  1111  		evt.from = fromEl;
  1112  		evt.dragged = dragEl;
  1113  		evt.draggedRect = dragRect;
  1114  		evt.related = targetEl || toEl;
  1115  		evt.relatedRect = targetRect || toEl.getBoundingClientRect();
  1116  
  1117  		fromEl.dispatchEvent(evt);
  1118  
  1119  		if (onMoveFn) {
  1120  			retVal = onMoveFn.call(sortable, evt);
  1121  		}
  1122  
  1123  		return retVal;
  1124  	}
  1125  
  1126  
  1127  	function _disableDraggable(el) {
  1128  		el.draggable = false;
  1129  	}
  1130  
  1131  
  1132  	function _unsilent() {
  1133  		_silent = false;
  1134  	}
  1135  
  1136  
  1137  	/** @returns {HTMLElement|false} */
  1138  	function _ghostIsLast(el, evt) {
  1139  		var lastEl = el.lastElementChild,
  1140  				rect = lastEl.getBoundingClientRect();
  1141  
  1142  		return ((evt.clientY - (rect.top + rect.height) > 5) || (evt.clientX - (rect.right + rect.width) > 5)) && lastEl; // min delta
  1143  	}
  1144  
  1145  
  1146  	/**
  1147  	 * Generate id
  1148  	 * @param   {HTMLElement} el
  1149  	 * @returns {String}
  1150  	 * @private
  1151  	 */
  1152  	function _generateId(el) {
  1153  		var str = el.tagName + el.className + el.src + el.href + el.textContent,
  1154  			i = str.length,
  1155  			sum = 0;
  1156  
  1157  		while (i--) {
  1158  			sum += str.charCodeAt(i);
  1159  		}
  1160  
  1161  		return sum.toString(36);
  1162  	}
  1163  
  1164  	/**
  1165  	 * Returns the index of an element within its parent
  1166  	 * @param  {HTMLElement} el
  1167  	 * @return {number}
  1168  	 */
  1169  	function _index(el) {
  1170  		var index = 0;
  1171  
  1172  		if (!el || !el.parentNode) {
  1173  			return -1;
  1174  		}
  1175  
  1176  		while (el && (el = el.previousElementSibling)) {
  1177  			if (el.nodeName.toUpperCase() !== 'TEMPLATE') {
  1178  				index++;
  1179  			}
  1180  		}
  1181  
  1182  		return index;
  1183  	}
  1184  
  1185  	function _throttle(callback, ms) {
  1186  		var args, _this;
  1187  
  1188  		return function () {
  1189  			if (args === void 0) {
  1190  				args = arguments;
  1191  				_this = this;
  1192  
  1193  				setTimeout(function () {
  1194  					if (args.length === 1) {
  1195  						callback.call(_this, args[0]);
  1196  					} else {
  1197  						callback.apply(_this, args);
  1198  					}
  1199  
  1200  					args = void 0;
  1201  				}, ms);
  1202  			}
  1203  		};
  1204  	}
  1205  
  1206  	function _extend(dst, src) {
  1207  		if (dst && src) {
  1208  			for (var key in src) {
  1209  				if (src.hasOwnProperty(key)) {
  1210  					dst[key] = src[key];
  1211  				}
  1212  			}
  1213  		}
  1214  
  1215  		return dst;
  1216  	}
  1217  
  1218  
  1219  	// Export utils
  1220  	Sortable.utils = {
  1221  		on: _on,
  1222  		off: _off,
  1223  		css: _css,
  1224  		find: _find,
  1225  		is: function (el, selector) {
  1226  			return !!_closest(el, selector, el);
  1227  		},
  1228  		extend: _extend,
  1229  		throttle: _throttle,
  1230  		closest: _closest,
  1231  		toggleClass: _toggleClass,
  1232  		index: _index
  1233  	};
  1234  
  1235  
  1236  	/**
  1237  	 * Create sortable instance
  1238  	 * @param {HTMLElement}  el
  1239  	 * @param {Object}      [options]
  1240  	 */
  1241  	Sortable.create = function (el, options) {
  1242  		return new Sortable(el, options);
  1243  	};
  1244  
  1245  
  1246  	// Export
  1247  	Sortable.version = '1.3.0';
  1248  	return Sortable;
  1249  });