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

L
Lebedev Konstantin 已提交
7
(function sortableModule(factory) {
R
RubaXa 已提交
8 9
	"use strict";

R
RubaXa 已提交
10
	if (typeof define === "function" && define.amd) {
R
RubaXa 已提交
11
		define(factory);
R
RubaXa 已提交
12
	}
R
RubaXa 已提交
13
	else if (typeof module != "undefined" && typeof module.exports != "undefined") {
S
Scott Nelson 已提交
14 15
		module.exports = factory();
	}
R
RubaXa 已提交
16
	else {
R
RubaXa 已提交
17
		/* jshint sub:true */
R
RubaXa 已提交
18 19
		window["Sortable"] = factory();
	}
L
Lebedev Konstantin 已提交
20
})(function sortableFactory() {
R
RubaXa 已提交
21 22
	"use strict";

R
RubaXa 已提交
23
	if (typeof window === "undefined" || !window.document) {
L
Lebedev Konstantin 已提交
24
		return function sortableError() {
R
RubaXa 已提交
25 26
			throw new Error("Sortable.js requires a window with a document");
		};
O
Onoshko Dan 已提交
27
	}
R
RubaXa 已提交
28

R
RubaXa 已提交
29
	var dragEl,
30
		parentEl,
R
RubaXa 已提交
31 32 33 34
		ghostEl,
		cloneEl,
		rootEl,
		nextEl,
L
Lebedev Konstantin 已提交
35
		lastDownEl,
R
RubaXa 已提交
36

37 38
		scrollEl,
		scrollParentEl,
39
		scrollCustomFn,
40

R
RubaXa 已提交
41 42
		lastEl,
		lastCSS,
S
sp-kilobug 已提交
43
		lastParentCSS,
R
RubaXa 已提交
44

R
RubaXa 已提交
45 46 47
		oldIndex,
		newIndex,

R
RubaXa 已提交
48
		activeGroup,
R
RubaXa 已提交
49 50
		putSortable,

F
Flame2057 已提交
51 52 53 54 55
		autoScrolls = [],

		pointerElemChangedInterval,
		lastPointerElemX,
		lastPointerElemY,
R
RubaXa 已提交
56

R
RubaXa 已提交
57 58
		tapEvt,
		touchEvt,
R
RubaXa 已提交
59

C
ChiefORZ 已提交
60 61
		moved,

62 63
		forRepaintDummy,

R
RubaXa 已提交
64
		/** @const */
65 66
		R_SPACE = /\s+/g,
		R_FLOAT = /left|right|inline/,
R
RubaXa 已提交
67

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

R
RubaXa 已提交
70 71 72
		win = window,
		document = win.document,
		parseInt = win.parseInt,
R
RubaXa 已提交
73
		setTimeout = win.setTimeout,
R
RubaXa 已提交
74

R
* UPD  
RubaXa 已提交
75
		$ = win.jQuery || win.Zepto,
76 77
		Polymer = win.Polymer,

L
Lebedev Konstantin 已提交
78
		captureMode = false,
R
RubaXa 已提交
79
		passiveMode = false,
80

R
RubaXa 已提交
81
		supportDraggable = ('draggable' in document.createElement('div')),
82
		supportCssPointerEvents = (function (el) {
83
			// false when IE11
84
			if (!!navigator.userAgent.match(/(?:Trident.*rv[ :]?11\.|msie)/i)) {
85 86
				return false;
			}
87 88 89 90
			el = document.createElement('x');
			el.style.cssText = 'pointer-events:auto';
			return el.style.pointerEvents === 'auto';
		})(),
R
RubaXa 已提交
91

R
RubaXa 已提交
92
		_silent = false,
R
RubaXa 已提交
93

R
RubaXa 已提交
94
		abs = Math.abs,
R
RubaXa 已提交
95
		min = Math.min,
R
RubaXa 已提交
96

L
Lebedev Konstantin 已提交
97
		savedInputChecked = [],
98 99
		touchDragOverListeners = [],

100
		alwaysFalse = function () { return false; },
R
RubaXa 已提交
101

F
Flame2057 已提交
102 103 104 105 106 107 108 109 110 111 112
		_getParentAutoScrollElement = function(rootEl, includeSelf) {
			// will skip to window in _autoScroll
			if (!rootEl || !rootEl.getBoundingClientRect) return;

			var elem = rootEl;
			var gotSelf = false;
			do {
				if (
					(elem.clientWidth < elem.scrollWidth) ||
					(elem.clientHeight < elem.scrollHeight)
				) {
O
owen-m1 已提交
113
					if (!elem || !elem.getBoundingClientRect || elem === document.body) return;
F
Flame2057 已提交
114 115 116 117 118 119 120 121

					if (gotSelf || includeSelf) return elem;
					gotSelf = true;
				}

			} while (elem = elem.parentNode);
		},

122 123
		_autoScroll = _throttle(function (/**Event*/evt, /**Object*/options, /**HTMLElement*/rootEl) {
			// Bug: https://bugzilla.mozilla.org/show_bug.cgi?id=505521
F
Flame2057 已提交
124 125
			if (options.scroll) {
				var _this = rootEl ? rootEl[expando] : window,
126 127 128 129 130 131 132 133 134 135 136
					rect,
					sens = options.scrollSensitivity,
					speed = options.scrollSpeed,

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

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

					vx,
137 138 139 140
					vy,

					scrollOffsetX,
					scrollOffsetY
D
desmaisons_david 已提交
141
				;
142

F
Flame2057 已提交
143
				// Detect scrollEl
144
				if (scrollParentEl !== rootEl) {
F
Flame2057 已提交
145 146
					_clearAutoScrolls();

147
					scrollEl = options.scroll;
148
					scrollCustomFn = options.scrollFn;
149 150

					if (scrollEl === true) {
F
Flame2057 已提交
151 152
						scrollEl = _getParentAutoScrollElement(rootEl, true);
						scrollParentEl = scrollEl;
153 154 155 156
					}
				}


F
Flame2057 已提交
157 158 159 160
				var layersOut = 0;
				var currentParent = scrollEl;
				do {
					var el;
161

F
Flame2057 已提交
162 163 164 165 166 167
					if (currentParent) {
						el = currentParent;
						rect = currentParent.getBoundingClientRect();
						vx = (abs(rect.right - x) <= sens) - (abs(rect.left - x) <= sens);
						vy = (abs(rect.bottom - y) <= sens) - (abs(rect.top - y) <= sens);
					}
168 169


F
Flame2057 已提交
170 171 172 173 174 175 176
					if (!(vx || vy)) {
						vx = (winWidth - x <= sens) - (x <= sens);
						vy = (winHeight - y <= sens) - (y <= sens);

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

F
Flame2057 已提交
178 179 180 181 182 183 184
					if (!autoScrolls[layersOut]) {
						for (var i = 0; i <= layersOut; i++) {
							if (!autoScrolls[i]) {
								autoScrolls[i] = {};
							}
						}
					}
185

F
Flame2057 已提交
186 187 188 189
					if (autoScrolls[layersOut].vx !== vx || autoScrolls[layersOut].vy !== vy || autoScrolls[layersOut].el !== el) {
						autoScrolls[layersOut].el = el;
						autoScrolls[layersOut].vx = vx;
						autoScrolls[layersOut].vy = vy;
190

F
Flame2057 已提交
191
						clearInterval(autoScrolls[layersOut].pid);
192

F
Flame2057 已提交
193 194 195 196
						if (el) {
							autoScrolls[layersOut].pid = setInterval((function () {
								scrollOffsetY = autoScrolls[this.layersOut].vy ? autoScrolls[this.layersOut].vy * speed : 0;
								scrollOffsetX = autoScrolls[this.layersOut].vx ? autoScrolls[this.layersOut].vx * speed : 0;
197

F
Flame2057 已提交
198 199 200 201
								if ('function' === typeof(scrollCustomFn)) {
									if (scrollCustomFn.call(_this, scrollOffsetX, scrollOffsetY, evt, touchEvt, autoScrolls[this.layersOut].el) !== 'continue') {
										return;
									}
N
Noel Heesen 已提交
202
								}
203

F
Flame2057 已提交
204 205 206 207 208 209 210 211
								if (autoScrolls[this.layersOut].el === win) {
									win.scrollTo(win.pageXOffset + scrollOffsetX, win.pageYOffset + scrollOffsetY);
								} else {
									autoScrolls[this.layersOut].el.scrollTop += scrollOffsetY;
									autoScrolls[this.layersOut].el.scrollLeft += scrollOffsetX;
								}
							}).bind({layersOut: layersOut}), 24);
						}
212
					}
F
Flame2057 已提交
213
					layersOut++;
214
				} while (options.bubbleScroll && (currentParent = _getParentAutoScrollElement(currentParent, false)));
215
			}
R
RubaXa 已提交
216 217
		}, 30),

F
Flame2057 已提交
218 219 220 221 222 223 224
		_clearAutoScrolls = function () {
			autoScrolls.forEach(function(autoScroll) {
				clearInterval(autoScroll.pid);
			});
			autoScrolls = [];
		},

R
RubaXa 已提交
225
		_prepareGroup = function (options) {
R
RubaXa 已提交
226
			function toFn(value, pull) {
227 228 229 230 231 232 233 234 235 236
				return function(to, from, dragEl, evt) {
					var ret;

					if (value == null || value === false) {
						ret = false;
					} else if (pull && value === 'clone') {
						ret = value;
					} else if (typeof value === 'function') {
						ret = value(to, from, dragEl, evt);
					} else {
F
Flame2057 已提交
237 238
						var otherGroup = (pull ? to : from).options.group.name;

239
						ret = (value === true ||
F
Flame2057 已提交
240
						(typeof value === 'string' && value === otherGroup) ||
241 242 243 244
						(value.join && value.indexOf(otherGroup) > -1));
					}

					return ret || to.options.group.name === from.options.group.name;
R
RubaXa 已提交
245 246 247
				}
			}

R
RubaXa 已提交
248 249
			var group = {};
			var originalGroup = options.group;
R
RubaXa 已提交
250

R
RubaXa 已提交
251
			if (!originalGroup || typeof originalGroup != 'object') {
D
desmaisons_david 已提交
252
				originalGroup = {name: originalGroup};
R
RubaXa 已提交
253 254
			}

R
RubaXa 已提交
255 256 257
			group.name = originalGroup.name;
			group.checkPull = toFn(originalGroup.pull, true);
			group.checkPut = toFn(originalGroup.put);
L
Lebedev Konstantin 已提交
258
			group.revertClone = originalGroup.revertClone;
R
RubaXa 已提交
259 260

			options.group = group;
R
RubaXa 已提交
261
		}
D
desmaisons_david 已提交
262
	;
R
RubaXa 已提交
263

R
RubaXa 已提交
264 265 266 267
	// Detect support a passive mode
	try {
		window.addEventListener('test', null, Object.defineProperty({}, 'passive', {
			get: function () {
R
RubaXa 已提交
268 269 270
				// `false`, because everything starts to work incorrectly and instead of d'n'd,
				// begins the page has scrolled.
				passiveMode = false;
R
RubaXa 已提交
271 272 273 274 275 276 277
				captureMode = {
					capture: false,
					passive: passiveMode
				};
			}
		}));
	} catch (err) {}
R
RubaXa 已提交
278 279 280 281

	/**
	 * @class  Sortable
	 * @param  {HTMLElement}  el
282
	 * @param  {Object}       [options]
R
RubaXa 已提交
283
	 */
R
RubaXa 已提交
284
	function Sortable(el, options) {
R
RubaXa 已提交
285 286 287 288
		if (!(el && el.nodeType && el.nodeType === 1)) {
			throw 'Sortable: `el` must be HTMLElement, and not ' + {}.toString.call(el);
		}

R
RubaXa 已提交
289
		this.el = el; // root element
290
		this.options = options = _extend({}, options);
R
RubaXa 已提交
291 292


293 294 295
		// Export instance
		el[expando] = this;

R
RubaXa 已提交
296
		// Default options
297
		var defaults = {
R
RubaXa 已提交
298
			group: null,
R
RubaXa 已提交
299
			sort: true,
R
RubaXa 已提交
300
			disabled: false,
301 302
			store: null,
			handle: null,
F
Flame2057 已提交
303
      		scroll: true,
R
RubaXa 已提交
304 305
			scrollSensitivity: 30,
			scrollSpeed: 10,
306
			bubbleScroll: true,
R
RubaXa 已提交
307
			draggable: /[uo]l/i.test(el.nodeName) ? 'li' : '>*',
308
			ghostClass: 'sortable-ghost',
R
RubaXa 已提交
309
			chosenClass: 'sortable-chosen',
R
RubaXa 已提交
310
			dragClass: 'sortable-drag',
311
			ignore: 'a, img',
312
			filter: null,
L
Lebedev Konstantin 已提交
313
			preventOnFilter: true,
R
RubaXa 已提交
314 315 316
			animation: 0,
			setData: function (dataTransfer, dragEl) {
				dataTransfer.setData('Text', dragEl.textContent);
317 318
			},
			dropBubble: false,
R
RubaXa 已提交
319
			dragoverBubble: false,
320
			dataIdAttr: 'data-id',
321
			delay: 0,
322
			touchStartThreshold: parseInt(window.devicePixelRatio, 10) || 1,
C
ChiefORZ 已提交
323 324
			forceFallback: false,
			fallbackClass: 'sortable-fallback',
325
			fallbackOnBody: false,
326
			fallbackTolerance: 0,
R
RubaXa 已提交
327
			fallbackOffset: {x: 0, y: 0},
328
			supportPointer: Sortable.supportPointer !== false
R
RubaXa 已提交
329
		};
330

R
RubaXa 已提交
331

332 333
		// Set default options
		for (var name in defaults) {
R
RubaXa 已提交
334
			!(name in options) && (options[name] = defaults[name]);
335
		}
R
RubaXa 已提交
336

R
RubaXa 已提交
337
		_prepareGroup(options);
R
RubaXa 已提交
338

R
* JSDoc  
RubaXa 已提交
339
		// Bind all private methods
R
RubaXa 已提交
340
		for (var fn in this) {
D
dev101 已提交
341
			if (fn.charAt(0) === '_' && typeof this[fn] === 'function') {
342
				this[fn] = this[fn].bind(this);
R
RubaXa 已提交
343 344 345
			}
		}

R
RubaXa 已提交
346 347
		// Setup drag mode
		this.nativeDraggable = options.forceFallback ? false : supportDraggable;
R
RubaXa 已提交
348 349 350 351

		// Bind events
		_on(el, 'mousedown', this._onTapStart);
		_on(el, 'touchstart', this._onTapStart);
R
RubaXa 已提交
352
		options.supportPointer && _on(el, 'pointerdown', this._onTapStart);
R
RubaXa 已提交
353

R
RubaXa 已提交
354
		if (this.nativeDraggable) {
355 356 357
			_on(el, 'dragover', this);
			_on(el, 'dragenter', this);
		}
R
RubaXa 已提交
358 359

		touchDragOverListeners.push(this._onDragOver);
360 361 362

		// Restore sorting
		options.store && this.sort(options.store.get(this));
R
RubaXa 已提交
363 364 365
	}


366
	Sortable.prototype = /** @lends Sortable.prototype */ {
R
RubaXa 已提交
367 368
		constructor: Sortable,

369
		_onTapStart: function (/** Event|TouchEvent */evt) {
370 371
			var _this = this,
				el = this.el,
372
				options = this.options,
373
				preventOnFilter = options.preventOnFilter,
374 375 376
				type = evt.type,
				touch = evt.touches && evt.touches[0],
				target = (touch || evt).target,
G
Gaetan D 已提交
377
				originalTarget = evt.target.shadowRoot && ((evt.path && evt.path[0]) || (evt.composedPath && evt.composedPath()[0])) || target,
R
RubaXa 已提交
378 379
				filter = options.filter,
				startIndex;
R
RubaXa 已提交
380

L
Lebedev Konstantin 已提交
381 382 383
			_saveInputCheckedState(el);


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

389
			if (/mousedown|pointerdown/.test(type) && evt.button !== 0 || options.disabled) {
390
				return; // only left button or enabled
R
RubaXa 已提交
391
			}
R
RubaXa 已提交
392

393 394 395 396
			// cancel dnd if original target is content editable
			if (originalTarget.isContentEditable) {
				return;
			}
397 398

			target = _closest(target, options.draggable, el);
399

400
			if (!target) {
401 402
				return;
			}
403

L
Lebedev Konstantin 已提交
404 405 406 407 408
			if (lastDownEl === target) {
				// Ignoring duplicate `down`
				return;
			}

R
RubaXa 已提交
409
			// Get the index of the dragged element within its parent
R
RubaXa 已提交
410
			startIndex = _index(target, options.draggable);
411 412 413 414

			// Check filter
			if (typeof filter === 'function') {
				if (filter.call(this, evt, target, this)) {
J
Joey Becker 已提交
415
					_dispatchEvent(_this, originalTarget, 'filter', target, el, el, startIndex);
L
Lebedev Konstantin 已提交
416
					preventOnFilter && evt.preventDefault();
417 418
					return; // cancel dnd
				}
419
			}
420 421 422
			else if (filter) {
				filter = filter.split(',').some(function (criteria) {
					criteria = _closest(originalTarget, criteria.trim(), el);
423

424
					if (criteria) {
J
Joey Becker 已提交
425
						_dispatchEvent(_this, criteria, 'filter', target, el, el, startIndex);
426 427 428 429 430
						return true;
					}
				});

				if (filter) {
L
Lebedev Konstantin 已提交
431
					preventOnFilter && evt.preventDefault();
432
					return; // cancel dnd
433 434 435
				}
			}

436 437 438 439
			if (options.handle && !_closest(originalTarget, options.handle, el)) {
				return;
			}

440
			// Prepare `dragstart`
R
RubaXa 已提交
441
			this._prepareDragStart(evt, touch, target, startIndex);
442 443
		},

F
Flame2057 已提交
444 445 446 447

		_handleAutoScroll: function(evt) {
			if (!dragEl || !this.options.scroll || (this.options.supportPointer && evt.type == 'touchmove')) return;
			var
448 449
				x = (evt.touches ? evt.touches[0] : evt).clientX,
				y = (evt.touches ? evt.touches[0] : evt).clientY,
F
Flame2057 已提交
450

451 452 453
				elem = document.elementFromPoint(x, y),
				_this = this
			;
F
Flame2057 已提交
454 455 456


			// touch does not have native autoscroll, even with DnD enabled
457
			if (!_this.nativeDraggable || evt.touches || (evt.pointerType && evt.pointerType == 'touch')) {
F
Flame2057 已提交
458

459
				_autoScroll(evt.touches ? evt.touches[0] : evt, _this.options, elem);
F
Flame2057 已提交
460 461 462 463 464 465 466 467

				// Listener for pointer element change
				var ogElemScroller = _getParentAutoScrollElement(elem, true);
				if (!pointerElemChangedInterval ||
					x != lastPointerElemX ||
					y != lastPointerElemY) {

					pointerElemChangedInterval && clearInterval(pointerElemChangedInterval);
468
					// Detect for pointer elem change, emulating native DnD behaviour
F
Flame2057 已提交
469
					pointerElemChangedInterval = setInterval(function() {
470
						if (!dragEl) return;
F
Flame2057 已提交
471 472 473 474 475 476 477 478 479 480 481 482 483
						var newElem = _getParentAutoScrollElement(document.elementFromPoint(x, y), true);
						if (newElem != ogElemScroller) {
							ogElemScroller = newElem;
							_clearAutoScrolls();
							_autoScroll(evt.touches ? evt.touches[0] : evt, _this.options, ogElemScroller);
						}
					}, 10);
					lastPointerElemX = x;
					lastPointerElemY = y;
				}

			} else {
				// if DnD is enabled, first autoscroll will already scroll, so get parent autoscroll of first autoscroll
O
owen-m1 已提交
484
				if (!_this.options.bubbleScroll) return;
485
				_autoScroll(evt, _this.options, _getParentAutoScrollElement(elem, false));
F
Flame2057 已提交
486 487 488
			}
		},

R
RubaXa 已提交
489
		_prepareDragStart: function (/** Event */evt, /** Touch */touch, /** HTMLElement */target, /** Number */startIndex) {
490 491 492 493 494
			var _this = this,
				el = _this.el,
				options = _this.options,
				ownerDocument = el.ownerDocument,
				dragStartFn;
495 496 497 498

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

499
				rootEl = el;
500
				dragEl = target;
R
RubaXa 已提交
501
				parentEl = dragEl.parentNode;
502
				nextEl = dragEl.nextSibling;
D
desmaisons_david 已提交
503
				lastDownEl = target;
504
				activeGroup = options.group;
R
RubaXa 已提交
505
				oldIndex = startIndex;
506

507 508
				this._lastX = (touch || evt).clientX;
				this._lastY = (touch || evt).clientY;
S
sp-kilobug 已提交
509

R
RubaXa 已提交
510
				dragEl.style['will-change'] = 'all';
L
Lebedev Konstantin 已提交
511

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

517
					// Make the element draggable
518
					dragEl.draggable = _this.nativeDraggable;
Y
yak 已提交
519

520 521 522
					// Bind the events: dragstart/dragend
					_this._triggerDragStart(evt, touch);

523 524
					// Drag start event
					_dispatchEvent(_this, rootEl, 'choose', dragEl, rootEl, rootEl, oldIndex);
F
Flame2057 已提交
525

R
RubaXa 已提交
526
					// Chosen item
R
RubaXa 已提交
527
					_toggleClass(dragEl, options.chosenClass, true);
528 529
				};

R
RubaXa 已提交
530 531 532 533 534
				// Disable "draggable"
				options.ignore.split(',').forEach(function (criteria) {
					_find(dragEl, criteria.trim(), _disableDraggable);
				});

535 536 537
				_on(ownerDocument, 'mouseup', _this._onDrop);
				_on(ownerDocument, 'touchend', _this._onDrop);
				_on(ownerDocument, 'touchcancel', _this._onDrop);
538
				_on(ownerDocument, 'selectstart', _this);
R
RubaXa 已提交
539
				options.supportPointer && _on(ownerDocument, 'pointercancel', _this._onDrop);
540 541

				if (options.delay) {
542 543
					// If the user moves the pointer or let go the click or touch
					// before the delay has been reached:
544
					// disable the delayed drag
545 546 547
					_on(ownerDocument, 'mouseup', _this._disableDelayedDrag);
					_on(ownerDocument, 'touchend', _this._disableDelayedDrag);
					_on(ownerDocument, 'touchcancel', _this._disableDelayedDrag);
Y
yak 已提交
548
					_on(ownerDocument, 'mousemove', _this._delayedDragTouchMoveHandler);
549 550
					_on(ownerDocument, 'touchmove', _this._delayedDragTouchMoveHandler);
					options.supportPointer && _on(ownerDocument, 'pointermove', _this._delayedDragTouchMoveHandler);
551

552
					_this._dragStartTimer = setTimeout(dragStartFn.bind(_this), options.delay);
553 554
				} else {
					dragStartFn();
555
				}
556

557

D
desmaisons_david 已提交
558
			}
559
		},
R
RubaXa 已提交
560

561
		_delayedDragTouchMoveHandler: function (/** TouchEvent|PointerEvent **/e) {
Y
yak 已提交
562 563 564 565
			var touch = e.touches ? e.touches[0] : e;
			if (min(abs(touch.clientX - this._lastX), abs(touch.clientY - this._lastY))
					>= this.options.touchStartThreshold
			) {
566 567 568 569
				this._disableDelayedDrag();
			}
		},

570 571
		_disableDelayedDrag: function () {
			var ownerDocument = this.el.ownerDocument;
572

573
			clearTimeout(this._dragStartTimer);
574 575 576
			_off(ownerDocument, 'mouseup', this._disableDelayedDrag);
			_off(ownerDocument, 'touchend', this._disableDelayedDrag);
			_off(ownerDocument, 'touchcancel', this._disableDelayedDrag);
Y
yak 已提交
577 578 579
			_off(ownerDocument, 'mousemove', this._delayedDragTouchMoveHandler);
			_off(ownerDocument, 'touchmove', this._delayedDragTouchMoveHandler);
			_off(ownerDocument, 'pointermove', this._delayedDragTouchMoveHandler);
580
		},
R
RubaXa 已提交
581

582
		_triggerDragStart: function (/** Event */evt, /** Touch */touch) {
S
Sapphire 已提交
583 584
			var touchType = evt && (evt.pointerType == 'touch' || evt.type == 'pointerDown' || evt.type == 'pointerdown')
			touch = touch || (touchType ? evt : null);
585

586 587 588 589 590 591 592
			if (touch) {
				// Touch device support
				tapEvt = {
					target: dragEl,
					clientX: touch.clientX,
					clientY: touch.clientY
				};
593

594
				this._onDragStart(tapEvt, 'touch');
R
RubaXa 已提交
595
			}
R
RubaXa 已提交
596
			else if (!this.nativeDraggable) {
597 598 599 600 601
				this._onDragStart(tapEvt, true);
			}
			else {
				_on(dragEl, 'dragend', this);
				_on(rootEl, 'dragstart', this._onDragStart);
602 603
			}

604
			try {
605 606
				if (document.selection) {
					// Timeout neccessary for IE9
R
RubaXa 已提交
607
					_nextTick(function () {
608
						document.selection.empty();
609
					});
610 611
				} else {
					window.getSelection().removeAllRanges();
612
				}
613
			} catch (err) {
614
			}
615
		},
616

617 618
		_dragStarted: function () {
			if (rootEl && dragEl) {
F
Flame2057 已提交
619
				_on(document, 'drag', this._handleAutoScroll);
R
RubaXa 已提交
620 621
				var options = this.options;

622
				// Apply effect
R
RubaXa 已提交
623 624
				_toggleClass(dragEl, options.ghostClass, true);
				_toggleClass(dragEl, options.dragClass, false);
R
RubaXa 已提交
625

626
				Sortable.active = this;
627

628
				// Drag start event
J
Joey Becker 已提交
629
				_dispatchEvent(this, rootEl, 'start', dragEl, rootEl, rootEl, oldIndex);
L
#1009  
Lebedev Konstantin 已提交
630 631
			} else {
				this._nulling();
632
			}
R
RubaXa 已提交
633 634
		},

R
RubaXa 已提交
635 636
		_emulateDragOver: function () {
			if (touchEvt) {
R
RubaXa 已提交
637
				if (this._lastX === touchEvt.clientX && this._lastY === touchEvt.clientY) {
638 639
					return;
				}
R
RubaXa 已提交
640 641 642

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

644 645 646
				if (!supportCssPointerEvents) {
					_css(ghostEl, 'display', 'none');
				}
R
RubaXa 已提交
647

L
Luiz Corte Real 已提交
648 649
				var target = document.elementFromPoint(touchEvt.clientX, touchEvt.clientY);
				var parent = target;
R
RubaXa 已提交
650

R
RubaXa 已提交
651
				while (target && target.shadowRoot) {
652
					target = target.shadowRoot.elementFromPoint(touchEvt.clientX, touchEvt.clientY);
L
Luiz Corte Real 已提交
653
					parent = target;
654 655
				}

R
RubaXa 已提交
656 657
				if (parent) {
					do {
R
RubaXa 已提交
658
						if (parent[expando]) {
659
							var i = touchDragOverListeners.length;
R
RubaXa 已提交
660 661 662 663 664 665 666 667 668
							while (i--) {
								touchDragOverListeners[i]({
									clientX: touchEvt.clientX,
									clientY: touchEvt.clientY,
									target: target,
									rootEl: parent
								});
							}

669 670 671
							if (!this.options.dragoverBubble) {
								break;
							}
R
RubaXa 已提交
672 673 674
						}

						target = parent; // store last element
L
Larry Davis 已提交
675
					}
R
RubaXa 已提交
676 677
					/* jshint boss:true */
					while (parent = parent.parentNode);
R
RubaXa 已提交
678 679
				}

680 681 682
				if (!supportCssPointerEvents) {
					_css(ghostEl, 'display', '');
				}
R
RubaXa 已提交
683 684 685 686
			}
		},


R
RubaXa 已提交
687 688
		_onTouchMove: function (/**TouchEvent*/evt) {
			if (tapEvt) {
D
desmaisons_david 已提交
689
				var	options = this.options,
R
RubaXa 已提交
690
					fallbackTolerance = options.fallbackTolerance,
691
					fallbackOffset = options.fallbackOffset,
R
RubaXa 已提交
692
					touch = evt.touches ? evt.touches[0] : evt,
693 694
					dx = (touch.clientX - tapEvt.clientX) + fallbackOffset.x,
					dy = (touch.clientY - tapEvt.clientY) + fallbackOffset.y,
R
RubaXa 已提交
695
					translate3d = evt.touches ? 'translate3d(' + dx + 'px,' + dy + 'px,0)' : 'translate(' + dx + 'px,' + dy + 'px)';
S
sp-kilobug 已提交
696

697
				// only set the status to dragging, when we are actually dragging
R
RubaXa 已提交
698
				if (!Sortable.active) {
R
RubaXa 已提交
699 700 701 702
					if (fallbackTolerance &&
						min(abs(touch.clientX - this._lastX), abs(touch.clientY - this._lastY)) < fallbackTolerance
					) {
						return;
S
sp-kilobug 已提交
703
					}
R
RubaXa 已提交
704

705 706
					this._dragStarted();
				}
R
RubaXa 已提交
707

708 709
				// as well as creating the ghost element on the document body
				this._appendGhost();
R
RubaXa 已提交
710

A
Alex Wild 已提交
711
				moved = true;
R
RubaXa 已提交
712
				touchEvt = touch;
R
RubaXa 已提交
713 714 715 716 717 718

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

M
Marius Petcu 已提交
719
				evt.preventDefault();
R
RubaXa 已提交
720 721 722
			}
		},

R
RubaXa 已提交
723 724
		_appendGhost: function () {
			if (!ghostEl) {
R
RubaXa 已提交
725 726
				var rect = dragEl.getBoundingClientRect(),
					css = _css(dragEl),
R
RubaXa 已提交
727
					options = this.options,
R
RubaXa 已提交
728
					ghostRect;
R
RubaXa 已提交
729

730
				ghostEl = dragEl.cloneNode(true);
R
RubaXa 已提交
731

R
RubaXa 已提交
732 733
				_toggleClass(ghostEl, options.ghostClass, false);
				_toggleClass(ghostEl, options.fallbackClass, true);
R
RubaXa 已提交
734
				_toggleClass(ghostEl, options.dragClass, true);
C
ChiefORZ 已提交
735

R
RubaXa 已提交
736 737
				_css(ghostEl, 'top', rect.top - parseInt(css.marginTop, 10));
				_css(ghostEl, 'left', rect.left - parseInt(css.marginLeft, 10));
R
RubaXa 已提交
738 739
				_css(ghostEl, 'width', rect.width);
				_css(ghostEl, 'height', rect.height);
R
RubaXa 已提交
740 741 742
				_css(ghostEl, 'opacity', '0.8');
				_css(ghostEl, 'position', 'fixed');
				_css(ghostEl, 'zIndex', '100000');
743
				_css(ghostEl, 'pointerEvents', 'none');
R
RubaXa 已提交
744

R
RubaXa 已提交
745
				options.fallbackOnBody && document.body.appendChild(ghostEl) || rootEl.appendChild(ghostEl);
R
RubaXa 已提交
746 747 748

				// Fixing dimensions.
				ghostRect = ghostEl.getBoundingClientRect();
R
RubaXa 已提交
749 750
				_css(ghostEl, 'width', rect.width * 2 - ghostRect.width);
				_css(ghostEl, 'height', rect.height * 2 - ghostRect.height);
751 752 753 754
			}
		},

		_onDragStart: function (/**Event*/evt, /**boolean*/useFallback) {
R
RubaXa 已提交
755 756
			var _this = this;
			var dataTransfer = evt.dataTransfer;
R
RubaXa 已提交
757
			var options = _this.options;
758

R
RubaXa 已提交
759
			_this._offUpEvents();
760

R
RubaXa 已提交
761
			if (activeGroup.checkPull(_this, _this, dragEl, evt)) {
762
				cloneEl = _clone(dragEl);
L
Lebedev Konstantin 已提交
763 764 765

				cloneEl.draggable = false;
				cloneEl.style['will-change'] = '';
L
Lebedev Konstantin 已提交
766

767
				_css(cloneEl, 'display', 'none');
R
RubaXa 已提交
768
				_toggleClass(cloneEl, _this.options.chosenClass, false);
L
Lebedev Konstantin 已提交
769

R
RubaXa 已提交
770
				// #1143: IFrame support workaround
R
RubaXa 已提交
771 772 773
				_this._cloneId = _nextTick(function () {
					rootEl.insertBefore(cloneEl, dragEl);
					_dispatchEvent(_this, rootEl, 'clone', dragEl);
R
RubaXa 已提交
774
				});
775 776
			}

R
RubaXa 已提交
777 778
			_toggleClass(dragEl, options.dragClass, true);

779
			if (useFallback) {
R
RubaXa 已提交
780 781
				if (useFallback === 'touch') {
					// Bind touch events
R
RubaXa 已提交
782
					_on(document, 'touchmove', _this._onTouchMove);
F
Flame2057 已提交
783 784
					// onTouchMove before handleAutoScroll in this case, because onTouchMove sets touchEvt
					_on(document, 'touchmove', _this._handleAutoScroll);
R
RubaXa 已提交
785 786
					_on(document, 'touchend', _this._onDrop);
					_on(document, 'touchcancel', _this._onDrop);
R
RubaXa 已提交
787 788

					if (options.supportPointer) {
F
Flame2057 已提交
789
						_on(document, 'pointermove', _this._handleAutoScroll);
R
RubaXa 已提交
790 791 792
						_on(document, 'pointermove', _this._onTouchMove);
						_on(document, 'pointerup', _this._onDrop);
					}
R
RubaXa 已提交
793 794
				} else {
					// Old brwoser
F
Flame2057 已提交
795
					_on(document, 'mousemove', _this._handleAutoScroll);
R
RubaXa 已提交
796 797
					_on(document, 'mousemove', _this._onTouchMove);
					_on(document, 'mouseup', _this._onDrop);
R
RubaXa 已提交
798
				}
R
RubaXa 已提交
799

R
RubaXa 已提交
800
				_this._loopId = setInterval(_this._emulateDragOver, 50);
R
RubaXa 已提交
801 802
			}
			else {
R
RubaXa 已提交
803 804
				if (dataTransfer) {
					dataTransfer.effectAllowed = 'move';
R
RubaXa 已提交
805
					options.setData && options.setData.call(_this, dataTransfer, dragEl);
R
RubaXa 已提交
806
				}
R
RubaXa 已提交
807

R
RubaXa 已提交
808
				_on(document, 'drop', _this);
R
RubaXa 已提交
809 810

				// #1143: Бывает элемент с IFrame внутри блокирует `drop`,
R
typo  
Roman Dvornov 已提交
811
				// поэтому если вызвался `mouseover`, значит надо отменять весь d'n'd.
R
RubaXa 已提交
812 813
				// Breaking Chrome 62+
				// _on(document, 'mouseover', _this);
R
RubaXa 已提交
814

R
RubaXa 已提交
815
				_this._dragStartId = _nextTick(_this._dragStarted);
R
RubaXa 已提交
816 817 818
			}
		},

R
RubaXa 已提交
819
		_onDragOver: function (/**Event*/evt) {
R
RubaXa 已提交
820 821 822
			var el = this.el,
				target,
				dragRect,
R
#930  
RubaXa 已提交
823
				targetRect,
R
RubaXa 已提交
824 825 826
				revert,
				options = this.options,
				group = options.group,
R
RubaXa 已提交
827
				activeSortable = Sortable.active,
828
				isOwner = (activeGroup === group),
829
				isMovingBetweenSortable = false,
830
				canSort = options.sort;
R
RubaXa 已提交
831

R
RubaXa 已提交
832
			if (evt.preventDefault !== void 0) {
R
RubaXa 已提交
833
				evt.preventDefault();
834
				!options.dragoverBubble && evt.stopPropagation();
R
RubaXa 已提交
835
			}
R
RubaXa 已提交
836

837 838 839 840
			if (dragEl.animated) {
				return;
			}

A
Alex Wild 已提交
841
			moved = true;
Y
yak 已提交
842

F
Flame2057 已提交
843
			target = evt.target == el ? evt.target : _closest(evt.target, options.draggable, el);
Y
yak 已提交
844

F
Flame2057 已提交
845
			if (target === el) return;
A
Alex Wild 已提交
846

L
#1009  
Lebedev Konstantin 已提交
847
			if (activeSortable && !options.disabled &&
848
				(isOwner
R
RubaXa 已提交
849
					? canSort || (revert = !rootEl.contains(dragEl)) // Reverting item into the original list
R
RubaXa 已提交
850 851
					: (
						putSortable === this ||
852 853 854 855
						(
							(activeSortable.lastPullMode = activeGroup.checkPull(this, activeSortable, dragEl, evt)) &&
							group.checkPut(this, activeSortable, dragEl, evt)
						)
R
RubaXa 已提交
856
					)
857
				) &&
R
RubaXa 已提交
858
				(evt.rootEl === void 0 || evt.rootEl === this.el) // touch fallback
R
RubaXa 已提交
859
			) {
R
RubaXa 已提交
860 861 862 863
				if (_silent) {
					return;
				}

R
RubaXa 已提交
864
				dragRect = dragEl.getBoundingClientRect();
865 866

				if (putSortable !== this) {
D
desmaisons_david 已提交
867
					putSortable = this;
868 869
					isMovingBetweenSortable = true;
				}
R
RubaXa 已提交
870

871
				if (revert) {
L
Lebedev Konstantin 已提交
872
					_cloneHide(activeSortable, true);
R
RubaXa 已提交
873
					parentEl = rootEl; // actualization
R
RubaXa 已提交
874

875 876 877 878 879 880 881
					if (cloneEl || nextEl) {
						rootEl.insertBefore(dragEl, cloneEl || nextEl);
					}
					else if (!canSort) {
						rootEl.appendChild(dragEl);
					}

R
RubaXa 已提交
882 883
					return;
				}
R
RubaXa 已提交
884

R
RubaXa 已提交
885

R
RubaXa 已提交
886
				if ((el.children.length === 0) || (el.children[0] === ghostEl) ||
887
					(el === evt.target) && (_ghostIsLast(el, evt))
R
RubaXa 已提交
888
				) {
889 890 891 892 893
					//assign target only if condition is true
					if (el.children.length !== 0 && el.children[0] !== ghostEl && el === evt.target) {
						target = el.lastElementChild;
					}

R
RubaXa 已提交
894 895 896 897
					if (target) {
						if (target.animated) {
							return;
						}
R
RubaXa 已提交
898

R
RubaXa 已提交
899 900
						targetRect = target.getBoundingClientRect();
					}
R
RubaXa 已提交
901

L
Lebedev Konstantin 已提交
902
					_cloneHide(activeSortable, isOwner);
R
RubaXa 已提交
903

904
					if (_onMove(rootEl, el, dragEl, dragRect, target, targetRect, evt) !== false) {
905 906
						if (!dragEl.contains(el)) {
							el.appendChild(dragEl);
R
RubaXa 已提交
907
							parentEl = el; // actualization
908
						}
R
RubaXa 已提交
909

R
RubaXa 已提交
910 911 912
						this._animate(dragRect, dragEl);
						target && this._animate(targetRect, target);
					}
R
RubaXa 已提交
913
				}
R
RubaXa 已提交
914 915
				else if (target && !target.animated && target !== dragEl && (target.parentNode[expando] !== void 0)) {
					if (lastEl !== target) {
R
RubaXa 已提交
916
						lastEl = target;
R
RubaXa 已提交
917
						lastCSS = _css(target);
S
sp-kilobug 已提交
918
						lastParentCSS = _css(target.parentNode);
R
RubaXa 已提交
919 920
					}

R
#930  
RubaXa 已提交
921
					targetRect = target.getBoundingClientRect();
R
RubaXa 已提交
922

R
#930  
RubaXa 已提交
923
					var width = targetRect.right - targetRect.left,
R
RubaXa 已提交
924
						height = targetRect.bottom - targetRect.top,
925
						floating = R_FLOAT.test(lastCSS.cssFloat + lastCSS.display)
S
sp-kilobug 已提交
926
							|| (lastParentCSS.display == 'flex' && lastParentCSS['flex-direction'].indexOf('row') === 0),
R
RubaXa 已提交
927 928 929 930
						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,
931
						after = false
D
desmaisons_david 已提交
932
					;
R
RubaXa 已提交
933

934 935 936
					if (floating) {
						var elTop = dragEl.offsetTop,
							tgTop = target.offsetTop;
R
RubaXa 已提交
937

938 939 940 941 942 943 944 945
						if (elTop === tgTop) {
							after = (target.previousElementSibling === dragEl) && !isWide || halfway && isWide;
						}
						else if (target.previousElementSibling === dragEl || dragEl.previousElementSibling === target) {
							after = (evt.clientY - targetRect.top) / height > 0.5;
						} else {
							after = tgTop > elTop;
						}
D
desmaisons_david 已提交
946
						} else if (!isMovingBetweenSortable) {
947 948
						after = (nextSibling !== dragEl) && !isLong || halfway && isLong;
					}
R
RubaXa 已提交
949

950 951 952
					var moveVector = _onMove(rootEl, el, dragEl, dragRect, target, targetRect, evt, after);

					if (moveVector !== false) {
R
RubaXa 已提交
953 954 955
						if (moveVector === 1 || moveVector === -1) {
							after = (moveVector === 1);
						}
R
RubaXa 已提交
956

957 958 959 960
						_silent = true;
						setTimeout(_unsilent, 30);

						_cloneHide(activeSortable, isOwner);
R
RubaXa 已提交
961

962 963 964 965 966 967
						if (!dragEl.contains(el)) {
							if (after && !nextSibling) {
								el.appendChild(dragEl);
							} else {
								target.parentNode.insertBefore(dragEl, after ? nextSibling : target);
							}
R
RubaXa 已提交
968
						}
R
RubaXa 已提交
969

R
RubaXa 已提交
970 971
						parentEl = dragEl.parentNode; // actualization

R
RubaXa 已提交
972 973 974
						this._animate(dragRect, dragEl);
						this._animate(targetRect, target);
					}
R
RubaXa 已提交
975 976 977 978
				}
			}
		},

979 980 981 982 983 984
		_animate: function (prevRect, target) {
			var ms = this.options.animation;

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

L
Lebedev Konstantin 已提交
985 986 987 988
				if (prevRect.nodeType === 1) {
					prevRect = prevRect.getBoundingClientRect();
				}

R
RubaXa 已提交
989
				_css(target, 'transition', 'none');
990 991 992 993 994
				_css(target, 'transform', 'translate3d('
					+ (prevRect.left - currentRect.left) + 'px,'
					+ (prevRect.top - currentRect.top) + 'px,0)'
				);

995
				forRepaintDummy = target.offsetWidth; // repaint
996

R
RubaXa 已提交
997
				_css(target, 'transition', 'all ' + ms + 'ms');
998 999
				_css(target, 'transform', 'translate3d(0,0,0)');

R
* anim  
RubaXa 已提交
1000 1001
				clearTimeout(target.animated);
				target.animated = setTimeout(function () {
1002
					_css(target, 'transition', '');
1003
					_css(target, 'transform', '');
1004 1005 1006 1007 1008
					target.animated = false;
				}, ms);
			}
		},

1009
		_offUpEvents: function () {
1010 1011
			var ownerDocument = this.el.ownerDocument;

F
Flame2057 已提交
1012 1013 1014
			_off(document, 'touchmove', this._handleAutoScroll);
			_off(document, 'pointermove', this._handleAutoScroll);
			_off(document, 'mousemove', this._handleAutoScroll);
1015
			_off(document, 'touchmove', this._onTouchMove);
1016
			_off(document, 'pointermove', this._onTouchMove);
1017 1018
			_off(ownerDocument, 'mouseup', this._onDrop);
			_off(ownerDocument, 'touchend', this._onDrop);
1019
			_off(ownerDocument, 'pointerup', this._onDrop);
1020
			_off(ownerDocument, 'touchcancel', this._onDrop);
진유정 已提交
1021
			_off(ownerDocument, 'pointercancel', this._onDrop);
1022
			_off(ownerDocument, 'selectstart', this);
1023
		},
R
RubaXa 已提交
1024

R
RubaXa 已提交
1025
		_onDrop: function (/**Event*/evt) {
1026 1027
			var el = this.el,
				options = this.options;
R
RubaXa 已提交
1028

R
RubaXa 已提交
1029
			clearInterval(this._loopId);
F
Flame2057 已提交
1030

1031
			clearInterval(pointerElemChangedInterval);
F
Flame2057 已提交
1032
			_clearAutoScrolls();
1033
			_cancelThrottle();
F
Flame2057 已提交
1034

R
RubaXa 已提交
1035
			clearTimeout(this._dragStartTimer);
1036

R
RubaXa 已提交
1037 1038 1039
			_cancelNextTick(this._cloneId);
			_cancelNextTick(this._dragStartId);

R
RubaXa 已提交
1040
			// Unbind events
R
RubaXa 已提交
1041
			_off(document, 'mouseover', this);
R
RubaXa 已提交
1042
			_off(document, 'mousemove', this._onTouchMove);
1043

F
Flame2057 已提交
1044

R
RubaXa 已提交
1045
			if (this.nativeDraggable) {
1046 1047
				_off(document, 'drop', this);
				_off(el, 'dragstart', this._onDragStart);
F
Flame2057 已提交
1048
				_off(document, 'drag', this._handleAutoScroll);
1049
			}
R
RubaXa 已提交
1050

1051
			this._offUpEvents();
R
RubaXa 已提交
1052

R
RubaXa 已提交
1053
			if (evt) {
R
RubaXa 已提交
1054
				if (moved) {
R
RubaXa 已提交
1055
					evt.preventDefault();
C
ChiefORZ 已提交
1056 1057
					!options.dropBubble && evt.stopPropagation();
				}
R
RubaXa 已提交
1058

B
Billy Hardy 已提交
1059
				ghostEl && ghostEl.parentNode && ghostEl.parentNode.removeChild(ghostEl);
R
RubaXa 已提交
1060

L
Lebedev Konstantin 已提交
1061 1062
				if (rootEl === parentEl || Sortable.active.lastPullMode !== 'clone') {
					// Remove clone
B
Billy Hardy 已提交
1063
					cloneEl && cloneEl.parentNode && cloneEl.parentNode.removeChild(cloneEl);
L
Lebedev Konstantin 已提交
1064 1065
				}

R
RubaXa 已提交
1066
				if (dragEl) {
R
RubaXa 已提交
1067
					if (this.nativeDraggable) {
1068 1069
						_off(dragEl, 'dragend', this);
					}
R
RubaXa 已提交
1070

1071
					_disableDraggable(dragEl);
L
Lebedev Konstantin 已提交
1072
					dragEl.style['will-change'] = '';
R
RubaXa 已提交
1073 1074

					// Remove class's
R
RubaXa 已提交
1075
					_toggleClass(dragEl, this.options.ghostClass, false);
R
RubaXa 已提交
1076
					_toggleClass(dragEl, this.options.chosenClass, false);
R
RubaXa 已提交
1077

1078
					// Drag stop event
S
Sam Wray 已提交
1079
					_dispatchEvent(this, rootEl, 'unchoose', dragEl, parentEl, rootEl, oldIndex, null, evt);
1080

1081
					if (rootEl !== parentEl) {
1082
						newIndex = _index(dragEl, options.draggable);
R
RubaXa 已提交
1083

1084
						if (newIndex >= 0) {
1085
							// Add event
S
Sam Wray 已提交
1086
							_dispatchEvent(null, parentEl, 'add', dragEl, parentEl, rootEl, oldIndex, newIndex, evt);
1087

1088
							// Remove event
S
Sam Wray 已提交
1089
							_dispatchEvent(this, rootEl, 'remove', dragEl, parentEl, rootEl, oldIndex, newIndex, evt);
N
n00dl3 已提交
1090

N
n00dl3 已提交
1091
							// drag from one list and drop into another
S
Sam Wray 已提交
1092 1093
							_dispatchEvent(null, parentEl, 'sort', dragEl, parentEl, rootEl, oldIndex, newIndex, evt);
							_dispatchEvent(this, rootEl, 'sort', dragEl, parentEl, rootEl, oldIndex, newIndex, evt);
1094
						}
R
RubaXa 已提交
1095
					}
R
RubaXa 已提交
1096 1097 1098
					else {
						if (dragEl.nextSibling !== nextEl) {
							// Get the index of the dragged element within its parent
1099
							newIndex = _index(dragEl, options.draggable);
1100 1101

							if (newIndex >= 0) {
1102
								// drag & drop within the same list
S
Sam Wray 已提交
1103 1104
								_dispatchEvent(this, rootEl, 'update', dragEl, parentEl, rootEl, oldIndex, newIndex, evt);
								_dispatchEvent(this, rootEl, 'sort', dragEl, parentEl, rootEl, oldIndex, newIndex, evt);
1105
							}
R
RubaXa 已提交
1106
						}
R
RubaXa 已提交
1107
					}
1108

R
RubaXa 已提交
1109
					if (Sortable.active) {
1110
						/* jshint eqnull:true */
L
Lebedev Konstantin 已提交
1111
						if (newIndex == null || newIndex === -1) {
R
RubaXa 已提交
1112 1113 1114
							newIndex = oldIndex;
						}

S
Sam Wray 已提交
1115
						_dispatchEvent(this, rootEl, 'end', dragEl, parentEl, rootEl, oldIndex, newIndex, evt);
R
RubaXa 已提交
1116 1117 1118 1119

						// Save sorting
						this.save();
					}
R
RubaXa 已提交
1120
				}
R
RubaXa 已提交
1121

1122
			}
R
RubaXa 已提交
1123

1124 1125
			this._nulling();
		},
R
RubaXa 已提交
1126

D
desmaisons_david 已提交
1127
		_nulling: function() {
L
Lebedev Konstantin 已提交
1128
			rootEl =
D
desmaisons_david 已提交
1129 1130 1131 1132 1133 1134
			dragEl =
			parentEl =
			ghostEl =
			nextEl =
			cloneEl =
			lastDownEl =
R
RubaXa 已提交
1135

D
desmaisons_david 已提交
1136 1137
			scrollEl =
			scrollParentEl =
C
ChiefORZ 已提交
1138

D
desmaisons_david 已提交
1139 1140
			tapEvt =
			touchEvt =
R
RubaXa 已提交
1141

D
desmaisons_david 已提交
1142 1143
			moved =
			newIndex =
R
RubaXa 已提交
1144

D
desmaisons_david 已提交
1145 1146
			lastEl =
			lastCSS =
R
RubaXa 已提交
1147

D
desmaisons_david 已提交
1148 1149 1150
			putSortable =
			activeGroup =
			Sortable.active = null;
L
Lebedev Konstantin 已提交
1151 1152 1153 1154 1155

			savedInputChecked.forEach(function (el) {
				el.checked = true;
			});
			savedInputChecked.length = 0;
1156
		},
R
RubaXa 已提交
1157

R
RubaXa 已提交
1158
		handleEvent: function (/**Event*/evt) {
1159 1160 1161 1162 1163 1164 1165 1166
			switch (evt.type) {
				case 'drop':
				case 'dragend':
					this._onDrop(evt);
					break;

				case 'dragover':
				case 'dragenter':
D
desmaisons_david 已提交
1167 1168 1169 1170
					if (dragEl) {
						this._onDragOver(evt);
						_globalDragOver(evt);
					}
1171
					break;
R
RubaXa 已提交
1172

R
RubaXa 已提交
1173 1174 1175 1176
				case 'mouseover':
					this._onDrop(evt);
					break;

1177 1178 1179
				case 'selectstart':
					evt.preventDefault();
					break;
R
RubaXa 已提交
1180
			}
R
RubaXa 已提交
1181 1182 1183
		},


1184 1185 1186 1187 1188 1189 1190 1191 1192
		/**
		 * Serializes the item into an array of string.
		 * @returns {String[]}
		 */
		toArray: function () {
			var order = [],
				el,
				children = this.el.children,
				i = 0,
R
RubaXa 已提交
1193 1194
				n = children.length,
				options = this.options;
1195 1196 1197

			for (; i < n; i++) {
				el = children[i];
R
RubaXa 已提交
1198
				if (_closest(el, options.draggable, this.el)) {
R
RubaXa 已提交
1199
					order.push(el.getAttribute(options.dataIdAttr) || _generateId(el));
R
RubaXa 已提交
1200
				}
1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211
			}

			return order;
		},


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

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

R
RubaXa 已提交
1217
				if (_closest(el, this.options.draggable, rootEl)) {
R
RubaXa 已提交
1218 1219 1220
					items[id] = el;
				}
			}, this);
1221 1222 1223

			order.forEach(function (id) {
				if (items[id]) {
R
RubaXa 已提交
1224 1225
					rootEl.removeChild(items[id]);
					rootEl.appendChild(items[id]);
1226 1227 1228 1229 1230
				}
			});
		},


R
RubaXa 已提交
1231 1232 1233 1234 1235 1236 1237 1238 1239
		/**
		 * Save the current sorting
		 */
		save: function () {
			var store = this.options.store;
			store && store.set(this);
		},


1240 1241 1242 1243 1244 1245 1246 1247 1248 1249 1250
		/**
		 * 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);
		},


1251 1252 1253 1254 1255 1256 1257 1258 1259 1260 1261 1262 1263
		/**
		 * 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 已提交
1264 1265 1266 1267

				if (name === 'group') {
					_prepareGroup(options);
				}
1268 1269 1270 1271
			}
		},


1272 1273 1274 1275
		/**
		 * Destroy
		 */
		destroy: function () {
1276
			var el = this.el;
R
RubaXa 已提交
1277

1278
			el[expando] = null;
1279

R
RubaXa 已提交
1280 1281
			_off(el, 'mousedown', this._onTapStart);
			_off(el, 'touchstart', this._onTapStart);
1282
			_off(el, 'pointerdown', this._onTapStart);
R
RubaXa 已提交
1283

R
RubaXa 已提交
1284
			if (this.nativeDraggable) {
1285 1286 1287
				_off(el, 'dragover', this);
				_off(el, 'dragenter', this);
			}
R
RubaXa 已提交
1288

1289
			// Remove draggable attributes
R
RubaXa 已提交
1290
			Array.prototype.forEach.call(el.querySelectorAll('[draggable]'), function (el) {
1291 1292 1293
				el.removeAttribute('draggable');
			});

R
RubaXa 已提交
1294 1295 1296 1297
			touchDragOverListeners.splice(touchDragOverListeners.indexOf(this._onDragOver), 1);

			this._onDrop();

1298
			this.el = el = null;
R
RubaXa 已提交
1299 1300 1301
		}
	};

1302

L
Lebedev Konstantin 已提交
1303
	function _cloneHide(sortable, state) {
1304 1305 1306 1307
		if (sortable.lastPullMode !== 'clone') {
			state = true;
		}

R
RubaXa 已提交
1308 1309
		if (cloneEl && (cloneEl.state !== state)) {
			_css(cloneEl, 'display', state ? 'none' : '');
L
Lebedev Konstantin 已提交
1310 1311 1312 1313 1314 1315 1316 1317 1318 1319 1320 1321

			if (!state) {
				if (cloneEl.state) {
					if (sortable.options.group.revertClone) {
						rootEl.insertBefore(cloneEl, nextEl);
						sortable._animate(dragEl, cloneEl);
					} else {
						rootEl.insertBefore(cloneEl, dragEl);
					}
				}
			}

R
RubaXa 已提交
1322 1323 1324 1325 1326
			cloneEl.state = state;
		}
	}


R
RubaXa 已提交
1327
	function _closest(/**HTMLElement*/el, /**String*/selector, /**HTMLElement*/ctx) {
R
RubaXa 已提交
1328
		if (el) {
R
RubaXa 已提交
1329 1330 1331
			ctx = ctx || document;

			do {
R
RubaXa 已提交
1332
				if ((selector === '>*' && el.parentNode === ctx) || _matches(el, selector)) {
R
RubaXa 已提交
1333
					return el;
R
RubaXa 已提交
1334
				}
R
#930  
RubaXa 已提交
1335 1336
				/* jshint boss:true */
			} while (el = _getParentOrHost(el));
R
RubaXa 已提交
1337 1338
		}

R
RubaXa 已提交
1339
		return null;
R
RubaXa 已提交
1340 1341 1342
	}


N
Nikita SVIRIDENKO 已提交
1343
	function _getParentOrHost(el) {
1344 1345 1346
		return (el.host && el !== document && el.host.nodeType)
			? el.host
			: el.parentNode;
N
Nikita SVIRIDENKO 已提交
1347
	}
1348 1349


1350
	function _globalDragOver(/**Event*/evt) {
1351 1352 1353
		if (evt.dataTransfer) {
			evt.dataTransfer.dropEffect = 'move';
		}
R
RubaXa 已提交
1354
		evt.preventDefault();
R
RubaXa 已提交
1355 1356 1357
	}


R
RubaXa 已提交
1358
	function _on(el, event, fn) {
1359
		el.addEventListener(event, fn, captureMode);
R
RubaXa 已提交
1360 1361 1362
	}


R
RubaXa 已提交
1363
	function _off(el, event, fn) {
1364
		el.removeEventListener(event, fn, captureMode);
R
RubaXa 已提交
1365 1366 1367
	}


R
RubaXa 已提交
1368 1369 1370
	function _toggleClass(el, name, state) {
		if (el) {
			if (el.classList) {
R
RubaXa 已提交
1371 1372 1373
				el.classList[state ? 'add' : 'remove'](name);
			}
			else {
1374 1375
				var className = (' ' + el.className + ' ').replace(R_SPACE, ' ').replace(' ' + name + ' ', ' ');
				el.className = (className + (state ? ' ' + name : '')).replace(R_SPACE, ' ');
R
RubaXa 已提交
1376 1377 1378 1379 1380
			}
		}
	}


R
RubaXa 已提交
1381
	function _css(el, prop, val) {
R
RubaXa 已提交
1382 1383
		var style = el && el.style;

R
RubaXa 已提交
1384 1385 1386
		if (style) {
			if (val === void 0) {
				if (document.defaultView && document.defaultView.getComputedStyle) {
R
RubaXa 已提交
1387 1388
					val = document.defaultView.getComputedStyle(el, '');
				}
R
RubaXa 已提交
1389 1390
				else if (el.currentStyle) {
					val = el.currentStyle;
R
RubaXa 已提交
1391
				}
R
RubaXa 已提交
1392 1393 1394 1395 1396 1397 1398 1399 1400

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

				style[prop] = val + (typeof val === 'string' ? '' : 'px');
R
RubaXa 已提交
1401 1402 1403 1404 1405
			}
		}
	}


R
RubaXa 已提交
1406 1407
	function _find(ctx, tagName, iterator) {
		if (ctx) {
R
RubaXa 已提交
1408
			var list = ctx.getElementsByTagName(tagName), i = 0, n = list.length;
R
RubaXa 已提交
1409

R
RubaXa 已提交
1410 1411
			if (iterator) {
				for (; i < n; i++) {
R
RubaXa 已提交
1412 1413 1414
					iterator(list[i], i);
				}
			}
R
RubaXa 已提交
1415

R
RubaXa 已提交
1416
			return list;
R
RubaXa 已提交
1417
		}
R
RubaXa 已提交
1418 1419

		return [];
R
RubaXa 已提交
1420 1421 1422
	}


R
RubaXa 已提交
1423

S
Sam Wray 已提交
1424
	function _dispatchEvent(sortable, rootEl, name, targetEl, toEl, fromEl, startIndex, newIndex, originalEvt) {
1425 1426
		sortable = (sortable || rootEl[expando]);

R
RubaXa 已提交
1427
		var evt = document.createEvent('Event'),
1428
			options = sortable.options,
R
RubaXa 已提交
1429 1430 1431 1432
			onName = 'on' + name.charAt(0).toUpperCase() + name.substr(1);

		evt.initEvent(name, true, true);

J
Joey Becker 已提交
1433
		evt.to = toEl || rootEl;
R
RubaXa 已提交
1434 1435 1436 1437 1438 1439 1440
		evt.from = fromEl || rootEl;
		evt.item = targetEl || rootEl;
		evt.clone = cloneEl;

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

1441 1442
		evt.originalEvent = originalEvt;

R
RubaXa 已提交
1443 1444
		rootEl.dispatchEvent(evt);

R
RubaXa 已提交
1445 1446 1447 1448 1449 1450
		if (options[onName]) {
			options[onName].call(sortable, evt);
		}
	}


1451
	function _onMove(fromEl, toEl, dragEl, dragRect, targetEl, targetRect, originalEvt, willInsertAfter) {
R
RubaXa 已提交
1452 1453 1454 1455 1456
		var evt,
			sortable = fromEl[expando],
			onMoveFn = sortable.options.onMove,
			retVal;

C
ChiefORZ 已提交
1457 1458
		evt = document.createEvent('Event');
		evt.initEvent('move', true, true);
R
RubaXa 已提交
1459

C
ChiefORZ 已提交
1460 1461 1462 1463 1464 1465
		evt.to = toEl;
		evt.from = fromEl;
		evt.dragged = dragEl;
		evt.draggedRect = dragRect;
		evt.related = targetEl || toEl;
		evt.relatedRect = targetRect || toEl.getBoundingClientRect();
1466
		evt.willInsertAfter = willInsertAfter;
R
RubaXa 已提交
1467

1468 1469
		evt.originalEvent = originalEvt;

C
ChiefORZ 已提交
1470
		fromEl.dispatchEvent(evt);
R
RubaXa 已提交
1471

C
ChiefORZ 已提交
1472
		if (onMoveFn) {
1473
			retVal = onMoveFn.call(sortable, evt, originalEvt);
R
RubaXa 已提交
1474 1475
		}

R
RubaXa 已提交
1476
		return retVal;
R
RubaXa 已提交
1477 1478 1479
	}


R
RubaXa 已提交
1480
	function _disableDraggable(el) {
R
RubaXa 已提交
1481
		el.draggable = false;
R
RubaXa 已提交
1482 1483 1484
	}


R
RubaXa 已提交
1485
	function _unsilent() {
R
RubaXa 已提交
1486 1487 1488 1489
		_silent = false;
	}


R
RubaXa 已提交
1490
	/** @returns {HTMLElement|false} */
1491
	function _ghostIsLast(el, evt) {
R
RubaXa 已提交
1492
		var lastEl = el.lastElementChild,
R
RubaXa 已提交
1493
			rect = lastEl.getBoundingClientRect();
R
RubaXa 已提交
1494

R
RubaXa 已提交
1495
		// 5 — min delta
R
RubaXa 已提交
1496
		// abs — нельзя добавлять, а то глюки при наведении сверху
1497 1498
		return (evt.clientY - (rect.top + rect.height) > 5) ||
			(evt.clientX - (rect.left + rect.width) > 5);
R
RubaXa 已提交
1499 1500 1501
	}


1502 1503 1504 1505 1506 1507 1508
	/**
	 * Generate id
	 * @param   {HTMLElement} el
	 * @returns {String}
	 * @private
	 */
	function _generateId(el) {
R
RubaXa 已提交
1509
		var str = el.tagName + el.className + el.src + el.href + el.textContent,
1510
			i = str.length,
R
RubaXa 已提交
1511
			sum = 0;
1512

1513 1514 1515
		while (i--) {
			sum += str.charCodeAt(i);
		}
1516

1517 1518 1519
		return sum.toString(36);
	}

1520
	/**
1521 1522
	 * Returns the index of an element within its parent for a selected set of
	 * elements
1523
	 * @param  {HTMLElement} el
1524
	 * @param  {selector} selector
1525
	 * @return {number}
1526
	 */
1527
	function _index(el, selector) {
1528 1529
		var index = 0;

1530 1531 1532
		if (!el || !el.parentNode) {
			return -1;
		}
1533

1534
		while (el && (el = el.previousElementSibling)) {
R
RubaXa 已提交
1535
			if ((el.nodeName.toUpperCase() !== 'TEMPLATE') && (selector === '>*' || _matches(el, selector))) {
1536 1537
				index++;
			}
1538
		}
1539

1540 1541
		return index;
	}
R
RubaXa 已提交
1542

1543
	function _matches(/**HTMLElement*/el, /**String*/selector) {
1544
		if (el) {
S
Sam Wray 已提交
1545 1546
			try {
				if (el.matches) {
1547
					return el.matches(selector);
S
Sam Wray 已提交
1548
				} else if (el.msMatchesSelector) {
1549 1550
					return el.msMatchesSelector(selector);
				}
S
Sam Wray 已提交
1551 1552
			} catch(_) {
				return false;
1553
			}
1554
		}
1555 1556

		return false;
1557 1558
	}

1559
	var _throttleTimeout;
R
RubaXa 已提交
1560 1561
	function _throttle(callback, ms) {
		return function () {
1562 1563 1564 1565
			if (!_throttleTimeout) {
				var args = arguments,
					_this = this
				;
R
RubaXa 已提交
1566

1567
				_throttleTimeout = setTimeout(function () {
R
RubaXa 已提交
1568 1569 1570 1571 1572 1573
					if (args.length === 1) {
						callback.call(_this, args[0]);
					} else {
						callback.apply(_this, args);
					}

1574
					_throttleTimeout = void 0;
R
RubaXa 已提交
1575 1576 1577 1578 1579
				}, ms);
			}
		};
	}

1580 1581 1582 1583 1584
	function _cancelThrottle() {
		clearTimeout(_throttleTimeout);
		_throttleTimeout = void 0;
	}

1585 1586 1587 1588 1589 1590 1591 1592 1593 1594 1595 1596
	function _extend(dst, src) {
		if (dst && src) {
			for (var key in src) {
				if (src.hasOwnProperty(key)) {
					dst[key] = src[key];
				}
			}
		}

		return dst;
	}

1597
	function _clone(el) {
C
camargo 已提交
1598
		if (Polymer && Polymer.dom) {
C
camargo 已提交
1599
			return Polymer.dom(el).cloneNode(true);
C
camargo 已提交
1600 1601
		}
		else if ($) {
C
camargo 已提交
1602
			return $(el).clone(true)[0];
C
camargo 已提交
1603 1604
		}
		else {
C
camargo 已提交
1605
			return el.cloneNode(true);
C
camargo 已提交
1606
		}
1607 1608
	}

L
Lebedev Konstantin 已提交
1609
	function _saveInputCheckedState(root) {
1610
		savedInputChecked.length = 0;
1611

L
Lebedev Konstantin 已提交
1612 1613 1614 1615 1616 1617 1618 1619 1620
		var inputs = root.getElementsByTagName('input');
		var idx = inputs.length;

		while (idx--) {
			var el = inputs[idx];
			el.checked && savedInputChecked.push(el);
		}
	}

R
RubaXa 已提交
1621
	function _nextTick(fn) {
R
RubaXa 已提交
1622 1623 1624 1625 1626
		return setTimeout(fn, 0);
	}

	function _cancelNextTick(id) {
		return clearTimeout(id);
R
RubaXa 已提交
1627 1628
	}

C
camargo 已提交
1629
	// Fixed #973:
L
Lebedev Konstantin 已提交
1630 1631 1632 1633 1634 1635
	_on(document, 'touchmove', function (evt) {
		if (Sortable.active) {
			evt.preventDefault();
		}
	});

R
RubaXa 已提交
1636 1637 1638 1639 1640 1641
	// Export utils
	Sortable.utils = {
		on: _on,
		off: _off,
		css: _css,
		find: _find,
R
RubaXa 已提交
1642 1643 1644
		is: function (el, selector) {
			return !!_closest(el, selector, el);
		},
1645
		extend: _extend,
R
RubaXa 已提交
1646
		throttle: _throttle,
R
RubaXa 已提交
1647
		closest: _closest,
1648
		toggleClass: _toggleClass,
1649
		clone: _clone,
R
RubaXa 已提交
1650
		index: _index,
R
RubaXa 已提交
1651 1652
		nextTick: _nextTick,
		cancelNextTick: _cancelNextTick
R
RubaXa 已提交
1653 1654 1655
	};


1656 1657 1658 1659 1660 1661
	/**
	 * Create sortable instance
	 * @param {HTMLElement}  el
	 * @param {Object}      [options]
	 */
	Sortable.create = function (el, options) {
R
RubaXa 已提交
1662
		return new Sortable(el, options);
1663
	};
R
RubaXa 已提交
1664

R
RubaXa 已提交
1665 1666

	// Export
1667
	Sortable.version = '1.7.0';
1668
	return Sortable;
R
RubaXa 已提交
1669
});