Sortable.js 30.4 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";

O
Onoshko Dan 已提交
27 28 29 30 31
	if (typeof window == "undefined" || typeof window.document == "undefined") {
		return function() {
			throw new Error( "Sortable.js requires a window with a document" );
		}
	}
R
RubaXa 已提交
32

R
RubaXa 已提交
33
	var dragEl,
34
		parentEl,
R
RubaXa 已提交
35 36 37 38
		ghostEl,
		cloneEl,
		rootEl,
		nextEl,
R
RubaXa 已提交
39

40 41 42
		scrollEl,
		scrollParentEl,

R
RubaXa 已提交
43 44
		lastEl,
		lastCSS,
S
sp-kilobug 已提交
45
		lastParentCSS,
R
RubaXa 已提交
46

R
RubaXa 已提交
47 48 49
		oldIndex,
		newIndex,

R
RubaXa 已提交
50
		activeGroup,
R
RubaXa 已提交
51 52
		putSortable,

R
RubaXa 已提交
53
		autoScroll = {},
R
RubaXa 已提交
54

R
RubaXa 已提交
55 56
		tapEvt,
		touchEvt,
R
RubaXa 已提交
57

C
ChiefORZ 已提交
58 59
		moved,

R
RubaXa 已提交
60 61 62
		/** @const */
		RSPACE = /\s+/g,

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

R
RubaXa 已提交
65 66 67
		win = window,
		document = win.document,
		parseInt = win.parseInt,
R
RubaXa 已提交
68

R
* UPD  
RubaXa 已提交
69
		$ = win.jQuery || win.Zepto,
70 71
		Polymer = win.Polymer,

R
RubaXa 已提交
72
		supportDraggable = !!('draggable' in document.createElement('div')),
73 74 75 76 77
		supportCssPointerEvents = (function (el) {
			el = document.createElement('x');
			el.style.cssText = 'pointer-events:auto';
			return el.style.pointerEvents === 'auto';
		})(),
R
RubaXa 已提交
78

R
RubaXa 已提交
79
		_silent = false,
R
RubaXa 已提交
80

R
RubaXa 已提交
81
		abs = Math.abs,
R
RubaXa 已提交
82
		min = Math.min,
R
RubaXa 已提交
83
		slice = [].slice,
R
RubaXa 已提交
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 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150
		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) {
151
								win.scrollTo(win.pageXOffset + vx * speed, win.pageYOffset + vy * speed);
152 153 154 155 156 157 158 159
							} else {
								vy && (el.scrollTop += vy * speed);
								vx && (el.scrollLeft += vx * speed);
							}
						}, 24);
					}
				}
			}
R
RubaXa 已提交
160 161 162
		}, 30),

		_prepareGroup = function (options) {
R
RubaXa 已提交
163 164
			function toFn(value, pull) {
				if (value === void 0) {
165
					value = group.name;
R
RubaXa 已提交
166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183
				}

				if (typeof value === 'function') {
					return value;
				} else {
					return function (to, from) {
						var fromGroup = from.options.group.name;

						return pull
							? value
							: value && (value.join
								? value.indexOf(fromGroup) > -1
								: (fromGroup == value)
							);
					};
				}
			}

R
RubaXa 已提交
184 185
			var group = {};
			var originalGroup = options.group;
R
RubaXa 已提交
186

R
RubaXa 已提交
187 188
			if (!originalGroup || typeof originalGroup != 'object') {
				originalGroup = {name: originalGroup};
R
RubaXa 已提交
189 190
			}

R
RubaXa 已提交
191 192 193 194 195
			group.name = originalGroup.name;
			group.checkPull = toFn(originalGroup.pull, true);
			group.checkPut = toFn(originalGroup.put);

			options.group = group;
R
RubaXa 已提交
196
		}
R
RubaXa 已提交
197 198 199
	;


200

R
RubaXa 已提交
201 202 203
	/**
	 * @class  Sortable
	 * @param  {HTMLElement}  el
204
	 * @param  {Object}       [options]
R
RubaXa 已提交
205
	 */
R
RubaXa 已提交
206
	function Sortable(el, options) {
R
RubaXa 已提交
207 208 209 210
		if (!(el && el.nodeType && el.nodeType === 1)) {
			throw 'Sortable: `el` must be HTMLElement, and not ' + {}.toString.call(el);
		}

R
RubaXa 已提交
211
		this.el = el; // root element
212
		this.options = options = _extend({}, options);
R
RubaXa 已提交
213 214


215 216 217 218
		// Export instance
		el[expando] = this;


R
RubaXa 已提交
219
		// Default options
220 221
		var defaults = {
			group: Math.random(),
R
RubaXa 已提交
222
			sort: true,
R
RubaXa 已提交
223
			disabled: false,
224 225
			store: null,
			handle: null,
R
RubaXa 已提交
226 227 228
			scroll: true,
			scrollSensitivity: 30,
			scrollSpeed: 10,
R
RubaXa 已提交
229
			draggable: /[uo]l/i.test(el.nodeName) ? 'li' : '>*',
230
			ghostClass: 'sortable-ghost',
R
RubaXa 已提交
231
			chosenClass: 'sortable-chosen',
232
			ignore: 'a, img',
233
			filter: null,
R
RubaXa 已提交
234 235 236
			animation: 0,
			setData: function (dataTransfer, dragEl) {
				dataTransfer.setData('Text', dragEl.textContent);
237 238
			},
			dropBubble: false,
R
RubaXa 已提交
239
			dragoverBubble: false,
240
			dataIdAttr: 'data-id',
241
			delay: 0,
C
ChiefORZ 已提交
242 243
			forceFallback: false,
			fallbackClass: 'sortable-fallback',
244 245
			fallbackOnBody: false,
			fallbackTolerance: 0
R
RubaXa 已提交
246
		};
247

R
RubaXa 已提交
248

249 250
		// Set default options
		for (var name in defaults) {
R
RubaXa 已提交
251
			!(name in options) && (options[name] = defaults[name]);
252
		}
R
RubaXa 已提交
253

R
RubaXa 已提交
254
		_prepareGroup(options);
R
RubaXa 已提交
255

R
* JSDoc  
RubaXa 已提交
256
		// Bind all private methods
R
RubaXa 已提交
257
		for (var fn in this) {
D
dev101 已提交
258
			if (fn.charAt(0) === '_' && typeof(this[fn]) === 'function') {
259
				this[fn] = this[fn].bind(this);
R
RubaXa 已提交
260 261 262
			}
		}

R
RubaXa 已提交
263 264
		// Setup drag mode
		this.nativeDraggable = options.forceFallback ? false : supportDraggable;
R
RubaXa 已提交
265 266 267 268 269

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

R
RubaXa 已提交
270
		if (this.nativeDraggable) {
271 272 273
			_on(el, 'dragover', this);
			_on(el, 'dragenter', this);
		}
R
RubaXa 已提交
274 275

		touchDragOverListeners.push(this._onDragOver);
276 277 278

		// Restore sorting
		options.store && this.sort(options.store.get(this));
R
RubaXa 已提交
279 280 281
	}


282
	Sortable.prototype = /** @lends Sortable.prototype */ {
R
RubaXa 已提交
283 284
		constructor: Sortable,

285
		_onTapStart: function (/** Event|TouchEvent */evt) {
286 287
			var _this = this,
				el = this.el,
288 289 290 291 292
				options = this.options,
				type = evt.type,
				touch = evt.touches && evt.touches[0],
				target = (touch || evt).target,
				originalTarget = target,
R
RubaXa 已提交
293 294
				filter = options.filter,
				startIndex;
R
RubaXa 已提交
295 296

			// Don't trigger start event when an element is been dragged, otherwise the evt.oldindex always wrong when set option.group.
W
why520crazy 已提交
297
			if (dragEl) {
298 299
				return;
			}
R
RubaXa 已提交
300

301 302
			if (type === 'mousedown' && evt.button !== 0 || options.disabled) {
				return; // only left button or enabled
R
RubaXa 已提交
303
			}
R
RubaXa 已提交
304

305
			target = _closest(target, options.draggable, el);
306

307 308
			if (!target) {
				return;
309
			}
310

311 312 313
			if (options.handle && !_closest(originalTarget, options.handle, el)) {
				return;
			}
314

R
RubaXa 已提交
315
			// Get the index of the dragged element within its parent
R
RubaXa 已提交
316
			startIndex = _index(target, options.draggable);
317 318 319 320

			// Check filter
			if (typeof filter === 'function') {
				if (filter.call(this, evt, target, this)) {
R
RubaXa 已提交
321
					_dispatchEvent(_this, originalTarget, 'filter', target, el, startIndex);
322 323 324
					evt.preventDefault();
					return; // cancel dnd
				}
325
			}
326 327 328
			else if (filter) {
				filter = filter.split(',').some(function (criteria) {
					criteria = _closest(originalTarget, criteria.trim(), el);
329

330
					if (criteria) {
R
RubaXa 已提交
331
						_dispatchEvent(_this, criteria, 'filter', target, el, startIndex);
332 333 334 335 336 337 338
						return true;
					}
				});

				if (filter) {
					evt.preventDefault();
					return; // cancel dnd
339 340 341
				}
			}

342
			// Prepare `dragstart`
R
RubaXa 已提交
343
			this._prepareDragStart(evt, touch, target, startIndex);
344 345
		},

R
RubaXa 已提交
346
		_prepareDragStart: function (/** Event */evt, /** Touch */touch, /** HTMLElement */target, /** Number */startIndex) {
347 348 349 350 351
			var _this = this,
				el = _this.el,
				options = _this.options,
				ownerDocument = el.ownerDocument,
				dragStartFn;
352 353 354 355

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

356
				rootEl = el;
357
				dragEl = target;
R
RubaXa 已提交
358
				parentEl = dragEl.parentNode;
359
				nextEl = dragEl.nextSibling;
360
				activeGroup = options.group;
R
RubaXa 已提交
361
				oldIndex = startIndex;
362

363 364
				this._lastX = (touch || evt).clientX;
				this._lastY = (touch || evt).clientY;
S
sp-kilobug 已提交
365

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

371
					// Make the element draggable
372
					dragEl.draggable = _this.nativeDraggable;
373

R
RubaXa 已提交
374 375
					// Chosen item
					_toggleClass(dragEl, _this.options.chosenClass, true);
376 377 378

					// Bind the events: dragstart/dragend
					_this._triggerDragStart(touch);
T
TaliLavi 已提交
379 380

					// Drag start event
381
					_dispatchEvent(_this, rootEl, 'choose', dragEl, rootEl, oldIndex);
382 383
				};

R
RubaXa 已提交
384 385 386 387 388
				// Disable "draggable"
				options.ignore.split(',').forEach(function (criteria) {
					_find(dragEl, criteria.trim(), _disableDraggable);
				});

389 390 391 392 393
				_on(ownerDocument, 'mouseup', _this._onDrop);
				_on(ownerDocument, 'touchend', _this._onDrop);
				_on(ownerDocument, 'touchcancel', _this._onDrop);

				if (options.delay) {
394 395
					// If the user moves the pointer or let go the click or touch
					// before the delay has been reached:
396
					// disable the delayed drag
397 398 399
					_on(ownerDocument, 'mouseup', _this._disableDelayedDrag);
					_on(ownerDocument, 'touchend', _this._disableDelayedDrag);
					_on(ownerDocument, 'touchcancel', _this._disableDelayedDrag);
400 401 402 403 404 405
					_on(ownerDocument, 'mousemove', _this._disableDelayedDrag);
					_on(ownerDocument, 'touchmove', _this._disableDelayedDrag);

					_this._dragStartTimer = setTimeout(dragStartFn, options.delay);
				} else {
					dragStartFn();
406 407 408
				}
			}
		},
R
RubaXa 已提交
409

410 411
		_disableDelayedDrag: function () {
			var ownerDocument = this.el.ownerDocument;
412

413
			clearTimeout(this._dragStartTimer);
414 415 416
			_off(ownerDocument, 'mouseup', this._disableDelayedDrag);
			_off(ownerDocument, 'touchend', this._disableDelayedDrag);
			_off(ownerDocument, 'touchcancel', this._disableDelayedDrag);
417 418 419
			_off(ownerDocument, 'mousemove', this._disableDelayedDrag);
			_off(ownerDocument, 'touchmove', this._disableDelayedDrag);
		},
R
RubaXa 已提交
420

421 422 423 424 425 426 427 428
		_triggerDragStart: function (/** Touch */touch) {
			if (touch) {
				// Touch device support
				tapEvt = {
					target: dragEl,
					clientX: touch.clientX,
					clientY: touch.clientY
				};
429

430
				this._onDragStart(tapEvt, 'touch');
R
RubaXa 已提交
431
			}
R
RubaXa 已提交
432
			else if (!this.nativeDraggable) {
433 434 435 436 437
				this._onDragStart(tapEvt, true);
			}
			else {
				_on(dragEl, 'dragend', this);
				_on(rootEl, 'dragstart', this._onDragStart);
438 439
			}

440 441 442 443 444
			try {
				if (document.selection) {
					document.selection.empty();
				} else {
					window.getSelection().removeAllRanges();
445
				}
446
			} catch (err) {
447
			}
448
		},
449

450 451 452 453
		_dragStarted: function () {
			if (rootEl && dragEl) {
				// Apply effect
				_toggleClass(dragEl, this.options.ghostClass, true);
R
RubaXa 已提交
454

455
				Sortable.active = this;
456

457
				// Drag start event
458
				_dispatchEvent(this, rootEl, 'start', dragEl, rootEl, oldIndex);
459
			}
R
RubaXa 已提交
460 461
		},

R
RubaXa 已提交
462 463
		_emulateDragOver: function () {
			if (touchEvt) {
R
RubaXa 已提交
464
				if (this._lastX === touchEvt.clientX && this._lastY === touchEvt.clientY) {
465 466
					return;
				}
R
RubaXa 已提交
467 468 469

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

471 472 473
				if (!supportCssPointerEvents) {
					_css(ghostEl, 'display', 'none');
				}
R
RubaXa 已提交
474

R
RubaXa 已提交
475
				var target = document.elementFromPoint(touchEvt.clientX, touchEvt.clientY),
R
RubaXa 已提交
476
					parent = target,
477
					groupName = ' ' + this.options.group.name + '',
R
RubaXa 已提交
478
					i = touchDragOverListeners.length;
R
RubaXa 已提交
479

R
RubaXa 已提交
480 481
				if (parent) {
					do {
R
RubaXa 已提交
482
						if (parent[expando]) {
R
RubaXa 已提交
483 484 485 486 487 488 489 490 491 492 493 494 495
							while (i--) {
								touchDragOverListeners[i]({
									clientX: touchEvt.clientX,
									clientY: touchEvt.clientY,
									target: target,
									rootEl: parent
								});
							}

							break;
						}

						target = parent; // store last element
L
Larry Davis 已提交
496
					}
R
RubaXa 已提交
497 498
					/* jshint boss:true */
					while (parent = parent.parentNode);
R
RubaXa 已提交
499 500
				}

501 502 503
				if (!supportCssPointerEvents) {
					_css(ghostEl, 'display', '');
				}
R
RubaXa 已提交
504 505 506 507
			}
		},


R
RubaXa 已提交
508 509
		_onTouchMove: function (/**TouchEvent*/evt) {
			if (tapEvt) {
R
RubaXa 已提交
510 511 512 513 514 515
				var	options = this.options,
					fallbackTolerance = options.fallbackTolerance,
					touch = evt.touches ? evt.touches[0] : evt,
					dx = touch.clientX - tapEvt.clientX,
					dy = touch.clientY - tapEvt.clientY,
					translate3d = evt.touches ? 'translate3d(' + dx + 'px,' + dy + 'px,0)' : 'translate(' + dx + 'px,' + dy + 'px)';
S
sp-kilobug 已提交
516

517
				// only set the status to dragging, when we are actually dragging
R
RubaXa 已提交
518
				if (!Sortable.active) {
R
RubaXa 已提交
519 520 521 522
					if (fallbackTolerance &&
						min(abs(touch.clientX - this._lastX), abs(touch.clientY - this._lastY)) < fallbackTolerance
					) {
						return;
S
sp-kilobug 已提交
523
					}
R
RubaXa 已提交
524

525 526
					this._dragStarted();
				}
R
RubaXa 已提交
527

528 529
				// as well as creating the ghost element on the document body
				this._appendGhost();
R
RubaXa 已提交
530

A
Alex Wild 已提交
531
				moved = true;
R
RubaXa 已提交
532
				touchEvt = touch;
R
RubaXa 已提交
533 534 535 536 537 538

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

M
Marius Petcu 已提交
539
				evt.preventDefault();
R
RubaXa 已提交
540 541 542
			}
		},

R
RubaXa 已提交
543 544
		_appendGhost: function () {
			if (!ghostEl) {
R
RubaXa 已提交
545 546
				var rect = dragEl.getBoundingClientRect(),
					css = _css(dragEl),
R
RubaXa 已提交
547
					options = this.options,
R
RubaXa 已提交
548
					ghostRect;
R
RubaXa 已提交
549

550
				ghostEl = dragEl.cloneNode(true);
R
RubaXa 已提交
551

R
RubaXa 已提交
552 553
				_toggleClass(ghostEl, options.ghostClass, false);
				_toggleClass(ghostEl, options.fallbackClass, true);
C
ChiefORZ 已提交
554

R
RubaXa 已提交
555 556
				_css(ghostEl, 'top', rect.top - parseInt(css.marginTop, 10));
				_css(ghostEl, 'left', rect.left - parseInt(css.marginLeft, 10));
R
RubaXa 已提交
557 558
				_css(ghostEl, 'width', rect.width);
				_css(ghostEl, 'height', rect.height);
R
RubaXa 已提交
559 560 561
				_css(ghostEl, 'opacity', '0.8');
				_css(ghostEl, 'position', 'fixed');
				_css(ghostEl, 'zIndex', '100000');
562
				_css(ghostEl, 'pointerEvents', 'none');
R
RubaXa 已提交
563

R
RubaXa 已提交
564
				options.fallbackOnBody && document.body.appendChild(ghostEl) || rootEl.appendChild(ghostEl);
R
RubaXa 已提交
565 566 567

				// Fixing dimensions.
				ghostRect = ghostEl.getBoundingClientRect();
R
RubaXa 已提交
568 569
				_css(ghostEl, 'width', rect.width * 2 - ghostRect.width);
				_css(ghostEl, 'height', rect.height * 2 - ghostRect.height);
570 571 572 573 574 575 576 577 578
			}
		},

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

			this._offUpEvents();

R
RubaXa 已提交
579
			if (activeGroup.checkPull(this, this, dragEl, evt) == 'clone') {
580
				cloneEl = _clone(dragEl);
581 582
				_css(cloneEl, 'display', 'none');
				rootEl.insertBefore(cloneEl, dragEl);
R
RubaXa 已提交
583
				_dispatchEvent(this, rootEl, 'clone', dragEl);
584 585 586
			}

			if (useFallback) {
R
RubaXa 已提交
587 588 589 590 591 592 593 594 595 596
				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 已提交
597

598
				this._loopId = setInterval(this._emulateDragOver, 50);
R
RubaXa 已提交
599 600
			}
			else {
R
RubaXa 已提交
601 602 603 604
				if (dataTransfer) {
					dataTransfer.effectAllowed = 'move';
					options.setData && options.setData.call(this, dataTransfer, dragEl);
				}
R
RubaXa 已提交
605

R
RubaXa 已提交
606
				_on(document, 'drop', this);
607
				setTimeout(this._dragStarted, 0);
R
RubaXa 已提交
608 609 610
			}
		},

R
RubaXa 已提交
611
		_onDragOver: function (/**Event*/evt) {
R
RubaXa 已提交
612 613 614 615 616 617
			var el = this.el,
				target,
				dragRect,
				revert,
				options = this.options,
				group = options.group,
R
RubaXa 已提交
618
				activeSortable = Sortable.active,
619 620
				isOwner = (activeGroup === group),
				canSort = options.sort;
R
RubaXa 已提交
621

R
RubaXa 已提交
622 623
			if (evt.preventDefault !== void 0) {
				evt.preventDefault();
624
				!options.dragoverBubble && evt.stopPropagation();
R
RubaXa 已提交
625
			}
R
RubaXa 已提交
626

A
Alex Wild 已提交
627 628
			moved = true;

R
RubaXa 已提交
629
			if (activeGroup && !options.disabled &&
630
				(isOwner
R
RubaXa 已提交
631
					? canSort || (revert = !rootEl.contains(dragEl)) // Reverting item into the original list
R
RubaXa 已提交
632 633 634
					: (
						putSortable === this ||
						activeGroup.checkPull(this, activeSortable, dragEl, evt) && group.checkPut(this, activeSortable, dragEl, evt)
R
RubaXa 已提交
635
					)
636
				) &&
R
RubaXa 已提交
637
				(evt.rootEl === void 0 || evt.rootEl === this.el) // touch fallback
R
RubaXa 已提交
638
			) {
R
RubaXa 已提交
639 640 641 642 643 644 645
				// Smart auto-scrolling
				_autoScroll(evt, options, this.el);

				if (_silent) {
					return;
				}

R
RubaXa 已提交
646
				target = _closest(evt.target, options.draggable, el);
R
RubaXa 已提交
647
				dragRect = dragEl.getBoundingClientRect();
R
RubaXa 已提交
648
				putSortable = this;
R
RubaXa 已提交
649

650
				if (revert) {
R
RubaXa 已提交
651
					_cloneHide(true);
R
RubaXa 已提交
652
					parentEl = rootEl; // actualization
R
RubaXa 已提交
653

654 655 656 657 658 659 660
					if (cloneEl || nextEl) {
						rootEl.insertBefore(dragEl, cloneEl || nextEl);
					}
					else if (!canSort) {
						rootEl.appendChild(dragEl);
					}

R
RubaXa 已提交
661 662
					return;
				}
R
RubaXa 已提交
663

R
RubaXa 已提交
664

R
RubaXa 已提交
665
				if ((el.children.length === 0) || (el.children[0] === ghostEl) ||
666
					(el === evt.target) && (target = _ghostIsLast(el, evt))
R
RubaXa 已提交
667
				) {
R
RubaXa 已提交
668 669 670 671
					if (target) {
						if (target.animated) {
							return;
						}
R
RubaXa 已提交
672

R
RubaXa 已提交
673 674
						targetRect = target.getBoundingClientRect();
					}
R
RubaXa 已提交
675

R
RubaXa 已提交
676 677
					_cloneHide(isOwner);

R
RubaXa 已提交
678
					if (_onMove(rootEl, el, dragEl, dragRect, target, targetRect) !== false) {
679 680
						if (!dragEl.contains(el)) {
							el.appendChild(dragEl);
R
RubaXa 已提交
681
							parentEl = el; // actualization
682
						}
R
RubaXa 已提交
683

R
RubaXa 已提交
684 685 686
						this._animate(dragRect, dragEl);
						target && this._animate(targetRect, target);
					}
R
RubaXa 已提交
687
				}
R
RubaXa 已提交
688 689
				else if (target && !target.animated && target !== dragEl && (target.parentNode[expando] !== void 0)) {
					if (lastEl !== target) {
R
RubaXa 已提交
690
						lastEl = target;
R
RubaXa 已提交
691
						lastCSS = _css(target);
S
sp-kilobug 已提交
692
						lastParentCSS = _css(target.parentNode);
R
RubaXa 已提交
693 694 695
					}


R
RubaXa 已提交
696 697 698
					var targetRect = target.getBoundingClientRect(),
						width = targetRect.right - targetRect.left,
						height = targetRect.bottom - targetRect.top,
S
sp-kilobug 已提交
699 700
						floating = /left|right|inline/.test(lastCSS.cssFloat + lastCSS.display)
							|| (lastParentCSS.display == 'flex' && lastParentCSS['flex-direction'].indexOf('row') === 0),
R
RubaXa 已提交
701 702 703 704
						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 已提交
705
						moveVector = _onMove(rootEl, el, dragEl, dragRect, target, targetRect),
R
RubaXa 已提交
706
						after
R
RubaXa 已提交
707
					;
R
RubaXa 已提交
708

R
RubaXa 已提交
709
					if (moveVector !== false) {
R
RubaXa 已提交
710 711
						_silent = true;
						setTimeout(_unsilent, 30);
R
RubaXa 已提交
712

R
RubaXa 已提交
713
						_cloneHide(isOwner);
R
RubaXa 已提交
714

R
RubaXa 已提交
715 716 717 718
						if (moveVector === 1 || moveVector === -1) {
							after = (moveVector === 1);
						}
						else if (floating) {
S
sp-kilobug 已提交
719 720
							var elTop = dragEl.offsetTop,
								tgTop = target.offsetTop;
R
RubaXa 已提交
721 722

							if (elTop === tgTop) {
S
sp-kilobug 已提交
723
								after = (target.previousElementSibling === dragEl) && !isWide || halfway && isWide;
724
							}
725
							else if (target.previousElementSibling === dragEl || dragEl.previousElementSibling === target) {
726
								after = (evt.clientY - targetRect.top) / height > 0.5;
S
sp-kilobug 已提交
727 728 729
							} else {
								after = tgTop > elTop;
							}
R
RubaXa 已提交
730 731 732
						} else {
							after = (nextSibling !== dragEl) && !isLong || halfway && isLong;
						}
R
RubaXa 已提交
733

734 735 736 737 738 739
						if (!dragEl.contains(el)) {
							if (after && !nextSibling) {
								el.appendChild(dragEl);
							} else {
								target.parentNode.insertBefore(dragEl, after ? nextSibling : target);
							}
R
RubaXa 已提交
740
						}
R
RubaXa 已提交
741

R
RubaXa 已提交
742 743
						parentEl = dragEl.parentNode; // actualization

R
RubaXa 已提交
744 745 746
						this._animate(dragRect, dragEl);
						this._animate(targetRect, target);
					}
R
RubaXa 已提交
747 748 749 750
				}
			}
		},

751 752 753 754 755 756
		_animate: function (prevRect, target) {
			var ms = this.options.animation;

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

R
RubaXa 已提交
757
				_css(target, 'transition', 'none');
758 759 760 761 762 763 764
				_css(target, 'transform', 'translate3d('
					+ (prevRect.left - currentRect.left) + 'px,'
					+ (prevRect.top - currentRect.top) + 'px,0)'
				);

				target.offsetWidth; // repaint

R
RubaXa 已提交
765
				_css(target, 'transition', 'all ' + ms + 'ms');
766 767
				_css(target, 'transform', 'translate3d(0,0,0)');

R
* anim  
RubaXa 已提交
768 769
				clearTimeout(target.animated);
				target.animated = setTimeout(function () {
770
					_css(target, 'transition', '');
771
					_css(target, 'transform', '');
772 773 774 775 776
					target.animated = false;
				}, ms);
			}
		},

777
		_offUpEvents: function () {
778 779
			var ownerDocument = this.el.ownerDocument;

780
			_off(document, 'touchmove', this._onTouchMove);
781 782 783
			_off(ownerDocument, 'mouseup', this._onDrop);
			_off(ownerDocument, 'touchend', this._onDrop);
			_off(ownerDocument, 'touchcancel', this._onDrop);
784
		},
R
RubaXa 已提交
785

R
RubaXa 已提交
786
		_onDrop: function (/**Event*/evt) {
787 788
			var el = this.el,
				options = this.options;
R
RubaXa 已提交
789

R
RubaXa 已提交
790
			clearInterval(this._loopId);
R
RubaXa 已提交
791
			clearInterval(autoScroll.pid);
R
RubaXa 已提交
792
			clearTimeout(this._dragStartTimer);
793

R
RubaXa 已提交
794
			// Unbind events
R
RubaXa 已提交
795
			_off(document, 'mousemove', this._onTouchMove);
796

R
RubaXa 已提交
797
			if (this.nativeDraggable) {
798 799 800
				_off(document, 'drop', this);
				_off(el, 'dragstart', this._onDragStart);
			}
R
RubaXa 已提交
801

802
			this._offUpEvents();
R
RubaXa 已提交
803

R
RubaXa 已提交
804
			if (evt) {
R
RubaXa 已提交
805
				if (moved) {
C
ChiefORZ 已提交
806 807 808
					evt.preventDefault();
					!options.dropBubble && evt.stopPropagation();
				}
R
RubaXa 已提交
809

R
RubaXa 已提交
810
				ghostEl && ghostEl.parentNode.removeChild(ghostEl);
R
RubaXa 已提交
811

R
RubaXa 已提交
812
				if (dragEl) {
R
RubaXa 已提交
813
					if (this.nativeDraggable) {
814 815
						_off(dragEl, 'dragend', this);
					}
R
RubaXa 已提交
816

817
					_disableDraggable(dragEl);
R
RubaXa 已提交
818 819

					// Remove class's
R
RubaXa 已提交
820
					_toggleClass(dragEl, this.options.ghostClass, false);
R
RubaXa 已提交
821
					_toggleClass(dragEl, this.options.chosenClass, false);
R
RubaXa 已提交
822

823
					if (rootEl !== parentEl) {
824
						newIndex = _index(dragEl, options.draggable);
R
RubaXa 已提交
825

826
						if (newIndex >= 0) {
827 828 829
							// 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 已提交
830

831 832
							// Add event
							_dispatchEvent(null, parentEl, 'add', dragEl, rootEl, oldIndex, newIndex);
833

834 835 836
							// Remove event
							_dispatchEvent(this, rootEl, 'remove', dragEl, rootEl, oldIndex, newIndex);
						}
R
RubaXa 已提交
837
					}
R
RubaXa 已提交
838 839
					else {
						// Remove clone
R
RubaXa 已提交
840
						cloneEl && cloneEl.parentNode.removeChild(cloneEl);
R
RubaXa 已提交
841

R
RubaXa 已提交
842 843
						if (dragEl.nextSibling !== nextEl) {
							// Get the index of the dragged element within its parent
844
							newIndex = _index(dragEl, options.draggable);
845 846

							if (newIndex >= 0) {
847 848 849 850
								// drag & drop within the same list
								_dispatchEvent(this, rootEl, 'update', dragEl, rootEl, oldIndex, newIndex);
								_dispatchEvent(this, rootEl, 'sort', dragEl, rootEl, oldIndex, newIndex);
							}
R
RubaXa 已提交
851
						}
R
RubaXa 已提交
852
					}
853

R
RubaXa 已提交
854
					if (Sortable.active) {
R
RubaXa 已提交
855
						if (newIndex === null || newIndex === -1) {
R
RubaXa 已提交
856 857 858
							newIndex = oldIndex;
						}

R
RubaXa 已提交
859 860 861 862 863
						_dispatchEvent(this, rootEl, 'end', dragEl, rootEl, oldIndex, newIndex);

						// Save sorting
						this.save();
					}
R
RubaXa 已提交
864
				}
R
RubaXa 已提交
865

866
			}
R
RubaXa 已提交
867

868 869
			this._nulling();
		},
R
RubaXa 已提交
870

R
RubaXa 已提交
871
		_nulling: function () {
872 873 874 875 876 877
			rootEl =
			dragEl =
			parentEl =
			ghostEl =
			nextEl =
			cloneEl =
878

879 880
			scrollEl =
			scrollParentEl =
R
RubaXa 已提交
881

882 883
			tapEvt =
			touchEvt =
C
ChiefORZ 已提交
884

885 886
			moved =
			newIndex =
R
RubaXa 已提交
887

888 889
			lastEl =
			lastCSS =
R
RubaXa 已提交
890

R
RubaXa 已提交
891
			putSortable =
892
			activeGroup =
R
RubaXa 已提交
893

894 895
			Sortable.active = null;
		},
R
RubaXa 已提交
896

R
RubaXa 已提交
897 898 899
		handleEvent: function (/**Event*/evt) {
			var type = evt.type;

R
RubaXa 已提交
900
			if (type === 'dragover' || type === 'dragenter') {
R
RubaXa 已提交
901 902 903 904
				if (dragEl) {
					this._onDragOver(evt);
					_globalDragOver(evt);
				}
R
RubaXa 已提交
905
			}
R
RubaXa 已提交
906
			else if (type === 'drop' || type === 'dragend') {
R
RubaXa 已提交
907 908
				this._onDrop(evt);
			}
R
RubaXa 已提交
909 910 911
		},


912 913 914 915 916 917 918 919 920
		/**
		 * Serializes the item into an array of string.
		 * @returns {String[]}
		 */
		toArray: function () {
			var order = [],
				el,
				children = this.el.children,
				i = 0,
R
RubaXa 已提交
921 922
				n = children.length,
				options = this.options;
923 924 925

			for (; i < n; i++) {
				el = children[i];
R
RubaXa 已提交
926
				if (_closest(el, options.draggable, this.el)) {
R
RubaXa 已提交
927
					order.push(el.getAttribute(options.dataIdAttr) || _generateId(el));
R
RubaXa 已提交
928
				}
929 930 931 932 933 934 935 936 937 938 939
			}

			return order;
		},


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

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

R
RubaXa 已提交
945
				if (_closest(el, this.options.draggable, rootEl)) {
R
RubaXa 已提交
946 947 948
					items[id] = el;
				}
			}, this);
949 950 951

			order.forEach(function (id) {
				if (items[id]) {
R
RubaXa 已提交
952 953
					rootEl.removeChild(items[id]);
					rootEl.appendChild(items[id]);
954 955 956 957 958
				}
			});
		},


R
RubaXa 已提交
959 960 961 962 963 964 965 966 967
		/**
		 * Save the current sorting
		 */
		save: function () {
			var store = this.options.store;
			store && store.set(this);
		},


968 969 970 971 972 973 974 975 976 977 978
		/**
		 * 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);
		},


979 980 981 982 983 984 985 986 987 988 989 990 991
		/**
		 * 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;
R
RubaXa 已提交
992 993 994 995

				if (name === 'group') {
					_prepareGroup(options);
				}
996 997 998 999
			}
		},


1000 1001 1002 1003
		/**
		 * Destroy
		 */
		destroy: function () {
1004
			var el = this.el;
R
RubaXa 已提交
1005

1006
			el[expando] = null;
1007

R
RubaXa 已提交
1008 1009 1010
			_off(el, 'mousedown', this._onTapStart);
			_off(el, 'touchstart', this._onTapStart);

R
RubaXa 已提交
1011
			if (this.nativeDraggable) {
1012 1013 1014
				_off(el, 'dragover', this);
				_off(el, 'dragenter', this);
			}
R
RubaXa 已提交
1015

1016
			// Remove draggable attributes
R
RubaXa 已提交
1017
			Array.prototype.forEach.call(el.querySelectorAll('[draggable]'), function (el) {
1018 1019 1020
				el.removeAttribute('draggable');
			});

R
RubaXa 已提交
1021 1022 1023 1024
			touchDragOverListeners.splice(touchDragOverListeners.indexOf(this._onDragOver), 1);

			this._onDrop();

1025
			this.el = el = null;
R
RubaXa 已提交
1026 1027 1028
		}
	};

1029

R
RubaXa 已提交
1030 1031 1032 1033 1034 1035 1036 1037 1038
	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 已提交
1039
	function _closest(/**HTMLElement*/el, /**String*/selector, /**HTMLElement*/ctx) {
R
RubaXa 已提交
1040
		if (el) {
R
RubaXa 已提交
1041 1042 1043
			ctx = ctx || document;

			do {
R
RubaXa 已提交
1044
				if ((selector === '>*' && el.parentNode === ctx) || _matches(el, selector)) {
R
RubaXa 已提交
1045
					return el;
R
RubaXa 已提交
1046 1047
				}
			}
R
RubaXa 已提交
1048
			while (el !== ctx && (el = el.parentNode));
R
RubaXa 已提交
1049 1050
		}

R
RubaXa 已提交
1051
		return null;
R
RubaXa 已提交
1052 1053 1054
	}


1055
	function _globalDragOver(/**Event*/evt) {
1056 1057 1058
		if (evt.dataTransfer) {
			evt.dataTransfer.dropEffect = 'move';
		}
R
RubaXa 已提交
1059 1060 1061 1062
		evt.preventDefault();
	}


R
RubaXa 已提交
1063
	function _on(el, event, fn) {
R
RubaXa 已提交
1064 1065 1066 1067
		el.addEventListener(event, fn, false);
	}


R
RubaXa 已提交
1068
	function _off(el, event, fn) {
R
RubaXa 已提交
1069 1070 1071 1072
		el.removeEventListener(event, fn, false);
	}


R
RubaXa 已提交
1073 1074 1075
	function _toggleClass(el, name, state) {
		if (el) {
			if (el.classList) {
R
RubaXa 已提交
1076 1077 1078
				el.classList[state ? 'add' : 'remove'](name);
			}
			else {
B
Bogdan Mustiata 已提交
1079 1080
				var className = (' ' + el.className + ' ').replace(RSPACE, ' ').replace(' ' + name + ' ', ' ');
				el.className = (className + (state ? ' ' + name : '')).replace(RSPACE, ' ');
R
RubaXa 已提交
1081 1082 1083 1084 1085
			}
		}
	}


R
RubaXa 已提交
1086
	function _css(el, prop, val) {
R
RubaXa 已提交
1087 1088
		var style = el && el.style;

R
RubaXa 已提交
1089 1090 1091
		if (style) {
			if (val === void 0) {
				if (document.defaultView && document.defaultView.getComputedStyle) {
R
RubaXa 已提交
1092 1093
					val = document.defaultView.getComputedStyle(el, '');
				}
R
RubaXa 已提交
1094 1095
				else if (el.currentStyle) {
					val = el.currentStyle;
R
RubaXa 已提交
1096
				}
R
RubaXa 已提交
1097 1098 1099 1100 1101 1102 1103 1104 1105

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

				style[prop] = val + (typeof val === 'string' ? '' : 'px');
R
RubaXa 已提交
1106 1107 1108 1109 1110
			}
		}
	}


R
RubaXa 已提交
1111 1112
	function _find(ctx, tagName, iterator) {
		if (ctx) {
R
RubaXa 已提交
1113
			var list = ctx.getElementsByTagName(tagName), i = 0, n = list.length;
R
RubaXa 已提交
1114

R
RubaXa 已提交
1115 1116
			if (iterator) {
				for (; i < n; i++) {
R
RubaXa 已提交
1117 1118 1119
					iterator(list[i], i);
				}
			}
R
RubaXa 已提交
1120

R
RubaXa 已提交
1121
			return list;
R
RubaXa 已提交
1122
		}
R
RubaXa 已提交
1123 1124

		return [];
R
RubaXa 已提交
1125 1126 1127
	}


R
RubaXa 已提交
1128 1129

	function _dispatchEvent(sortable, rootEl, name, targetEl, fromEl, startIndex, newIndex) {
1130 1131
		sortable = (sortable || rootEl[expando]);

R
RubaXa 已提交
1132
		var evt = document.createEvent('Event'),
1133
			options = sortable.options,
R
RubaXa 已提交
1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145
			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 已提交
1146 1147
		rootEl.dispatchEvent(evt);

R
RubaXa 已提交
1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159
		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 已提交
1160 1161
		evt = document.createEvent('Event');
		evt.initEvent('move', true, true);
R
RubaXa 已提交
1162

C
ChiefORZ 已提交
1163 1164 1165 1166 1167 1168
		evt.to = toEl;
		evt.from = fromEl;
		evt.dragged = dragEl;
		evt.draggedRect = dragRect;
		evt.related = targetEl || toEl;
		evt.relatedRect = targetRect || toEl.getBoundingClientRect();
R
RubaXa 已提交
1169

C
ChiefORZ 已提交
1170
		fromEl.dispatchEvent(evt);
R
RubaXa 已提交
1171

C
ChiefORZ 已提交
1172
		if (onMoveFn) {
R
RubaXa 已提交
1173 1174 1175
			retVal = onMoveFn.call(sortable, evt);
		}

R
RubaXa 已提交
1176
		return retVal;
R
RubaXa 已提交
1177 1178 1179
	}


R
RubaXa 已提交
1180
	function _disableDraggable(el) {
R
RubaXa 已提交
1181
		el.draggable = false;
R
RubaXa 已提交
1182 1183 1184
	}


R
RubaXa 已提交
1185
	function _unsilent() {
R
RubaXa 已提交
1186 1187 1188 1189
		_silent = false;
	}


R
RubaXa 已提交
1190
	/** @returns {HTMLElement|false} */
1191
	function _ghostIsLast(el, evt) {
R
RubaXa 已提交
1192
		var lastEl = el.lastElementChild,
R
RubaXa 已提交
1193
			rect = lastEl.getBoundingClientRect();
R
RubaXa 已提交
1194

R
RubaXa 已提交
1195 1196 1197 1198 1199
		// 5 — min delta
		return (
			(abs(evt.clientY - (rect.top + rect.height)) > 5) ||
			(abs(evt.clientX - (rect.right + rect.width)) > 5)
		) && lastEl;
R
RubaXa 已提交
1200 1201 1202
	}


1203 1204 1205 1206 1207 1208 1209
	/**
	 * Generate id
	 * @param   {HTMLElement} el
	 * @returns {String}
	 * @private
	 */
	function _generateId(el) {
R
RubaXa 已提交
1210
		var str = el.tagName + el.className + el.src + el.href + el.textContent,
1211
			i = str.length,
R
RubaXa 已提交
1212
			sum = 0;
1213

1214 1215 1216
		while (i--) {
			sum += str.charCodeAt(i);
		}
1217

1218 1219 1220
		return sum.toString(36);
	}

1221
	/**
1222 1223
	 * Returns the index of an element within its parent for a selected set of
	 * elements
1224
	 * @param  {HTMLElement} el
1225
	 * @param  {selector} selector
1226
	 * @return {number}
1227
	 */
1228
	function _index(el, selector) {
1229 1230
		var index = 0;

1231 1232 1233
		if (!el || !el.parentNode) {
			return -1;
		}
1234

1235
		while (el && (el = el.previousElementSibling)) {
R
RubaXa 已提交
1236
			if ((el.nodeName.toUpperCase() !== 'TEMPLATE') && (selector === '>*' || _matches(el, selector))) {
1237 1238
				index++;
			}
1239
		}
1240

1241 1242
		return index;
	}
R
RubaXa 已提交
1243

1244
	function _matches(/**HTMLElement*/el, /**String*/selector) {
1245 1246 1247 1248 1249 1250 1251 1252 1253 1254
		if (el) {
			selector = selector.split('.');

			var tag = selector.shift().toUpperCase(),
				re = new RegExp('\\s(' + selector.join('|') + ')(?=\\s)', 'g');

			return (
				(tag === '' || el.nodeName.toUpperCase() == tag) &&
				(!selector.length || ((' ' + el.className + ' ').match(re) || []).length == selector.length)
			);
1255
		}
1256 1257

		return false;
1258 1259
	}

R
RubaXa 已提交
1260 1261 1262 1263 1264 1265 1266 1267 1268 1269 1270 1271 1272 1273 1274 1275 1276 1277 1278 1279 1280
	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);
			}
		};
	}

1281 1282 1283 1284 1285 1286 1287 1288 1289 1290 1291 1292
	function _extend(dst, src) {
		if (dst && src) {
			for (var key in src) {
				if (src.hasOwnProperty(key)) {
					dst[key] = src[key];
				}
			}
		}

		return dst;
	}

1293 1294 1295 1296 1297 1298 1299 1300 1301
	function _clone(el) {
		return $
			? $(el).clone(true)[0]
			: (Polymer && Polymer.dom
				? Polymer.dom(el).cloneNode(true)
				: el.cloneNode(true)
			);
	}

R
RubaXa 已提交
1302

R
RubaXa 已提交
1303 1304 1305 1306 1307 1308
	// Export utils
	Sortable.utils = {
		on: _on,
		off: _off,
		css: _css,
		find: _find,
R
RubaXa 已提交
1309 1310 1311
		is: function (el, selector) {
			return !!_closest(el, selector, el);
		},
1312
		extend: _extend,
R
RubaXa 已提交
1313
		throttle: _throttle,
R
RubaXa 已提交
1314
		closest: _closest,
1315
		toggleClass: _toggleClass,
1316
		clone: _clone,
1317
		index: _index
R
RubaXa 已提交
1318 1319 1320
	};


1321 1322 1323 1324 1325 1326
	/**
	 * Create sortable instance
	 * @param {HTMLElement}  el
	 * @param {Object}      [options]
	 */
	Sortable.create = function (el, options) {
R
RubaXa 已提交
1327
		return new Sortable(el, options);
1328
	};
R
RubaXa 已提交
1329

R
RubaXa 已提交
1330 1331

	// Export
R
RubaXa 已提交
1332
	Sortable.version = '1.4.2';
1333
	return Sortable;
R
RubaXa 已提交
1334
});