Sortable.js 26.1 KB
Newer Older
R
RubaXa 已提交
1 2 3 4 5 6 7
/**!
 * Sortable
 * @author	RubaXa   <trash@rubaxa.org>
 * @license MIT
 */


R
RubaXa 已提交
8
(function (factory) {
R
RubaXa 已提交
9 10
	"use strict";

R
RubaXa 已提交
11
	if (typeof define === "function" && define.amd) {
R
RubaXa 已提交
12
		define(factory);
R
RubaXa 已提交
13
	}
R
RubaXa 已提交
14
	else if (typeof module != "undefined" && typeof module.exports != "undefined") {
S
Scott Nelson 已提交
15 16
		module.exports = factory();
	}
R
RubaXa 已提交
17
	else if (typeof Package !== "undefined") {
18 19
		Sortable = factory();  // export for Meteor.js
	}
R
RubaXa 已提交
20
	else {
R
RubaXa 已提交
21
		/* jshint sub:true */
R
RubaXa 已提交
22 23
		window["Sortable"] = factory();
	}
R
RubaXa 已提交
24
})(function () {
R
RubaXa 已提交
25 26
	"use strict";

R
RubaXa 已提交
27 28 29 30 31
	var dragEl,
		ghostEl,
		cloneEl,
		rootEl,
		nextEl,
R
RubaXa 已提交
32

33 34 35
		scrollEl,
		scrollParentEl,

R
RubaXa 已提交
36 37
		lastEl,
		lastCSS,
R
RubaXa 已提交
38

R
RubaXa 已提交
39 40 41
		oldIndex,
		newIndex,

R
RubaXa 已提交
42
		activeGroup,
R
RubaXa 已提交
43
		autoScroll = {},
R
RubaXa 已提交
44

R
RubaXa 已提交
45 46
		tapEvt,
		touchEvt,
R
RubaXa 已提交
47

R
RubaXa 已提交
48 49 50
		/** @const */
		RSPACE = /\s+/g,

R
RubaXa 已提交
51
		expando = 'Sortable' + (new Date).getTime(),
R
RubaXa 已提交
52

R
RubaXa 已提交
53 54 55
		win = window,
		document = win.document,
		parseInt = win.parseInt,
R
RubaXa 已提交
56 57 58

		supportDraggable = !!('draggable' in document.createElement('div')),

R
RubaXa 已提交
59
		_silent = false,
R
RubaXa 已提交
60

R
RubaXa 已提交
61
		abs = Math.abs,
R
RubaXa 已提交
62
		slice = [].slice,
R
RubaXa 已提交
63

64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129
		touchDragOverListeners = [],

		_autoScroll = _throttle(function (/**Event*/evt, /**Object*/options, /**HTMLElement*/rootEl) {
			// Bug: https://bugzilla.mozilla.org/show_bug.cgi?id=505521
			if (rootEl && options.scroll) {
				var el,
					rect,
					sens = options.scrollSensitivity,
					speed = options.scrollSpeed,

					x = evt.clientX,
					y = evt.clientY,

					winWidth = window.innerWidth,
					winHeight = window.innerHeight,

					vx,
					vy
				;

				// Delect scrollEl
				if (scrollParentEl !== rootEl) {
					scrollEl = options.scroll;
					scrollParentEl = rootEl;

					if (scrollEl === true) {
						scrollEl = rootEl;

						do {
							if ((scrollEl.offsetWidth < scrollEl.scrollWidth) ||
								(scrollEl.offsetHeight < scrollEl.scrollHeight)
							) {
								break;
							}
							/* jshint boss:true */
						} while (scrollEl = scrollEl.parentNode);
					}
				}

				if (scrollEl) {
					el = scrollEl;
					rect = scrollEl.getBoundingClientRect();
					vx = (abs(rect.right - x) <= sens) - (abs(rect.left - x) <= sens);
					vy = (abs(rect.bottom - y) <= sens) - (abs(rect.top - y) <= sens);
				}


				if (!(vx || vy)) {
					vx = (winWidth - x <= sens) - (x <= sens);
					vy = (winHeight - y <= sens) - (y <= sens);

					/* jshint expr:true */
					(vx || vy) && (el = win);
				}


				if (autoScroll.vx !== vx || autoScroll.vy !== vy || autoScroll.el !== el) {
					autoScroll.el = el;
					autoScroll.vx = vx;
					autoScroll.vy = vy;

					clearInterval(autoScroll.pid);

					if (el) {
						autoScroll.pid = setInterval(function () {
							if (el === win) {
130
								win.scrollTo(win.pageXOffset + vx * speed, win.pageYOffset + vy * speed);
131 132 133 134 135 136 137 138 139
							} else {
								vy && (el.scrollTop += vy * speed);
								vx && (el.scrollLeft += vx * speed);
							}
						}, 24);
					}
				}
			}
		}, 30)
R
RubaXa 已提交
140 141 142
	;


143

R
RubaXa 已提交
144 145 146
	/**
	 * @class  Sortable
	 * @param  {HTMLElement}  el
147
	 * @param  {Object}       [options]
R
RubaXa 已提交
148
	 */
R
RubaXa 已提交
149
	function Sortable(el, options) {
R
RubaXa 已提交
150
		this.el = el; // root element
151
		this.options = options = _extend({}, options);
R
RubaXa 已提交
152 153


154 155 156 157
		// Export instance
		el[expando] = this;


R
RubaXa 已提交
158
		// Default options
159 160
		var defaults = {
			group: Math.random(),
R
RubaXa 已提交
161
			sort: true,
R
RubaXa 已提交
162
			disabled: false,
163 164
			store: null,
			handle: null,
R
RubaXa 已提交
165 166 167
			scroll: true,
			scrollSensitivity: 30,
			scrollSpeed: 10,
R
RubaXa 已提交
168
			draggable: /[uo]l/i.test(el.nodeName) ? 'li' : '>*',
169 170
			ghostClass: 'sortable-ghost',
			ignore: 'a, img',
171
			filter: null,
R
RubaXa 已提交
172 173 174
			animation: 0,
			setData: function (dataTransfer, dragEl) {
				dataTransfer.setData('Text', dragEl.textContent);
175 176
			},
			dropBubble: false,
R
RubaXa 已提交
177
			dragoverBubble: false,
178
			dataIdAttr: 'data-id',
179 180
			delay: 0,
			forcePolyfill: false
R
RubaXa 已提交
181
		};
182

R
RubaXa 已提交
183

184 185
		// Set default options
		for (var name in defaults) {
R
RubaXa 已提交
186
			!(name in options) && (options[name] = defaults[name]);
187
		}
R
RubaXa 已提交
188

189

R
RubaXa 已提交
190 191
		var group = options.group;

R
RubaXa 已提交
192 193
		if (!group || typeof group != 'object') {
			group = options.group = { name: group };
R
RubaXa 已提交
194 195
		}

R
RubaXa 已提交
196

R
RubaXa 已提交
197
		['pull', 'put'].forEach(function (key) {
R
RubaXa 已提交
198 199
			if (!(key in group)) {
				group[key] = true;
R
RubaXa 已提交
200 201 202 203
			}
		});


204
		options.groups = ' ' + group.name + (group.put.join ? ' ' + group.put.join(' ') : '') + ' ';
R
RubaXa 已提交
205 206


R
* JSDoc  
RubaXa 已提交
207
		// Bind all private methods
R
RubaXa 已提交
208 209
		for (var fn in this) {
			if (fn.charAt(0) === '_') {
R
RubaXa 已提交
210 211 212 213 214 215 216 217 218
				this[fn] = _bind(this, this[fn]);
			}
		}


		// Bind events
		_on(el, 'mousedown', this._onTapStart);
		_on(el, 'touchstart', this._onTapStart);

R
RubaXa 已提交
219 220
		_on(el, 'dragover', this);
		_on(el, 'dragenter', this);
R
RubaXa 已提交
221 222

		touchDragOverListeners.push(this._onDragOver);
223 224 225

		// Restore sorting
		options.store && this.sort(options.store.get(this));
R
RubaXa 已提交
226 227 228
	}


229
	Sortable.prototype = /** @lends Sortable.prototype */ {
R
RubaXa 已提交
230 231
		constructor: Sortable,

232
		_onTapStart: function (/** Event|TouchEvent */evt) {
233 234
			var _this = this,
				el = this.el,
235 236 237 238 239 240
				options = this.options,
				type = evt.type,
				touch = evt.touches && evt.touches[0],
				target = (touch || evt).target,
				originalTarget = target,
				filter = options.filter;
R
RubaXa 已提交
241 242


243 244
			if (type === 'mousedown' && evt.button !== 0 || options.disabled) {
				return; // only left button or enabled
R
RubaXa 已提交
245
			}
R
RubaXa 已提交
246

247
			target = _closest(target, options.draggable, el);
248

249 250
			if (!target) {
				return;
251
			}
252 253 254 255 256 257 258

			// get the index of the dragged element within its parent
			oldIndex = _index(target);

			// Check filter
			if (typeof filter === 'function') {
				if (filter.call(this, evt, target, this)) {
259
					_dispatchEvent(_this, originalTarget, 'filter', target, el, oldIndex);
260 261 262
					evt.preventDefault();
					return; // cancel dnd
				}
263
			}
264 265 266
			else if (filter) {
				filter = filter.split(',').some(function (criteria) {
					criteria = _closest(originalTarget, criteria.trim(), el);
267

268
					if (criteria) {
269
						_dispatchEvent(_this, criteria, 'filter', target, el, oldIndex);
270 271 272 273 274 275 276
						return true;
					}
				});

				if (filter) {
					evt.preventDefault();
					return; // cancel dnd
277 278 279 280
				}
			}


281 282 283
			if (options.handle && !_closest(originalTarget, options.handle, el)) {
				return;
			}
284

285 286 287

			// Prepare `dragstart`
			this._prepareDragStart(evt, touch, target);
288 289
		},

290 291 292 293 294 295
		_prepareDragStart: function (/** Event */evt, /** Touch */touch, /** HTMLElement */target) {
			var _this = this,
				el = _this.el,
				options = _this.options,
				ownerDocument = el.ownerDocument,
				dragStartFn;
296 297 298 299

			if (target && !dragEl && (target.parentNode === el)) {
				tapEvt = evt;

300
				rootEl = el;
301 302
				dragEl = target;
				nextEl = dragEl.nextSibling;
303 304 305 306 307 308
				activeGroup = options.group;

				dragStartFn = function () {
					// Delayed drag has been triggered
					// we can re-enable the events: touchmove/mousemove
					_this._disableDelayedDrag();
309

310 311
					// Make the element draggable
					dragEl.draggable = true;
312

313 314 315 316 317 318 319 320 321 322 323 324 325 326
					// Disable "draggable"
					options.ignore.split(',').forEach(function (criteria) {
						_find(dragEl, criteria.trim(), _disableDraggable);
					});

					// Bind the events: dragstart/dragend
					_this._triggerDragStart(touch);
				};

				_on(ownerDocument, 'mouseup', _this._onDrop);
				_on(ownerDocument, 'touchend', _this._onDrop);
				_on(ownerDocument, 'touchcancel', _this._onDrop);

				if (options.delay) {
327 328
					// If the user moves the pointer or let go the click or touch
					// before the delay has been reached:
329
					// disable the delayed drag
330 331 332
					_on(ownerDocument, 'mouseup', _this._disableDelayedDrag);
					_on(ownerDocument, 'touchend', _this._disableDelayedDrag);
					_on(ownerDocument, 'touchcancel', _this._disableDelayedDrag);
333 334 335 336 337 338
					_on(ownerDocument, 'mousemove', _this._disableDelayedDrag);
					_on(ownerDocument, 'touchmove', _this._disableDelayedDrag);

					_this._dragStartTimer = setTimeout(dragStartFn, options.delay);
				} else {
					dragStartFn();
339 340 341
				}
			}
		},
R
RubaXa 已提交
342

343 344
		_disableDelayedDrag: function () {
			var ownerDocument = this.el.ownerDocument;
345

346
			clearTimeout(this._dragStartTimer);
347 348 349
			_off(ownerDocument, 'mouseup', this._disableDelayedDrag);
			_off(ownerDocument, 'touchend', this._disableDelayedDrag);
			_off(ownerDocument, 'touchcancel', this._disableDelayedDrag);
350 351 352
			_off(ownerDocument, 'mousemove', this._disableDelayedDrag);
			_off(ownerDocument, 'touchmove', this._disableDelayedDrag);
		},
R
RubaXa 已提交
353

354 355 356 357 358 359 360 361
		_triggerDragStart: function (/** Touch */touch) {
			if (touch) {
				// Touch device support
				tapEvt = {
					target: dragEl,
					clientX: touch.clientX,
					clientY: touch.clientY
				};
362

363
				this._onDragStart(tapEvt, 'touch');
R
RubaXa 已提交
364
			}
365
			else if (!supportDraggable || this.options.forcePolyfill) {
366 367 368 369 370
				this._onDragStart(tapEvt, true);
			}
			else {
				_on(dragEl, 'dragend', this);
				_on(rootEl, 'dragstart', this._onDragStart);
371 372
			}

373 374 375 376 377
			try {
				if (document.selection) {
					document.selection.empty();
				} else {
					window.getSelection().removeAllRanges();
378
				}
379
			} catch (err) {
380
			}
381
		},
382

383 384 385 386
		_dragStarted: function () {
			if (rootEl && dragEl) {
				// Apply effect
				_toggleClass(dragEl, this.options.ghostClass, true);
R
RubaXa 已提交
387

388
				Sortable.active = this;
389

390
				// Drag start event
391
				_dispatchEvent(this, rootEl, 'start', dragEl, rootEl, oldIndex);
392
			}
R
RubaXa 已提交
393 394
		},

R
RubaXa 已提交
395 396
		_emulateDragOver: function () {
			if (touchEvt) {
R
RubaXa 已提交
397 398
				_css(ghostEl, 'display', 'none');

R
RubaXa 已提交
399
				var target = document.elementFromPoint(touchEvt.clientX, touchEvt.clientY),
R
RubaXa 已提交
400
					parent = target,
401
					groupName = ' ' + this.options.group.name + '',
R
RubaXa 已提交
402
					i = touchDragOverListeners.length;
R
RubaXa 已提交
403

R
RubaXa 已提交
404 405
				if (parent) {
					do {
406
						if (parent[expando] && parent[expando].options.groups.indexOf(groupName) > -1) {
R
RubaXa 已提交
407 408 409 410 411 412 413 414 415 416 417 418 419
							while (i--) {
								touchDragOverListeners[i]({
									clientX: touchEvt.clientX,
									clientY: touchEvt.clientY,
									target: target,
									rootEl: parent
								});
							}

							break;
						}

						target = parent; // store last element
L
Larry Davis 已提交
420
					}
R
RubaXa 已提交
421 422
					/* jshint boss:true */
					while (parent = parent.parentNode);
R
RubaXa 已提交
423 424 425 426 427 428 429
				}

				_css(ghostEl, 'display', '');
			}
		},


R
RubaXa 已提交
430 431
		_onTouchMove: function (/**TouchEvent*/evt) {
			if (tapEvt) {
432 433 434 435 436 437
				// only set the status to dragging, when we are actually dragging
				if(!Sortable.active) {
					this._dragStarted();
				}
				// as well as creating the ghost element on the document body
				this._appendGhost();
R
RubaXa 已提交
438
				var touch = evt.touches ? evt.touches[0] : evt,
R
RubaXa 已提交
439 440
					dx = touch.clientX - tapEvt.clientX,
					dy = touch.clientY - tapEvt.clientY,
R
RubaXa 已提交
441
					translate3d = evt.touches ? 'translate3d(' + dx + 'px,' + dy + 'px,0)' : 'translate(' + dx + 'px,' + dy + 'px)';
R
RubaXa 已提交
442 443

				touchEvt = touch;
R
RubaXa 已提交
444 445 446 447 448 449

				_css(ghostEl, 'webkitTransform', translate3d);
				_css(ghostEl, 'mozTransform', translate3d);
				_css(ghostEl, 'msTransform', translate3d);
				_css(ghostEl, 'transform', translate3d);

M
Marius Petcu 已提交
450
				evt.preventDefault();
R
RubaXa 已提交
451 452 453
			}
		},

454 455
		_appendGhost: function() {
			if(!ghostEl) {
R
RubaXa 已提交
456 457 458
				var rect = dragEl.getBoundingClientRect(),
					css = _css(dragEl),
					ghostRect;
R
RubaXa 已提交
459

460
				ghostEl = dragEl.cloneNode(true);
R
RubaXa 已提交
461 462 463

				_css(ghostEl, 'top', rect.top - parseInt(css.marginTop, 10));
				_css(ghostEl, 'left', rect.left - parseInt(css.marginLeft, 10));
R
RubaXa 已提交
464 465
				_css(ghostEl, 'width', rect.width);
				_css(ghostEl, 'height', rect.height);
R
RubaXa 已提交
466 467 468 469
				_css(ghostEl, 'opacity', '0.8');
				_css(ghostEl, 'position', 'fixed');
				_css(ghostEl, 'zIndex', '100000');

470
				document.body.appendChild(ghostEl);
R
RubaXa 已提交
471 472 473

				// Fixing dimensions.
				ghostRect = ghostEl.getBoundingClientRect();
R
RubaXa 已提交
474 475
				_css(ghostEl, 'width', rect.width * 2 - ghostRect.width);
				_css(ghostEl, 'height', rect.height * 2 - ghostRect.height);
476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491
			}
		},

		_onDragStart: function (/**Event*/evt, /**boolean*/useFallback) {
			var dataTransfer = evt.dataTransfer,
				options = this.options;

			this._offUpEvents();

			if (activeGroup.pull == 'clone') {
				cloneEl = dragEl.cloneNode(true);
				_css(cloneEl, 'display', 'none');
				rootEl.insertBefore(cloneEl, dragEl);
			}

			if (useFallback) {
R
RubaXa 已提交
492

R
RubaXa 已提交
493 494 495 496 497 498 499 500 501 502
				if (useFallback === 'touch') {
					// Bind touch events
					_on(document, 'touchmove', this._onTouchMove);
					_on(document, 'touchend', this._onDrop);
					_on(document, 'touchcancel', this._onDrop);
				} else {
					// Old brwoser
					_on(document, 'mousemove', this._onTouchMove);
					_on(document, 'mouseup', this._onDrop);
				}
R
RubaXa 已提交
503

R
RubaXa 已提交
504
				this._loopId = setInterval(this._emulateDragOver, 150);
R
RubaXa 已提交
505 506
			}
			else {
R
RubaXa 已提交
507 508 509 510
				if (dataTransfer) {
					dataTransfer.effectAllowed = 'move';
					options.setData && options.setData.call(this, dataTransfer, dragEl);
				}
R
RubaXa 已提交
511

R
RubaXa 已提交
512
				_on(document, 'drop', this);
513
				setTimeout(this._dragStarted, 0);
R
RubaXa 已提交
514
			}
515
			
R
RubaXa 已提交
516 517
		},

R
RubaXa 已提交
518
		_onDragOver: function (/**Event*/evt) {
R
RubaXa 已提交
519 520 521 522 523 524
			var el = this.el,
				target,
				dragRect,
				revert,
				options = this.options,
				group = options.group,
R
RubaXa 已提交
525
				groupPut = group.put,
526 527
				isOwner = (activeGroup === group),
				canSort = options.sort;
R
RubaXa 已提交
528

R
RubaXa 已提交
529 530
			if (evt.preventDefault !== void 0) {
				evt.preventDefault();
531
				!options.dragoverBubble && evt.stopPropagation();
R
RubaXa 已提交
532
			}
R
RubaXa 已提交
533

R
RubaXa 已提交
534
			if (activeGroup && !options.disabled &&
535
				(isOwner
R
RubaXa 已提交
536
					? canSort || (revert = !rootEl.contains(dragEl)) // Reverting item into the original list
R
RubaXa 已提交
537 538 539 540
					: activeGroup.pull && groupPut && (
						(activeGroup.name === group.name) || // by Name
						(groupPut.indexOf && ~groupPut.indexOf(activeGroup.name)) // by Array
					)
541
				) &&
R
RubaXa 已提交
542
				(evt.rootEl === void 0 || evt.rootEl === this.el) // touch fallback
R
RubaXa 已提交
543
			) {
R
RubaXa 已提交
544 545 546 547 548 549 550
				// Smart auto-scrolling
				_autoScroll(evt, options, this.el);

				if (_silent) {
					return;
				}

R
RubaXa 已提交
551
				target = _closest(evt.target, options.draggable, el);
R
RubaXa 已提交
552 553 554
				dragRect = dragEl.getBoundingClientRect();


555
				if (revert) {
R
RubaXa 已提交
556 557
					_cloneHide(true);

558 559 560 561 562 563 564
					if (cloneEl || nextEl) {
						rootEl.insertBefore(dragEl, cloneEl || nextEl);
					}
					else if (!canSort) {
						rootEl.appendChild(dragEl);
					}

R
RubaXa 已提交
565 566
					return;
				}
R
RubaXa 已提交
567

R
RubaXa 已提交
568

R
RubaXa 已提交
569
				if ((el.children.length === 0) || (el.children[0] === ghostEl) ||
R
RubaXa 已提交
570
					(el === evt.target) && (target = _ghostInBottom(el, evt))
R
RubaXa 已提交
571
				) {
R
RubaXa 已提交
572 573 574 575 576 577
					if (target) {
						if (target.animated) {
							return;
						}
						targetRect = target.getBoundingClientRect();
					}
R
RubaXa 已提交
578

R
RubaXa 已提交
579 580
					_cloneHide(isOwner);

R
RubaXa 已提交
581
					if (_onMove(rootEl, el, dragEl, dragRect, target, targetRect) !== false) {
R
RubaXa 已提交
582 583 584 585
						el.appendChild(dragEl);
						this._animate(dragRect, dragEl);
						target && this._animate(targetRect, target);
					}
R
RubaXa 已提交
586
				}
R
RubaXa 已提交
587 588
				else if (target && !target.animated && target !== dragEl && (target.parentNode[expando] !== void 0)) {
					if (lastEl !== target) {
R
RubaXa 已提交
589
						lastEl = target;
R
RubaXa 已提交
590
						lastCSS = _css(target);
R
RubaXa 已提交
591 592 593
					}


R
RubaXa 已提交
594 595 596 597 598 599 600 601
					var targetRect = target.getBoundingClientRect(),
						width = targetRect.right - targetRect.left,
						height = targetRect.bottom - targetRect.top,
						floating = /left|right|inline/.test(lastCSS.cssFloat + lastCSS.display),
						isWide = (target.offsetWidth > dragEl.offsetWidth),
						isLong = (target.offsetHeight > dragEl.offsetHeight),
						halfway = (floating ? (evt.clientX - targetRect.left) / width : (evt.clientY - targetRect.top) / height) > 0.5,
						nextSibling = target.nextElementSibling,
R
RubaXa 已提交
602
						moveVector = _onMove(rootEl, el, dragEl, dragRect, target, targetRect),
R
RubaXa 已提交
603
						after
R
RubaXa 已提交
604
					;
R
RubaXa 已提交
605

R
RubaXa 已提交
606
					if (moveVector !== false) {
R
RubaXa 已提交
607 608
						_silent = true;
						setTimeout(_unsilent, 30);
R
RubaXa 已提交
609

R
RubaXa 已提交
610
						_cloneHide(isOwner);
R
RubaXa 已提交
611

R
RubaXa 已提交
612 613 614 615
						if (moveVector === 1 || moveVector === -1) {
							after = (moveVector === 1);
						}
						else if (floating) {
R
RubaXa 已提交
616 617 618 619
							after = (target.previousElementSibling === dragEl) && !isWide || halfway && isWide;
						} else {
							after = (nextSibling !== dragEl) && !isLong || halfway && isLong;
						}
R
RubaXa 已提交
620

R
RubaXa 已提交
621 622 623 624 625
						if (after && !nextSibling) {
							el.appendChild(dragEl);
						} else {
							target.parentNode.insertBefore(dragEl, after ? nextSibling : target);
						}
R
RubaXa 已提交
626

R
RubaXa 已提交
627 628 629
						this._animate(dragRect, dragEl);
						this._animate(targetRect, target);
					}
R
RubaXa 已提交
630 631 632 633
				}
			}
		},

634 635 636 637 638 639
		_animate: function (prevRect, target) {
			var ms = this.options.animation;

			if (ms) {
				var currentRect = target.getBoundingClientRect();

R
RubaXa 已提交
640
				_css(target, 'transition', 'none');
641 642 643 644 645 646 647
				_css(target, 'transform', 'translate3d('
					+ (prevRect.left - currentRect.left) + 'px,'
					+ (prevRect.top - currentRect.top) + 'px,0)'
				);

				target.offsetWidth; // repaint

R
RubaXa 已提交
648
				_css(target, 'transition', 'all ' + ms + 'ms');
649 650
				_css(target, 'transform', 'translate3d(0,0,0)');

R
* anim  
RubaXa 已提交
651 652
				clearTimeout(target.animated);
				target.animated = setTimeout(function () {
653
					_css(target, 'transition', '');
654
					_css(target, 'transform', '');
655 656 657 658 659
					target.animated = false;
				}, ms);
			}
		},

660
		_offUpEvents: function () {
661 662
			var ownerDocument = this.el.ownerDocument;

663
			_off(document, 'touchmove', this._onTouchMove);
664 665 666
			_off(ownerDocument, 'mouseup', this._onDrop);
			_off(ownerDocument, 'touchend', this._onDrop);
			_off(ownerDocument, 'touchcancel', this._onDrop);
667
		},
R
RubaXa 已提交
668

R
RubaXa 已提交
669
		_onDrop: function (/**Event*/evt) {
670 671
			var el = this.el,
				options = this.options;
R
RubaXa 已提交
672

R
RubaXa 已提交
673
			clearInterval(this._loopId);
R
RubaXa 已提交
674
			clearInterval(autoScroll.pid);
R
RubaXa 已提交
675
			clearTimeout(this._dragStartTimer);
676

R
RubaXa 已提交
677
			// Unbind events
R
RubaXa 已提交
678
			_off(document, 'drop', this);
R
RubaXa 已提交
679
			_off(document, 'mousemove', this._onTouchMove);
R
RubaXa 已提交
680
			_off(el, 'dragstart', this._onDragStart);
R
RubaXa 已提交
681

682
			this._offUpEvents();
R
RubaXa 已提交
683

R
RubaXa 已提交
684
			if (evt) {
R
RubaXa 已提交
685
				evt.preventDefault();
686
				!options.dropBubble && evt.stopPropagation();
R
RubaXa 已提交
687

R
RubaXa 已提交
688
				ghostEl && ghostEl.parentNode.removeChild(ghostEl);
R
RubaXa 已提交
689

R
RubaXa 已提交
690
				if (dragEl) {
R
RubaXa 已提交
691 692
					_off(dragEl, 'dragend', this);

693
					_disableDraggable(dragEl);
R
RubaXa 已提交
694 695
					_toggleClass(dragEl, this.options.ghostClass, false);

696
					if (rootEl !== dragEl.parentNode) {
R
RubaXa 已提交
697 698
						newIndex = _index(dragEl);

699
						// drag from one list and drop into another
700 701
						_dispatchEvent(null, dragEl.parentNode, 'sort', dragEl, rootEl, oldIndex, newIndex);
						_dispatchEvent(this, rootEl, 'sort', dragEl, rootEl, oldIndex, newIndex);
R
RubaXa 已提交
702 703

						// Add event
704
						_dispatchEvent(null, dragEl.parentNode, 'add', dragEl, rootEl, oldIndex, newIndex);
705 706

						// Remove event
707
						_dispatchEvent(this, rootEl, 'remove', dragEl, rootEl, oldIndex, newIndex);
R
RubaXa 已提交
708
					}
R
RubaXa 已提交
709 710
					else {
						// Remove clone
R
RubaXa 已提交
711
						cloneEl && cloneEl.parentNode.removeChild(cloneEl);
R
RubaXa 已提交
712

R
RubaXa 已提交
713 714 715
						if (dragEl.nextSibling !== nextEl) {
							// Get the index of the dragged element within its parent
							newIndex = _index(dragEl);
R
RubaXa 已提交
716

R
RubaXa 已提交
717
							// drag & drop within the same list
718 719
							_dispatchEvent(this, rootEl, 'update', dragEl, rootEl, oldIndex, newIndex);
							_dispatchEvent(this, rootEl, 'sort', dragEl, rootEl, oldIndex, newIndex);
R
RubaXa 已提交
720
						}
R
RubaXa 已提交
721
					}
722

R
RubaXa 已提交
723 724 725 726 727 728 729
					if (Sortable.active) {
						// Drag end event
						_dispatchEvent(this, rootEl, 'end', dragEl, rootEl, oldIndex, newIndex);

						// Save sorting
						this.save();
					}
R
RubaXa 已提交
730 731
				}

732
				// Nulling
R
RubaXa 已提交
733 734 735 736
				rootEl =
				dragEl =
				ghostEl =
				nextEl =
R
RubaXa 已提交
737
				cloneEl =
R
RubaXa 已提交
738

739 740 741
				scrollEl =
				scrollParentEl =

R
RubaXa 已提交
742 743 744 745 746 747
				tapEvt =
				touchEvt =

				lastEl =
				lastCSS =

R
RubaXa 已提交
748 749
				activeGroup =
				Sortable.active = null;
R
RubaXa 已提交
750 751 752 753
			}
		},


R
RubaXa 已提交
754 755 756
		handleEvent: function (/**Event*/evt) {
			var type = evt.type;

R
RubaXa 已提交
757
			if (type === 'dragover' || type === 'dragenter') {
R
RubaXa 已提交
758 759 760 761
				if (dragEl) {
					this._onDragOver(evt);
					_globalDragOver(evt);
				}
R
RubaXa 已提交
762
			}
R
RubaXa 已提交
763
			else if (type === 'drop' || type === 'dragend') {
R
RubaXa 已提交
764 765
				this._onDrop(evt);
			}
R
RubaXa 已提交
766 767 768
		},


769 770 771 772 773 774 775 776 777
		/**
		 * Serializes the item into an array of string.
		 * @returns {String[]}
		 */
		toArray: function () {
			var order = [],
				el,
				children = this.el.children,
				i = 0,
R
RubaXa 已提交
778 779
				n = children.length,
				options = this.options;
780 781 782

			for (; i < n; i++) {
				el = children[i];
R
RubaXa 已提交
783
				if (_closest(el, options.draggable, this.el)) {
R
RubaXa 已提交
784
					order.push(el.getAttribute(options.dataIdAttr) || _generateId(el));
R
RubaXa 已提交
785
				}
786 787 788 789 790 791 792 793 794 795 796
			}

			return order;
		},


		/**
		 * Sorts the elements according to the array.
		 * @param  {String[]}  order  order of the items
		 */
		sort: function (order) {
R
RubaXa 已提交
797
			var items = {}, rootEl = this.el;
798 799

			this.toArray().forEach(function (id, i) {
R
RubaXa 已提交
800 801
				var el = rootEl.children[i];

R
RubaXa 已提交
802
				if (_closest(el, this.options.draggable, rootEl)) {
R
RubaXa 已提交
803 804 805
					items[id] = el;
				}
			}, this);
806 807 808

			order.forEach(function (id) {
				if (items[id]) {
R
RubaXa 已提交
809 810
					rootEl.removeChild(items[id]);
					rootEl.appendChild(items[id]);
811 812 813 814 815
				}
			});
		},


R
RubaXa 已提交
816 817 818 819 820 821 822 823 824
		/**
		 * Save the current sorting
		 */
		save: function () {
			var store = this.options.store;
			store && store.set(this);
		},


825 826 827 828 829 830 831 832 833 834 835
		/**
		 * 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.
		 * @param   {HTMLElement}  el
		 * @param   {String}       [selector]  default: `options.draggable`
		 * @returns {HTMLElement|null}
		 */
		closest: function (el, selector) {
			return _closest(el, selector || this.options.draggable, this.el);
		},


836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852
		/**
		 * Set/get option
		 * @param   {string} name
		 * @param   {*}      [value]
		 * @returns {*}
		 */
		option: function (name, value) {
			var options = this.options;

			if (value === void 0) {
				return options[name];
			} else {
				options[name] = value;
			}
		},


853 854 855 856
		/**
		 * Destroy
		 */
		destroy: function () {
857
			var el = this.el;
R
RubaXa 已提交
858

859
			el[expando] = null;
860

R
RubaXa 已提交
861 862 863
			_off(el, 'mousedown', this._onTapStart);
			_off(el, 'touchstart', this._onTapStart);

R
RubaXa 已提交
864 865
			_off(el, 'dragover', this);
			_off(el, 'dragenter', this);
R
RubaXa 已提交
866

867
			// Remove draggable attributes
R
RubaXa 已提交
868
			Array.prototype.forEach.call(el.querySelectorAll('[draggable]'), function (el) {
869 870 871
				el.removeAttribute('draggable');
			});

R
RubaXa 已提交
872 873 874 875
			touchDragOverListeners.splice(touchDragOverListeners.indexOf(this._onDragOver), 1);

			this._onDrop();

876
			this.el = el = null;
R
RubaXa 已提交
877 878 879
		}
	};

880

R
RubaXa 已提交
881 882 883 884 885 886 887 888 889
	function _cloneHide(state) {
		if (cloneEl && (cloneEl.state !== state)) {
			_css(cloneEl, 'display', state ? 'none' : '');
			!state && cloneEl.state && rootEl.insertBefore(cloneEl, dragEl);
			cloneEl.state = state;
		}
	}


R
RubaXa 已提交
890
	function _bind(ctx, fn) {
R
RubaXa 已提交
891
		var args = slice.call(arguments, 2);
R
RubaXa 已提交
892
		return	fn.bind ? fn.bind.apply(fn, [ctx].concat(args)) : function () {
R
RubaXa 已提交
893 894 895 896 897
			return fn.apply(ctx, args.concat(slice.call(arguments)));
		};
	}


R
RubaXa 已提交
898
	function _closest(/**HTMLElement*/el, /**String*/selector, /**HTMLElement*/ctx) {
R
RubaXa 已提交
899
		if (el) {
R
RubaXa 已提交
900 901 902
			ctx = ctx || document;
			selector = selector.split('.');

R
RubaXa 已提交
903
			var tag = selector.shift().toUpperCase(),
904
				re = new RegExp('\\s(' + selector.join('|') + ')(?=\\s)', 'g');
R
RubaXa 已提交
905 906

			do {
R
RubaXa 已提交
907
				if (
R
RubaXa 已提交
908
					(tag === '>*' && el.parentNode === ctx) || (
R
raphj 已提交
909
						(tag === '' || el.nodeName.toUpperCase() == tag) &&
R
RubaXa 已提交
910 911
						(!selector.length || ((' ' + el.className + ' ').match(re) || []).length == selector.length)
					)
R
RubaXa 已提交
912 913
				) {
					return el;
R
RubaXa 已提交
914 915
				}
			}
R
RubaXa 已提交
916
			while (el !== ctx && (el = el.parentNode));
R
RubaXa 已提交
917 918
		}

R
RubaXa 已提交
919
		return null;
R
RubaXa 已提交
920 921 922
	}


923
	function _globalDragOver(/**Event*/evt) {
R
RubaXa 已提交
924 925 926 927 928
		evt.dataTransfer.dropEffect = 'move';
		evt.preventDefault();
	}


R
RubaXa 已提交
929
	function _on(el, event, fn) {
R
RubaXa 已提交
930 931 932 933
		el.addEventListener(event, fn, false);
	}


R
RubaXa 已提交
934
	function _off(el, event, fn) {
R
RubaXa 已提交
935 936 937 938
		el.removeEventListener(event, fn, false);
	}


R
RubaXa 已提交
939 940 941
	function _toggleClass(el, name, state) {
		if (el) {
			if (el.classList) {
R
RubaXa 已提交
942 943 944
				el.classList[state ? 'add' : 'remove'](name);
			}
			else {
B
Bogdan Mustiata 已提交
945 946
				var className = (' ' + el.className + ' ').replace(RSPACE, ' ').replace(' ' + name + ' ', ' ');
				el.className = (className + (state ? ' ' + name : '')).replace(RSPACE, ' ');
R
RubaXa 已提交
947 948 949 950 951
			}
		}
	}


R
RubaXa 已提交
952
	function _css(el, prop, val) {
R
RubaXa 已提交
953 954
		var style = el && el.style;

R
RubaXa 已提交
955 956 957
		if (style) {
			if (val === void 0) {
				if (document.defaultView && document.defaultView.getComputedStyle) {
R
RubaXa 已提交
958 959
					val = document.defaultView.getComputedStyle(el, '');
				}
R
RubaXa 已提交
960 961
				else if (el.currentStyle) {
					val = el.currentStyle;
R
RubaXa 已提交
962
				}
R
RubaXa 已提交
963 964 965 966 967 968 969 970 971

				return prop === void 0 ? val : val[prop];
			}
			else {
				if (!(prop in style)) {
					prop = '-webkit-' + prop;
				}

				style[prop] = val + (typeof val === 'string' ? '' : 'px');
R
RubaXa 已提交
972 973 974 975 976
			}
		}
	}


R
RubaXa 已提交
977 978
	function _find(ctx, tagName, iterator) {
		if (ctx) {
R
RubaXa 已提交
979
			var list = ctx.getElementsByTagName(tagName), i = 0, n = list.length;
R
RubaXa 已提交
980

R
RubaXa 已提交
981 982
			if (iterator) {
				for (; i < n; i++) {
R
RubaXa 已提交
983 984 985
					iterator(list[i], i);
				}
			}
R
RubaXa 已提交
986

R
RubaXa 已提交
987
			return list;
R
RubaXa 已提交
988
		}
R
RubaXa 已提交
989 990

		return [];
R
RubaXa 已提交
991 992 993
	}


R
RubaXa 已提交
994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009

	function _dispatchEvent(sortable, rootEl, name, targetEl, fromEl, startIndex, newIndex) {
		var evt = document.createEvent('Event'),
			options = (sortable || rootEl[expando]).options,
			onName = 'on' + name.charAt(0).toUpperCase() + name.substr(1);

		evt.initEvent(name, true, true);

		evt.to = rootEl;
		evt.from = fromEl || rootEl;
		evt.item = targetEl || rootEl;
		evt.clone = cloneEl;

		evt.oldIndex = startIndex;
		evt.newIndex = newIndex;

R
RubaXa 已提交
1010 1011
		rootEl.dispatchEvent(evt);

R
RubaXa 已提交
1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037
		if (options[onName]) {
			options[onName].call(sortable, evt);
		}
	}


	function _onMove(fromEl, toEl, dragEl, dragRect, targetEl, targetRect) {
		var evt,
			sortable = fromEl[expando],
			onMoveFn = sortable.options.onMove,
			retVal;

		if (onMoveFn) {
			evt = document.createEvent('Event');
			evt.initEvent('move', true, true);

			evt.to = toEl;
			evt.from = fromEl;
			evt.dragged = dragEl;
			evt.draggedRect = dragRect;
			evt.related = targetEl || toEl;
			evt.relatedRect = targetRect || toEl.getBoundingClientRect();

			retVal = onMoveFn.call(sortable, evt);
		}

R
RubaXa 已提交
1038
		return retVal;
R
RubaXa 已提交
1039 1040 1041
	}


R
RubaXa 已提交
1042
	function _disableDraggable(el) {
R
RubaXa 已提交
1043
		el.draggable = false;
R
RubaXa 已提交
1044 1045 1046
	}


R
RubaXa 已提交
1047
	function _unsilent() {
R
RubaXa 已提交
1048 1049 1050 1051
		_silent = false;
	}


R
RubaXa 已提交
1052
	/** @returns {HTMLElement|false} */
R
RubaXa 已提交
1053
	function _ghostInBottom(el, evt) {
R
RubaXa 已提交
1054 1055 1056
		var lastEl = el.lastElementChild,
			rect = lastEl.getBoundingClientRect();

R
RubaXa 已提交
1057
		return (evt.clientY - (rect.top + rect.height) > 5) && lastEl; // min delta
R
RubaXa 已提交
1058 1059 1060
	}


1061 1062 1063 1064 1065 1066 1067
	/**
	 * Generate id
	 * @param   {HTMLElement} el
	 * @returns {String}
	 * @private
	 */
	function _generateId(el) {
R
RubaXa 已提交
1068
		var str = el.tagName + el.className + el.src + el.href + el.textContent,
1069
			i = str.length,
R
RubaXa 已提交
1070
			sum = 0;
1071

1072 1073 1074
		while (i--) {
			sum += str.charCodeAt(i);
		}
1075

1076 1077 1078
		return sum.toString(36);
	}

1079 1080 1081
	/**
	 * Returns the index of an element within its parent
	 * @param el
1082 1083
	 * @returns {number}
	 * @private
1084 1085 1086
	 */
	function _index(/**HTMLElement*/el) {
		var index = 0;
1087 1088 1089 1090
		while (el && (el = el.previousElementSibling)) {
			if (el.nodeName.toUpperCase() !== 'TEMPLATE') {
				index++;
			}
1091 1092 1093
		}
		return index;
	}
R
RubaXa 已提交
1094

R
RubaXa 已提交
1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115
	function _throttle(callback, ms) {
		var args, _this;

		return function () {
			if (args === void 0) {
				args = arguments;
				_this = this;

				setTimeout(function () {
					if (args.length === 1) {
						callback.call(_this, args[0]);
					} else {
						callback.apply(_this, args);
					}

					args = void 0;
				}, ms);
			}
		};
	}

1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127
	function _extend(dst, src) {
		if (dst && src) {
			for (var key in src) {
				if (src.hasOwnProperty(key)) {
					dst[key] = src[key];
				}
			}
		}

		return dst;
	}

R
RubaXa 已提交
1128

R
RubaXa 已提交
1129 1130 1131 1132 1133 1134 1135
	// Export utils
	Sortable.utils = {
		on: _on,
		off: _off,
		css: _css,
		find: _find,
		bind: _bind,
R
RubaXa 已提交
1136 1137 1138
		is: function (el, selector) {
			return !!_closest(el, selector, el);
		},
1139
		extend: _extend,
R
RubaXa 已提交
1140
		throttle: _throttle,
R
RubaXa 已提交
1141
		closest: _closest,
1142
		toggleClass: _toggleClass,
1143
		index: _index
R
RubaXa 已提交
1144 1145 1146
	};


1147
	Sortable.version = '1.2.0';
1148

R
RubaXa 已提交
1149

1150 1151 1152 1153 1154 1155
	/**
	 * Create sortable instance
	 * @param {HTMLElement}  el
	 * @param {Object}      [options]
	 */
	Sortable.create = function (el, options) {
R
RubaXa 已提交
1156
		return new Sortable(el, options);
1157
	};
R
RubaXa 已提交
1158 1159

	// Export
1160
	return Sortable;
R
RubaXa 已提交
1161
});