Sortable.js 27.0 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
	var dragEl,
28
		parentEl,
R
RubaXa 已提交
29 30 31 32
		ghostEl,
		cloneEl,
		rootEl,
		nextEl,
R
RubaXa 已提交
33

34 35 36
		scrollEl,
		scrollParentEl,

R
RubaXa 已提交
37 38
		lastEl,
		lastCSS,
S
sp-kilobug 已提交
39
		lastParentCSS,
R
RubaXa 已提交
40

R
RubaXa 已提交
41 42 43
		oldIndex,
		newIndex,

R
RubaXa 已提交
44
		activeGroup,
R
RubaXa 已提交
45
		autoScroll = {},
R
RubaXa 已提交
46

R
RubaXa 已提交
47 48
		tapEvt,
		touchEvt,
R
RubaXa 已提交
49

C
ChiefORZ 已提交
50 51
		moved,

R
RubaXa 已提交
52 53 54
		/** @const */
		RSPACE = /\s+/g,

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

R
RubaXa 已提交
57 58 59
		win = window,
		document = win.document,
		parseInt = win.parseInt,
R
RubaXa 已提交
60

R
RubaXa 已提交
61 62
		supportDraggable = !!('draggable' in document.createElement('div')),

R
RubaXa 已提交
63
		_silent = false,
R
RubaXa 已提交
64

R
RubaXa 已提交
65
		abs = Math.abs,
R
RubaXa 已提交
66
		slice = [].slice,
R
RubaXa 已提交
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 130 131 132 133
		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) {
134
								win.scrollTo(win.pageXOffset + vx * speed, win.pageYOffset + vy * speed);
135 136 137 138 139 140 141 142 143
							} else {
								vy && (el.scrollTop += vy * speed);
								vx && (el.scrollLeft += vx * speed);
							}
						}, 24);
					}
				}
			}
		}, 30)
R
RubaXa 已提交
144 145 146
	;


147

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


158 159 160 161
		// Export instance
		el[expando] = this;


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

R
RubaXa 已提交
189

190 191
		// Set default options
		for (var name in defaults) {
R
RubaXa 已提交
192
			!(name in options) && (options[name] = defaults[name]);
193
		}
R
RubaXa 已提交
194

R
RubaXa 已提交
195 196
		var group = options.group;

R
RubaXa 已提交
197 198
		if (!group || typeof group != 'object') {
			group = options.group = { name: group };
R
RubaXa 已提交
199 200
		}

R
RubaXa 已提交
201

R
RubaXa 已提交
202
		['pull', 'put'].forEach(function (key) {
R
RubaXa 已提交
203 204
			if (!(key in group)) {
				group[key] = true;
R
RubaXa 已提交
205 206 207 208
			}
		});


209
		options.groups = ' ' + group.name + (group.put.join ? ' ' + group.put.join(' ') : '') + ' ';
R
RubaXa 已提交
210 211


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

R
RubaXa 已提交
219 220
		// Setup drag mode
		this.nativeDraggable = options.forceFallback ? false : supportDraggable;
R
RubaXa 已提交
221 222 223 224 225

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

R
RubaXa 已提交
226
		if (this.nativeDraggable) {
227 228 229
			_on(el, 'dragover', this);
			_on(el, 'dragenter', this);
		}
R
RubaXa 已提交
230 231

		touchDragOverListeners.push(this._onDragOver);
232 233 234

		// Restore sorting
		options.store && this.sort(options.store.get(this));
R
RubaXa 已提交
235 236 237
	}


238
	Sortable.prototype = /** @lends Sortable.prototype */ {
R
RubaXa 已提交
239 240
		constructor: Sortable,

241
		_onTapStart: function (/** Event|TouchEvent */evt) {
242 243
			var _this = this,
				el = this.el,
244 245 246 247 248 249
				options = this.options,
				type = evt.type,
				touch = evt.touches && evt.touches[0],
				target = (touch || evt).target,
				originalTarget = target,
				filter = options.filter;
R
RubaXa 已提交
250 251


252 253
			if (type === 'mousedown' && evt.button !== 0 || options.disabled) {
				return; // only left button or enabled
R
RubaXa 已提交
254
			}
R
RubaXa 已提交
255

256
			target = _closest(target, options.draggable, el);
257

258 259
			if (!target) {
				return;
260
			}
261 262 263 264 265 266 267

			// 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)) {
268
					_dispatchEvent(_this, originalTarget, 'filter', target, el, oldIndex);
269 270 271
					evt.preventDefault();
					return; // cancel dnd
				}
272
			}
273 274 275
			else if (filter) {
				filter = filter.split(',').some(function (criteria) {
					criteria = _closest(originalTarget, criteria.trim(), el);
276

277
					if (criteria) {
278
						_dispatchEvent(_this, criteria, 'filter', target, el, oldIndex);
279 280 281 282 283 284 285
						return true;
					}
				});

				if (filter) {
					evt.preventDefault();
					return; // cancel dnd
286 287 288 289
				}
			}


290 291 292
			if (options.handle && !_closest(originalTarget, options.handle, el)) {
				return;
			}
293

294 295 296

			// Prepare `dragstart`
			this._prepareDragStart(evt, touch, target);
297 298
		},

299 300 301 302 303 304
		_prepareDragStart: function (/** Event */evt, /** Touch */touch, /** HTMLElement */target) {
			var _this = this,
				el = _this.el,
				options = _this.options,
				ownerDocument = el.ownerDocument,
				dragStartFn;
305 306 307 308

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

309
				rootEl = el;
310
				dragEl = target;
311
				parentEl = target.parentNode;
312
				nextEl = dragEl.nextSibling;
313 314 315 316 317 318
				activeGroup = options.group;

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

320 321
					// Make the element draggable
					dragEl.draggable = true;
322

323 324 325 326 327 328 329 330 331 332 333 334 335 336
					// 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) {
337 338
					// If the user moves the pointer or let go the click or touch
					// before the delay has been reached:
339
					// disable the delayed drag
340 341 342
					_on(ownerDocument, 'mouseup', _this._disableDelayedDrag);
					_on(ownerDocument, 'touchend', _this._disableDelayedDrag);
					_on(ownerDocument, 'touchcancel', _this._disableDelayedDrag);
343 344 345 346 347 348
					_on(ownerDocument, 'mousemove', _this._disableDelayedDrag);
					_on(ownerDocument, 'touchmove', _this._disableDelayedDrag);

					_this._dragStartTimer = setTimeout(dragStartFn, options.delay);
				} else {
					dragStartFn();
349 350 351
				}
			}
		},
R
RubaXa 已提交
352

353 354
		_disableDelayedDrag: function () {
			var ownerDocument = this.el.ownerDocument;
355

356
			clearTimeout(this._dragStartTimer);
357 358 359
			_off(ownerDocument, 'mouseup', this._disableDelayedDrag);
			_off(ownerDocument, 'touchend', this._disableDelayedDrag);
			_off(ownerDocument, 'touchcancel', this._disableDelayedDrag);
360 361 362
			_off(ownerDocument, 'mousemove', this._disableDelayedDrag);
			_off(ownerDocument, 'touchmove', this._disableDelayedDrag);
		},
R
RubaXa 已提交
363

364 365 366 367 368 369 370 371
		_triggerDragStart: function (/** Touch */touch) {
			if (touch) {
				// Touch device support
				tapEvt = {
					target: dragEl,
					clientX: touch.clientX,
					clientY: touch.clientY
				};
372

373
				this._onDragStart(tapEvt, 'touch');
R
RubaXa 已提交
374
			}
R
RubaXa 已提交
375
			else if (!this.nativeDraggable) {
376 377 378 379 380
				this._onDragStart(tapEvt, true);
			}
			else {
				_on(dragEl, 'dragend', this);
				_on(rootEl, 'dragstart', this._onDragStart);
381 382
			}

383 384 385 386 387
			try {
				if (document.selection) {
					document.selection.empty();
				} else {
					window.getSelection().removeAllRanges();
388
				}
389
			} catch (err) {
390
			}
391
		},
392

393 394 395 396
		_dragStarted: function () {
			if (rootEl && dragEl) {
				// Apply effect
				_toggleClass(dragEl, this.options.ghostClass, true);
R
RubaXa 已提交
397

398
				Sortable.active = this;
399

400
				// Drag start event
401
				_dispatchEvent(this, rootEl, 'start', dragEl, rootEl, oldIndex);
402
			}
R
RubaXa 已提交
403 404
		},

R
RubaXa 已提交
405 406
		_emulateDragOver: function () {
			if (touchEvt) {
R
RubaXa 已提交
407
				if (this._lastX === touchEvt.clientX && this._lastY === touchEvt.clientY) {
408 409
					return;
				}
R
RubaXa 已提交
410 411 412

				this._lastX = touchEvt.clientX;
				this._lastY = touchEvt.clientY;
413

R
RubaXa 已提交
414 415
				_css(ghostEl, 'display', 'none');

R
RubaXa 已提交
416
				var target = document.elementFromPoint(touchEvt.clientX, touchEvt.clientY),
R
RubaXa 已提交
417
					parent = target,
418
					groupName = ' ' + this.options.group.name + '',
R
RubaXa 已提交
419
					i = touchDragOverListeners.length;
R
RubaXa 已提交
420

R
RubaXa 已提交
421 422
				if (parent) {
					do {
423
						if (parent[expando] && parent[expando].options.groups.indexOf(groupName) > -1) {
R
RubaXa 已提交
424 425 426 427 428 429 430 431 432 433 434 435 436
							while (i--) {
								touchDragOverListeners[i]({
									clientX: touchEvt.clientX,
									clientY: touchEvt.clientY,
									target: target,
									rootEl: parent
								});
							}

							break;
						}

						target = parent; // store last element
L
Larry Davis 已提交
437
					}
R
RubaXa 已提交
438 439
					/* jshint boss:true */
					while (parent = parent.parentNode);
R
RubaXa 已提交
440 441 442 443 444 445 446
				}

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


R
RubaXa 已提交
447 448
		_onTouchMove: function (/**TouchEvent*/evt) {
			if (tapEvt) {
449
				// only set the status to dragging, when we are actually dragging
R
RubaXa 已提交
450
				if (!Sortable.active) {
451 452
					this._dragStarted();
				}
R
RubaXa 已提交
453

454 455
				// as well as creating the ghost element on the document body
				this._appendGhost();
R
RubaXa 已提交
456

R
RubaXa 已提交
457
				var touch = evt.touches ? evt.touches[0] : evt,
R
RubaXa 已提交
458 459
					dx = touch.clientX - tapEvt.clientX,
					dy = touch.clientY - tapEvt.clientY,
R
RubaXa 已提交
460
					translate3d = evt.touches ? 'translate3d(' + dx + 'px,' + dy + 'px,0)' : 'translate(' + dx + 'px,' + dy + 'px)';
R
RubaXa 已提交
461

A
Alex Wild 已提交
462
				moved = true;
R
RubaXa 已提交
463
				touchEvt = touch;
R
RubaXa 已提交
464 465 466 467 468 469

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

M
Marius Petcu 已提交
470
				evt.preventDefault();
R
RubaXa 已提交
471 472 473
			}
		},

R
RubaXa 已提交
474 475
		_appendGhost: function () {
			if (!ghostEl) {
R
RubaXa 已提交
476 477 478
				var rect = dragEl.getBoundingClientRect(),
					css = _css(dragEl),
					ghostRect;
R
RubaXa 已提交
479

480
				ghostEl = dragEl.cloneNode(true);
R
RubaXa 已提交
481

C
ChiefORZ 已提交
482 483 484
				_toggleClass(ghostEl, this.options.ghostClass, false);
				_toggleClass(ghostEl, this.options.fallbackClass, true);

R
RubaXa 已提交
485 486
				_css(ghostEl, 'top', rect.top - parseInt(css.marginTop, 10));
				_css(ghostEl, 'left', rect.left - parseInt(css.marginLeft, 10));
R
RubaXa 已提交
487 488
				_css(ghostEl, 'width', rect.width);
				_css(ghostEl, 'height', rect.height);
R
RubaXa 已提交
489 490 491 492
				_css(ghostEl, 'opacity', '0.8');
				_css(ghostEl, 'position', 'fixed');
				_css(ghostEl, 'zIndex', '100000');

C
ChiefORZ 已提交
493
				this.options.fallbackOnBody && document.body.appendChild(ghostEl) || rootEl.appendChild(ghostEl);
R
RubaXa 已提交
494 495 496

				// Fixing dimensions.
				ghostRect = ghostEl.getBoundingClientRect();
R
RubaXa 已提交
497 498
				_css(ghostEl, 'width', rect.width * 2 - ghostRect.width);
				_css(ghostEl, 'height', rect.height * 2 - ghostRect.height);
499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514
			}
		},

		_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 已提交
515

R
RubaXa 已提交
516 517 518 519 520 521 522 523 524 525
				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 已提交
526

527
				this._loopId = setInterval(this._emulateDragOver, 50);
R
RubaXa 已提交
528 529
			}
			else {
R
RubaXa 已提交
530 531 532 533
				if (dataTransfer) {
					dataTransfer.effectAllowed = 'move';
					options.setData && options.setData.call(this, dataTransfer, dragEl);
				}
R
RubaXa 已提交
534

R
RubaXa 已提交
535
				_on(document, 'drop', this);
536
				setTimeout(this._dragStarted, 0);
R
RubaXa 已提交
537 538 539
			}
		},

R
RubaXa 已提交
540
		_onDragOver: function (/**Event*/evt) {
R
RubaXa 已提交
541 542 543 544 545 546
			var el = this.el,
				target,
				dragRect,
				revert,
				options = this.options,
				group = options.group,
R
RubaXa 已提交
547
				groupPut = group.put,
548 549
				isOwner = (activeGroup === group),
				canSort = options.sort;
R
RubaXa 已提交
550

R
RubaXa 已提交
551 552
			if (evt.preventDefault !== void 0) {
				evt.preventDefault();
553
				!options.dragoverBubble && evt.stopPropagation();
R
RubaXa 已提交
554
			}
R
RubaXa 已提交
555

A
Alex Wild 已提交
556 557
			moved = true;

R
RubaXa 已提交
558
			if (activeGroup && !options.disabled &&
559
				(isOwner
R
RubaXa 已提交
560
					? canSort || (revert = !rootEl.contains(dragEl)) // Reverting item into the original list
R
RubaXa 已提交
561 562 563 564
					: activeGroup.pull && groupPut && (
						(activeGroup.name === group.name) || // by Name
						(groupPut.indexOf && ~groupPut.indexOf(activeGroup.name)) // by Array
					)
565
				) &&
R
RubaXa 已提交
566
				(evt.rootEl === void 0 || evt.rootEl === this.el) // touch fallback
R
RubaXa 已提交
567
			) {
R
RubaXa 已提交
568 569 570 571 572 573 574
				// Smart auto-scrolling
				_autoScroll(evt, options, this.el);

				if (_silent) {
					return;
				}

R
RubaXa 已提交
575
				target = _closest(evt.target, options.draggable, el);
R
RubaXa 已提交
576
				dragRect = dragEl.getBoundingClientRect();
R
RubaXa 已提交
577
				parentEl = target && target.parentNode; // actualization
R
RubaXa 已提交
578

579
				if (revert) {
R
RubaXa 已提交
580 581
					_cloneHide(true);

582 583 584 585 586 587 588
					if (cloneEl || nextEl) {
						rootEl.insertBefore(dragEl, cloneEl || nextEl);
					}
					else if (!canSort) {
						rootEl.appendChild(dragEl);
					}

R
RubaXa 已提交
589 590
					return;
				}
R
RubaXa 已提交
591

R
RubaXa 已提交
592

R
RubaXa 已提交
593
				if ((el.children.length === 0) || (el.children[0] === ghostEl) ||
R
RubaXa 已提交
594
					(el === evt.target) && (target = _ghostInBottom(el, evt))
R
RubaXa 已提交
595
				) {
R
RubaXa 已提交
596 597 598 599 600 601
					if (target) {
						if (target.animated) {
							return;
						}
						targetRect = target.getBoundingClientRect();
					}
R
RubaXa 已提交
602

R
RubaXa 已提交
603 604
					_cloneHide(isOwner);

R
RubaXa 已提交
605
					if (_onMove(rootEl, el, dragEl, dragRect, target, targetRect) !== false) {
R
RubaXa 已提交
606 607 608 609
						el.appendChild(dragEl);
						this._animate(dragRect, dragEl);
						target && this._animate(targetRect, target);
					}
R
RubaXa 已提交
610
				}
R
RubaXa 已提交
611 612
				else if (target && !target.animated && target !== dragEl && (target.parentNode[expando] !== void 0)) {
					if (lastEl !== target) {
R
RubaXa 已提交
613
						lastEl = target;
R
RubaXa 已提交
614
						lastCSS = _css(target);
S
sp-kilobug 已提交
615
						lastParentCSS = _css(target.parentNode);
R
RubaXa 已提交
616 617 618
					}


R
RubaXa 已提交
619 620 621
					var targetRect = target.getBoundingClientRect(),
						width = targetRect.right - targetRect.left,
						height = targetRect.bottom - targetRect.top,
S
sp-kilobug 已提交
622 623
						floating = /left|right|inline/.test(lastCSS.cssFloat + lastCSS.display)
							|| (lastParentCSS.display == 'flex' && lastParentCSS['flex-direction'].indexOf('row') === 0),
R
RubaXa 已提交
624 625 626 627
						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 已提交
628
						moveVector = _onMove(rootEl, el, dragEl, dragRect, target, targetRect),
R
RubaXa 已提交
629
						after
R
RubaXa 已提交
630
					;
R
RubaXa 已提交
631

R
RubaXa 已提交
632
					if (moveVector !== false) {
R
RubaXa 已提交
633 634
						_silent = true;
						setTimeout(_unsilent, 30);
R
RubaXa 已提交
635

R
RubaXa 已提交
636
						_cloneHide(isOwner);
R
RubaXa 已提交
637

R
RubaXa 已提交
638 639 640 641
						if (moveVector === 1 || moveVector === -1) {
							after = (moveVector === 1);
						}
						else if (floating) {
R
RubaXa 已提交
642 643 644 645
							after = (target.previousElementSibling === dragEl) && !isWide || halfway && isWide;
						} else {
							after = (nextSibling !== dragEl) && !isLong || halfway && isLong;
						}
R
RubaXa 已提交
646

R
RubaXa 已提交
647 648 649 650 651
						if (after && !nextSibling) {
							el.appendChild(dragEl);
						} else {
							target.parentNode.insertBefore(dragEl, after ? nextSibling : target);
						}
R
RubaXa 已提交
652

R
RubaXa 已提交
653 654 655
						this._animate(dragRect, dragEl);
						this._animate(targetRect, target);
					}
R
RubaXa 已提交
656 657 658 659
				}
			}
		},

660 661 662 663 664 665
		_animate: function (prevRect, target) {
			var ms = this.options.animation;

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

R
RubaXa 已提交
666
				_css(target, 'transition', 'none');
667 668 669 670 671 672 673
				_css(target, 'transform', 'translate3d('
					+ (prevRect.left - currentRect.left) + 'px,'
					+ (prevRect.top - currentRect.top) + 'px,0)'
				);

				target.offsetWidth; // repaint

R
RubaXa 已提交
674
				_css(target, 'transition', 'all ' + ms + 'ms');
675 676
				_css(target, 'transform', 'translate3d(0,0,0)');

R
* anim  
RubaXa 已提交
677 678
				clearTimeout(target.animated);
				target.animated = setTimeout(function () {
679
					_css(target, 'transition', '');
680
					_css(target, 'transform', '');
681 682 683 684 685
					target.animated = false;
				}, ms);
			}
		},

686
		_offUpEvents: function () {
687 688
			var ownerDocument = this.el.ownerDocument;

689
			_off(document, 'touchmove', this._onTouchMove);
690 691 692
			_off(ownerDocument, 'mouseup', this._onDrop);
			_off(ownerDocument, 'touchend', this._onDrop);
			_off(ownerDocument, 'touchcancel', this._onDrop);
693
		},
R
RubaXa 已提交
694

R
RubaXa 已提交
695
		_onDrop: function (/**Event*/evt) {
696 697
			var el = this.el,
				options = this.options;
R
RubaXa 已提交
698

R
RubaXa 已提交
699
			clearInterval(this._loopId);
R
RubaXa 已提交
700
			clearInterval(autoScroll.pid);
R
RubaXa 已提交
701
			clearTimeout(this._dragStartTimer);
702

R
RubaXa 已提交
703
			// Unbind events
R
RubaXa 已提交
704
			_off(document, 'mousemove', this._onTouchMove);
705

R
RubaXa 已提交
706
			if (this.nativeDraggable) {
707 708 709
				_off(document, 'drop', this);
				_off(el, 'dragstart', this._onDragStart);
			}
R
RubaXa 已提交
710

711
			this._offUpEvents();
R
RubaXa 已提交
712

R
RubaXa 已提交
713
			if (evt) {
R
RubaXa 已提交
714
				if (moved) {
C
ChiefORZ 已提交
715 716 717
					evt.preventDefault();
					!options.dropBubble && evt.stopPropagation();
				}
R
RubaXa 已提交
718

R
RubaXa 已提交
719
				ghostEl && ghostEl.parentNode.removeChild(ghostEl);
R
RubaXa 已提交
720

R
RubaXa 已提交
721
				if (dragEl) {
R
RubaXa 已提交
722
					if (this.nativeDraggable) {
723 724
						_off(dragEl, 'dragend', this);
					}
R
RubaXa 已提交
725

726
					_disableDraggable(dragEl);
R
RubaXa 已提交
727 728
					_toggleClass(dragEl, this.options.ghostClass, false);

729
					if (rootEl !== parentEl) {
R
RubaXa 已提交
730
						newIndex = _index(dragEl);
R
RubaXa 已提交
731

732 733 734 735
						if (newIndex != -1) {
							// drag from one list and drop into another
							_dispatchEvent(null, parentEl, 'sort', dragEl, rootEl, oldIndex, newIndex);
							_dispatchEvent(this, rootEl, 'sort', dragEl, rootEl, oldIndex, newIndex);
R
RubaXa 已提交
736

737 738
							// Add event
							_dispatchEvent(null, parentEl, 'add', dragEl, rootEl, oldIndex, newIndex);
739

740 741 742
							// Remove event
							_dispatchEvent(this, rootEl, 'remove', dragEl, rootEl, oldIndex, newIndex);
						}
R
RubaXa 已提交
743
					}
R
RubaXa 已提交
744 745
					else {
						// Remove clone
R
RubaXa 已提交
746
						cloneEl && cloneEl.parentNode.removeChild(cloneEl);
R
RubaXa 已提交
747

R
RubaXa 已提交
748 749 750
						if (dragEl.nextSibling !== nextEl) {
							// Get the index of the dragged element within its parent
							newIndex = _index(dragEl);
751 752 753 754 755
							if (newIndex != -1) {
								// drag & drop within the same list
								_dispatchEvent(this, rootEl, 'update', dragEl, rootEl, oldIndex, newIndex);
								_dispatchEvent(this, rootEl, 'sort', dragEl, rootEl, oldIndex, newIndex);
							}
R
RubaXa 已提交
756
						}
R
RubaXa 已提交
757
					}
758

R
RubaXa 已提交
759 760 761 762 763 764 765
					if (Sortable.active) {
						// Drag end event
						_dispatchEvent(this, rootEl, 'end', dragEl, rootEl, oldIndex, newIndex);

						// Save sorting
						this.save();
					}
R
RubaXa 已提交
766
				}
R
RubaXa 已提交
767

768
				// Nulling
R
RubaXa 已提交
769 770
				rootEl =
				dragEl =
771
				parentEl =
R
RubaXa 已提交
772 773
				ghostEl =
				nextEl =
R
RubaXa 已提交
774
				cloneEl =
R
RubaXa 已提交
775

776 777 778
				scrollEl =
				scrollParentEl =

R
RubaXa 已提交
779 780 781
				tapEvt =
				touchEvt =

C
ChiefORZ 已提交
782 783
				moved =

R
RubaXa 已提交
784 785 786
				lastEl =
				lastCSS =

R
RubaXa 已提交
787 788
				activeGroup =
				Sortable.active = null;
R
RubaXa 已提交
789 790 791 792
			}
		},


R
RubaXa 已提交
793 794 795
		handleEvent: function (/**Event*/evt) {
			var type = evt.type;

R
RubaXa 已提交
796
			if (type === 'dragover' || type === 'dragenter') {
R
RubaXa 已提交
797 798 799 800
				if (dragEl) {
					this._onDragOver(evt);
					_globalDragOver(evt);
				}
R
RubaXa 已提交
801
			}
R
RubaXa 已提交
802
			else if (type === 'drop' || type === 'dragend') {
R
RubaXa 已提交
803 804
				this._onDrop(evt);
			}
R
RubaXa 已提交
805 806 807
		},


808 809 810 811 812 813 814 815 816
		/**
		 * Serializes the item into an array of string.
		 * @returns {String[]}
		 */
		toArray: function () {
			var order = [],
				el,
				children = this.el.children,
				i = 0,
R
RubaXa 已提交
817 818
				n = children.length,
				options = this.options;
819 820 821

			for (; i < n; i++) {
				el = children[i];
R
RubaXa 已提交
822
				if (_closest(el, options.draggable, this.el)) {
R
RubaXa 已提交
823
					order.push(el.getAttribute(options.dataIdAttr) || _generateId(el));
R
RubaXa 已提交
824
				}
825 826 827 828 829 830 831 832 833 834 835
			}

			return order;
		},


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

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

R
RubaXa 已提交
841
				if (_closest(el, this.options.draggable, rootEl)) {
R
RubaXa 已提交
842 843 844
					items[id] = el;
				}
			}, this);
845 846 847

			order.forEach(function (id) {
				if (items[id]) {
R
RubaXa 已提交
848 849
					rootEl.removeChild(items[id]);
					rootEl.appendChild(items[id]);
850 851 852 853 854
				}
			});
		},


R
RubaXa 已提交
855 856 857 858 859 860 861 862 863
		/**
		 * Save the current sorting
		 */
		save: function () {
			var store = this.options.store;
			store && store.set(this);
		},


864 865 866 867 868 869 870 871 872 873 874
		/**
		 * 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);
		},


875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891
		/**
		 * 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;
			}
		},


892 893 894 895
		/**
		 * Destroy
		 */
		destroy: function () {
896
			var el = this.el;
R
RubaXa 已提交
897

898
			el[expando] = null;
899

R
RubaXa 已提交
900 901 902
			_off(el, 'mousedown', this._onTapStart);
			_off(el, 'touchstart', this._onTapStart);

R
RubaXa 已提交
903
			if (this.nativeDraggable) {
904 905 906
				_off(el, 'dragover', this);
				_off(el, 'dragenter', this);
			}
R
RubaXa 已提交
907

908
			// Remove draggable attributes
R
RubaXa 已提交
909
			Array.prototype.forEach.call(el.querySelectorAll('[draggable]'), function (el) {
910 911 912
				el.removeAttribute('draggable');
			});

R
RubaXa 已提交
913 914 915 916
			touchDragOverListeners.splice(touchDragOverListeners.indexOf(this._onDragOver), 1);

			this._onDrop();

917
			this.el = el = null;
R
RubaXa 已提交
918 919 920
		}
	};

921

R
RubaXa 已提交
922 923 924 925 926 927 928 929 930
	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 已提交
931
	function _closest(/**HTMLElement*/el, /**String*/selector, /**HTMLElement*/ctx) {
R
RubaXa 已提交
932
		if (el) {
R
RubaXa 已提交
933 934 935
			ctx = ctx || document;
			selector = selector.split('.');

R
RubaXa 已提交
936
			var tag = selector.shift().toUpperCase(),
937
				re = new RegExp('\\s(' + selector.join('|') + ')(?=\\s)', 'g');
R
RubaXa 已提交
938 939

			do {
R
RubaXa 已提交
940
				if (
R
RubaXa 已提交
941
					(tag === '>*' && el.parentNode === ctx) || (
R
raphj 已提交
942
						(tag === '' || el.nodeName.toUpperCase() == tag) &&
R
RubaXa 已提交
943 944
						(!selector.length || ((' ' + el.className + ' ').match(re) || []).length == selector.length)
					)
R
RubaXa 已提交
945 946
				) {
					return el;
R
RubaXa 已提交
947 948
				}
			}
R
RubaXa 已提交
949
			while (el !== ctx && (el = el.parentNode));
R
RubaXa 已提交
950 951
		}

R
RubaXa 已提交
952
		return null;
R
RubaXa 已提交
953 954 955
	}


956
	function _globalDragOver(/**Event*/evt) {
R
RubaXa 已提交
957 958 959 960 961
		evt.dataTransfer.dropEffect = 'move';
		evt.preventDefault();
	}


R
RubaXa 已提交
962
	function _on(el, event, fn) {
R
RubaXa 已提交
963 964 965 966
		el.addEventListener(event, fn, false);
	}


R
RubaXa 已提交
967
	function _off(el, event, fn) {
R
RubaXa 已提交
968 969 970 971
		el.removeEventListener(event, fn, false);
	}


R
RubaXa 已提交
972 973 974
	function _toggleClass(el, name, state) {
		if (el) {
			if (el.classList) {
R
RubaXa 已提交
975 976 977
				el.classList[state ? 'add' : 'remove'](name);
			}
			else {
B
Bogdan Mustiata 已提交
978 979
				var className = (' ' + el.className + ' ').replace(RSPACE, ' ').replace(' ' + name + ' ', ' ');
				el.className = (className + (state ? ' ' + name : '')).replace(RSPACE, ' ');
R
RubaXa 已提交
980 981 982 983 984
			}
		}
	}


R
RubaXa 已提交
985
	function _css(el, prop, val) {
R
RubaXa 已提交
986 987
		var style = el && el.style;

R
RubaXa 已提交
988 989 990
		if (style) {
			if (val === void 0) {
				if (document.defaultView && document.defaultView.getComputedStyle) {
R
RubaXa 已提交
991 992
					val = document.defaultView.getComputedStyle(el, '');
				}
R
RubaXa 已提交
993 994
				else if (el.currentStyle) {
					val = el.currentStyle;
R
RubaXa 已提交
995
				}
R
RubaXa 已提交
996 997 998 999 1000 1001 1002 1003 1004

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

				style[prop] = val + (typeof val === 'string' ? '' : 'px');
R
RubaXa 已提交
1005 1006 1007 1008 1009
			}
		}
	}


R
RubaXa 已提交
1010 1011
	function _find(ctx, tagName, iterator) {
		if (ctx) {
R
RubaXa 已提交
1012
			var list = ctx.getElementsByTagName(tagName), i = 0, n = list.length;
R
RubaXa 已提交
1013

R
RubaXa 已提交
1014 1015
			if (iterator) {
				for (; i < n; i++) {
R
RubaXa 已提交
1016 1017 1018
					iterator(list[i], i);
				}
			}
R
RubaXa 已提交
1019

R
RubaXa 已提交
1020
			return list;
R
RubaXa 已提交
1021
		}
R
RubaXa 已提交
1022 1023

		return [];
R
RubaXa 已提交
1024 1025 1026
	}


R
RubaXa 已提交
1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042

	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 已提交
1043 1044
		rootEl.dispatchEvent(evt);

R
RubaXa 已提交
1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056
		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;

C
ChiefORZ 已提交
1057 1058
		evt = document.createEvent('Event');
		evt.initEvent('move', true, true);
R
RubaXa 已提交
1059

C
ChiefORZ 已提交
1060 1061 1062 1063 1064 1065
		evt.to = toEl;
		evt.from = fromEl;
		evt.dragged = dragEl;
		evt.draggedRect = dragRect;
		evt.related = targetEl || toEl;
		evt.relatedRect = targetRect || toEl.getBoundingClientRect();
R
RubaXa 已提交
1066

C
ChiefORZ 已提交
1067
		fromEl.dispatchEvent(evt);
R
RubaXa 已提交
1068

C
ChiefORZ 已提交
1069
		if (onMoveFn) {
R
RubaXa 已提交
1070 1071 1072
			retVal = onMoveFn.call(sortable, evt);
		}

R
RubaXa 已提交
1073
		return retVal;
R
RubaXa 已提交
1074 1075 1076
	}


R
RubaXa 已提交
1077
	function _disableDraggable(el) {
R
RubaXa 已提交
1078
		el.draggable = false;
R
RubaXa 已提交
1079 1080 1081
	}


R
RubaXa 已提交
1082
	function _unsilent() {
R
RubaXa 已提交
1083 1084 1085 1086
		_silent = false;
	}


R
RubaXa 已提交
1087
	/** @returns {HTMLElement|false} */
R
RubaXa 已提交
1088
	function _ghostInBottom(el, evt) {
R
RubaXa 已提交
1089 1090 1091
		var lastEl = el.lastElementChild,
			rect = lastEl.getBoundingClientRect();

R
RubaXa 已提交
1092
		return (evt.clientY - (rect.top + rect.height) > 5) && lastEl; // min delta
R
RubaXa 已提交
1093 1094 1095
	}


1096 1097 1098 1099 1100 1101 1102
	/**
	 * Generate id
	 * @param   {HTMLElement} el
	 * @returns {String}
	 * @private
	 */
	function _generateId(el) {
R
RubaXa 已提交
1103
		var str = el.tagName + el.className + el.src + el.href + el.textContent,
1104
			i = str.length,
R
RubaXa 已提交
1105
			sum = 0;
1106

1107 1108 1109
		while (i--) {
			sum += str.charCodeAt(i);
		}
1110

1111 1112 1113
		return sum.toString(36);
	}

1114 1115 1116
	/**
	 * Returns the index of an element within its parent
	 * @param el
1117 1118
	 * @returns {number}
	 * @private
1119 1120
	 */
	function _index(/**HTMLElement*/el) {
1121 1122 1123
		if (!el || !el.parentNode) {
			return -1;
		}
1124
		var index = 0;
1125 1126 1127 1128
		while (el && (el = el.previousElementSibling)) {
			if (el.nodeName.toUpperCase() !== 'TEMPLATE') {
				index++;
			}
1129 1130 1131
		}
		return index;
	}
R
RubaXa 已提交
1132

R
RubaXa 已提交
1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153
	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);
			}
		};
	}

1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165
	function _extend(dst, src) {
		if (dst && src) {
			for (var key in src) {
				if (src.hasOwnProperty(key)) {
					dst[key] = src[key];
				}
			}
		}

		return dst;
	}

R
RubaXa 已提交
1166

R
RubaXa 已提交
1167 1168 1169 1170 1171 1172
	// Export utils
	Sortable.utils = {
		on: _on,
		off: _off,
		css: _css,
		find: _find,
R
RubaXa 已提交
1173 1174 1175
		is: function (el, selector) {
			return !!_closest(el, selector, el);
		},
1176
		extend: _extend,
R
RubaXa 已提交
1177
		throttle: _throttle,
R
RubaXa 已提交
1178
		closest: _closest,
1179
		toggleClass: _toggleClass,
1180
		index: _index
R
RubaXa 已提交
1181 1182 1183
	};


1184 1185 1186 1187 1188 1189
	/**
	 * Create sortable instance
	 * @param {HTMLElement}  el
	 * @param {Object}      [options]
	 */
	Sortable.create = function (el, options) {
R
RubaXa 已提交
1190
		return new Sortable(el, options);
1191
	};
R
RubaXa 已提交
1192

R
RubaXa 已提交
1193 1194

	// Export
R
RubaXa 已提交
1195
	Sortable.version = '1.2.2';
1196
	return Sortable;
R
RubaXa 已提交
1197
});