PluginProbe
Page Builder: Pagelayer – Drag and Drop website builder / 0.9.0
Page Builder: Pagelayer – Drag and Drop website builder v0.9.0
2.2.1 2.2.0 2.1.9 2.1.8 2.1.7 2.1.6 2.1.5 2.1.4 2.1.3 trunk 0.9.0 0.9.1 0.9.2 0.9.3 0.9.4 0.9.5 0.9.6 0.9.7 0.9.8 0.9.9 1.0.0 1.0.2 1.0.3 1.0.4 1.0.5 All 129 releases
pagelayer / js / drag.js

drag.js in Page Builder: Pagelayer – Drag and Drop website builder 0.9.0, at js/drag.js

2,474 lines 66.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 /**!
2 * Sortable
3 * @author RubaXa <trash@rubaxa.org>
4 * @author owenm <owen23355@gmail.com>
5 * @license MIT
6 */
7
8 (function sortableModule(factory) {
9 "use strict";
10
11 if (typeof define === "function" && define.amd) {
12 define(factory);
13 }
14 else if (typeof module != "undefined" && typeof module.exports != "undefined") {
15 module.exports = factory();
16 }
17 else {
18 /* jshint sub:true */
19 window["Sortable"] = factory();
20 }
21 })(function sortableFactory() {
22 "use strict";
23
24 if (typeof window === "undefined" || !window.document) {
25 return function sortableError() {
26 throw new Error("Sortable.js requires a window with a document");
27 };
28 }
29
30 var dragEl,
31 parentEl,
32 ghostEl,
33 cloneEl,
34 rootEl,
35 nextEl,
36 lastDownEl,
37
38 scrollEl,
39 scrollParentEl,
40 scrollCustomFn,
41
42 oldIndex,
43 newIndex,
44
45 activeGroup,
46 putSortable,
47
48 autoScrolls = [],
49 scrolling = false,
50
51 awaitingDragStarted = false,
52 ignoreNextClick = false,
53 sortables = [],
54
55 pointerElemChangedInterval,
56 lastPointerElemX,
57 lastPointerElemY,
58
59 tapEvt,
60 touchEvt,
61
62 moved,
63
64
65 lastTarget,
66 lastDirection,
67 pastFirstInvertThresh = false,
68 isCircumstantialInvert = false,
69 lastMode, // 'swap' or 'insert'
70
71 targetMoveDistance,
72
73 // For positioning ghost absolutely
74 ghostRelativeParent,
75 ghostRelativeParentInitialScroll = [], // (left, top)
76
77
78 forRepaintDummy,
79 realDragElRect, // dragEl rect after current animation
80
81 /** @const */
82 R_SPACE = /\s+/g,
83
84 expando = 'Sortable' + (new Date).getTime(),
85
86 win = window,
87 document = win.document,
88 parseInt = win.parseInt,
89 setTimeout = win.setTimeout,
90
91 $ = win.jQuery || win.Zepto,
92 Polymer = win.Polymer,
93
94 captureMode = {
95 capture: false,
96 passive: false
97 },
98
99 IE11OrLess = !!navigator.userAgent.match(/(?:Trident.*rv[ :]?11\.|msie|iemobile)/i),
100 Edge = !!navigator.userAgent.match(/Edge/i),
101 FireFox = !!navigator.userAgent.match(/firefox/i),
102 Safari = !!(navigator.userAgent.match(/safari/i) && !navigator.userAgent.match(/chrome/i) && !navigator.userAgent.match(/android/i)),
103 IOS = !!(navigator.userAgent.match(/iP(ad|od|hone)/i)),
104
105 PositionGhostAbsolutely = IOS,
106
107 CSSFloatProperty = Edge || IE11OrLess ? 'cssFloat' : 'float',
108
109 // This will not pass for IE9, because IE9 DnD only works on anchors
110 supportDraggable = ('draggable' in document.createElement('div')),
111
112 supportCssPointerEvents = (function() {
113 // false when <= IE11
114 if (IE11OrLess) {
115 return false;
116 }
117 var el = document.createElement('x');
118 el.style.cssText = 'pointer-events:auto';
119 return el.style.pointerEvents === 'auto';
120 })(),
121
122 _silent = false,
123 _alignedSilent = false,
124
125 abs = Math.abs,
126 min = Math.min,
127 max = Math.max,
128
129 savedInputChecked = [],
130
131 _detectDirection = function(el, options) {
132 var elCSS = _css(el),
133 elWidth = parseInt(elCSS.width)
134 - parseInt(elCSS.paddingLeft)
135 - parseInt(elCSS.paddingRight)
136 - parseInt(elCSS.borderLeftWidth)
137 - parseInt(elCSS.borderRightWidth),
138 child1 = _getChild(el, 0, options),
139 child2 = _getChild(el, 1, options),
140 firstChildCSS = child1 && _css(child1),
141 secondChildCSS = child2 && _css(child2),
142 firstChildWidth = firstChildCSS && parseInt(firstChildCSS.marginLeft) + parseInt(firstChildCSS.marginRight) + _getRect(child1).width,
143 secondChildWidth = secondChildCSS && parseInt(secondChildCSS.marginLeft) + parseInt(secondChildCSS.marginRight) + _getRect(child2).width;
144
145 if (elCSS.display === 'flex') {
146 return elCSS.flexDirection === 'column' || elCSS.flexDirection === 'column-reverse'
147 ? 'vertical' : 'horizontal';
148 }
149
150 if (elCSS.display === 'grid') {
151 return elCSS.gridTemplateColumns.split(' ').length <= 1 ? 'vertical' : 'horizontal';
152 }
153
154 if (child1 && firstChildCSS.float !== 'none') {
155 var touchingSideChild2 = firstChildCSS.float === 'left' ? 'left' : 'right';
156
157 return child2 && (secondChildCSS.clear === 'both' || secondChildCSS.clear === touchingSideChild2) ?
158 'vertical' : 'horizontal';
159 }
160
161 return (child1 &&
162 (
163 firstChildCSS.display === 'block' ||
164 firstChildCSS.display === 'flex' ||
165 firstChildCSS.display === 'table' ||
166 firstChildCSS.display === 'grid' ||
167 firstChildWidth >= elWidth &&
168 elCSS[CSSFloatProperty] === 'none' ||
169 child2 &&
170 elCSS[CSSFloatProperty] === 'none' &&
171 firstChildWidth + secondChildWidth > elWidth
172 ) ?
173 'vertical' : 'horizontal'
174 );
175 },
176
177 /**
178 * Detects first nearest empty sortable to X and Y position using emptyInsertThreshold.
179 * @param {Number} x X position
180 * @param {Number} y Y position
181 * @return {HTMLElement} Element of the first found nearest Sortable
182 */
183 _detectNearestEmptySortable = function(x, y) {
184 for (var i = 0; i < sortables.length; i++) {
185 if (_lastChild(sortables[i])) continue;
186
187 var rect = _getRect(sortables[i]),
188 threshold = sortables[i][expando].options.emptyInsertThreshold,
189 insideHorizontally = x >= (rect.left - threshold) && x <= (rect.right + threshold),
190 insideVertically = y >= (rect.top - threshold) && y <= (rect.bottom + threshold);
191
192 if (insideHorizontally && insideVertically) {
193 return sortables[i];
194 }
195 }
196 },
197
198 _isClientInRowColumn = function(x, y, el, axis, options) {
199 var targetRect = _getRect(el),
200 targetS1Opp = axis === 'vertical' ? targetRect.left : targetRect.top,
201 targetS2Opp = axis === 'vertical' ? targetRect.right : targetRect.bottom,
202 mouseOnOppAxis = axis === 'vertical' ? x : y;
203
204 return targetS1Opp < mouseOnOppAxis && mouseOnOppAxis < targetS2Opp;
205 },
206
207 _isElInRowColumn = function(el1, el2, axis) {
208 var el1Rect = el1 === dragEl && realDragElRect || _getRect(el1),
209 el2Rect = el2 === dragEl && realDragElRect || _getRect(el2),
210 el1S1Opp = axis === 'vertical' ? el1Rect.left : el1Rect.top,
211 el1S2Opp = axis === 'vertical' ? el1Rect.right : el1Rect.bottom,
212 el1OppLength = axis === 'vertical' ? el1Rect.width : el1Rect.height,
213 el2S1Opp = axis === 'vertical' ? el2Rect.left : el2Rect.top,
214 el2S2Opp = axis === 'vertical' ? el2Rect.right : el2Rect.bottom,
215 el2OppLength = axis === 'vertical' ? el2Rect.width : el2Rect.height;
216
217 return (
218 el1S1Opp === el2S1Opp ||
219 el1S2Opp === el2S2Opp ||
220 (el1S1Opp + el1OppLength / 2) === (el2S1Opp + el2OppLength / 2)
221 );
222 },
223
224 _getParentAutoScrollElement = function(el, includeSelf) {
225 // skip to window
226 if (!el || !el.getBoundingClientRect) return _getWindowScrollingElement();
227
228 var elem = el;
229 var gotSelf = false;
230 do {
231 // we don't need to get elem css if it isn't even overflowing in the first place (performance)
232 if (elem.clientWidth < elem.scrollWidth || elem.clientHeight < elem.scrollHeight) {
233 var elemCSS = _css(elem);
234 if (
235 elem.clientWidth < elem.scrollWidth && (elemCSS.overflowX == 'auto' || elemCSS.overflowX == 'scroll') ||
236 elem.clientHeight < elem.scrollHeight && (elemCSS.overflowY == 'auto' || elemCSS.overflowY == 'scroll')
237 ) {
238 if (!elem || !elem.getBoundingClientRect || elem === document.body) return _getWindowScrollingElement();
239
240 if (gotSelf || includeSelf) return elem;
241 gotSelf = true;
242 }
243 }
244 /* jshint boss:true */
245 } while (elem = elem.parentNode);
246
247 return _getWindowScrollingElement();
248 },
249
250 _getWindowScrollingElement = function() {
251 if (IE11OrLess) {
252 return document.documentElement;
253 } else {
254 return document.scrollingElement;
255 }
256 },
257
258 _scrollBy = function(el, x, y) {
259 el.scrollLeft += x;
260 el.scrollTop += y;
261 },
262
263 _autoScroll = _throttle(function (/**Event*/evt, /**Object*/options, /**HTMLElement*/rootEl, /**Boolean*/isFallback) {
264 // Bug: https://bugzilla.mozilla.org/show_bug.cgi?id=505521
265 if (options.scroll) {
266 var _this = rootEl ? rootEl[expando] : window,
267 sens = options.scrollSensitivity,
268 speed = options.scrollSpeed,
269
270 x = evt.clientX,
271 y = evt.clientY,
272
273 winScroller = _getWindowScrollingElement(),
274
275 scrollThisInstance = false;
276
277 // Detect scrollEl
278 if (scrollParentEl !== rootEl) {
279 _clearAutoScrolls();
280
281 scrollEl = options.scroll;
282 scrollCustomFn = options.scrollFn;
283
284 if (scrollEl === true) {
285 scrollEl = _getParentAutoScrollElement(rootEl, true);
286 scrollParentEl = scrollEl;
287 }
288 }
289
290
291 var layersOut = 0;
292 var currentParent = scrollEl;
293 do {
294 var el = currentParent,
295 rect = _getRect(el),
296
297 top = rect.top,
298 bottom = rect.bottom,
299 left = rect.left,
300 right = rect.right,
301
302 width = rect.width,
303 height = rect.height,
304
305 scrollWidth,
306 scrollHeight,
307
308 css,
309
310 vx,
311 vy,
312
313 canScrollX,
314 canScrollY,
315
316 scrollPosX,
317 scrollPosY;
318
319
320 scrollWidth = el.scrollWidth;
321 scrollHeight = el.scrollHeight;
322
323 css = _css(el);
324
325 scrollPosX = el.scrollLeft;
326 scrollPosY = el.scrollTop;
327
328 if (el === winScroller) {
329 canScrollX = width < scrollWidth && (css.overflowX === 'auto' || css.overflowX === 'scroll' || css.overflowX === 'visible');
330 canScrollY = height < scrollHeight && (css.overflowY === 'auto' || css.overflowY === 'scroll' || css.overflowY === 'visible');
331 } else {
332 canScrollX = width < scrollWidth && (css.overflowX === 'auto' || css.overflowX === 'scroll');
333 canScrollY = height < scrollHeight && (css.overflowY === 'auto' || css.overflowY === 'scroll');
334 }
335
336 vx = canScrollX && (abs(right - x) <= sens && (scrollPosX + width) < scrollWidth) - (abs(left - x) <= sens && !!scrollPosX);
337
338 vy = canScrollY && (abs(bottom - y) <= sens && (scrollPosY + height) < scrollHeight) - (abs(top - y) <= sens && !!scrollPosY);
339
340
341 if (!autoScrolls[layersOut]) {
342 for (var i = 0; i <= layersOut; i++) {
343 if (!autoScrolls[i]) {
344 autoScrolls[i] = {};
345 }
346 }
347 }
348
349 if (autoScrolls[layersOut].vx != vx || autoScrolls[layersOut].vy != vy || autoScrolls[layersOut].el !== el) {
350 autoScrolls[layersOut].el = el;
351 autoScrolls[layersOut].vx = vx;
352 autoScrolls[layersOut].vy = vy;
353
354 clearInterval(autoScrolls[layersOut].pid);
355
356 if (el && (vx != 0 || vy != 0)) {
357 scrollThisInstance = true;
358 /* jshint loopfunc:true */
359 autoScrolls[layersOut].pid = setInterval((function () {
360 // emulate drag over during autoscroll (fallback), emulating native DnD behaviour
361 if (isFallback && this.layer === 0) {
362 Sortable.active._emulateDragOver(true);
363 Sortable.active._onTouchMove(touchEvt, true);
364 }
365 var scrollOffsetY = autoScrolls[this.layer].vy ? autoScrolls[this.layer].vy * speed : 0;
366 var scrollOffsetX = autoScrolls[this.layer].vx ? autoScrolls[this.layer].vx * speed : 0;
367
368 if ('function' === typeof(scrollCustomFn)) {
369 if (scrollCustomFn.call(_this, scrollOffsetX, scrollOffsetY, evt, touchEvt, autoScrolls[this.layer].el) !== 'continue') {
370 return;
371 }
372 }
373
374 _scrollBy(autoScrolls[this.layer].el, scrollOffsetX, scrollOffsetY);
375 }).bind({layer: layersOut}), 24);
376 }
377 }
378 layersOut++;
379 } while (options.bubbleScroll && currentParent !== winScroller && (currentParent = _getParentAutoScrollElement(currentParent, false)));
380 scrolling = scrollThisInstance; // in case another function catches scrolling as false in between when it is not
381 }
382 }, 30),
383
384 _clearAutoScrolls = function () {
385 autoScrolls.forEach(function(autoScroll) {
386 clearInterval(autoScroll.pid);
387 });
388 autoScrolls = [];
389 },
390
391 _prepareGroup = function (options) {
392 function toFn(value, pull) {
393 return function(to, from, dragEl, evt) {
394 var sameGroup = to.options.group.name &&
395 from.options.group.name &&
396 to.options.group.name === from.options.group.name;
397
398 if (value == null && (pull || sameGroup)) {
399 // Default pull value
400 // Default pull and put value if same group
401 return true;
402 } else if (value == null || value === false) {
403 return false;
404 } else if (pull && value === 'clone') {
405 return value;
406 } else if (typeof value === 'function') {
407 return toFn(value(to, from, dragEl, evt), pull)(to, from, dragEl, evt);
408 } else {
409 var otherGroup = (pull ? to : from).options.group.name;
410
411 return (value === true ||
412 (typeof value === 'string' && value === otherGroup) ||
413 (value.join && value.indexOf(otherGroup) > -1));
414 }
415 };
416 }
417
418 var group = {};
419 var originalGroup = options.group;
420
421 if (!originalGroup || typeof originalGroup != 'object') {
422 originalGroup = {name: originalGroup};
423 }
424
425 group.name = originalGroup.name;
426 group.checkPull = toFn(originalGroup.pull, true);
427 group.checkPut = toFn(originalGroup.put);
428 group.revertClone = originalGroup.revertClone;
429
430 options.group = group;
431 },
432
433 _checkAlignment = function(evt) {
434 if (!dragEl || !dragEl.parentNode) return;
435 dragEl.parentNode[expando] && dragEl.parentNode[expando]._computeIsAligned(evt);
436 },
437
438 _isTrueParentSortable = function(el, target) {
439 var trueParent = target;
440 while (!trueParent[expando]) {
441 trueParent = trueParent.parentNode;
442 }
443
444 return el === trueParent;
445 },
446
447 _artificalBubble = function(sortable, originalEvt, method) {
448 // Artificial IE bubbling
449 var nextParent = sortable.parentNode;
450 while (nextParent && !nextParent[expando]) {
451 nextParent = nextParent.parentNode;
452 }
453
454 if (nextParent) {
455 nextParent[expando][method](_extend(originalEvt, {
456 artificialBubble: true
457 }));
458 }
459 },
460
461 _hideGhostForTarget = function() {
462 if (!supportCssPointerEvents && ghostEl) {
463 _css(ghostEl, 'display', 'none');
464 }
465 },
466
467 _unhideGhostForTarget = function() {
468 if (!supportCssPointerEvents && ghostEl) {
469 _css(ghostEl, 'display', '');
470 }
471 };
472
473
474 // #1184 fix - Prevent click event on fallback if dragged but item not changed position
475 document.addEventListener('click', function(evt) {
476 if (ignoreNextClick) {
477 evt.preventDefault();
478 evt.stopPropagation && evt.stopPropagation();
479 evt.stopImmediatePropagation && evt.stopImmediatePropagation();
480 ignoreNextClick = false;
481 return false;
482 }
483 }, true);
484
485 var nearestEmptyInsertDetectEvent = function(evt) {
486 evt = evt.touches ? evt.touches[0] : evt;
487 if (dragEl) {
488 var nearest = _detectNearestEmptySortable(evt.clientX, evt.clientY);
489
490 if (nearest) {
491 nearest[expando]._onDragOver({
492 clientX: evt.clientX,
493 clientY: evt.clientY,
494 target: nearest,
495 rootEl: nearest
496 });
497 }
498 }
499 };
500 // We do not want this to be triggered if completed (bubbling canceled), so only define it here
501 _on(document, 'dragover', nearestEmptyInsertDetectEvent);
502 _on(document, 'mousemove', nearestEmptyInsertDetectEvent);
503 _on(document, 'touchmove', nearestEmptyInsertDetectEvent);
504
505 /**
506 * @class Sortable
507 * @param {HTMLElement} el
508 * @param {Object} [options]
509 */
510 function Sortable(el, options) {
511 if (!(el && el.nodeType && el.nodeType === 1)) {
512 throw 'Sortable: `el` must be HTMLElement, not ' + {}.toString.call(el);
513 }
514
515 this.el = el; // root element
516 this.options = options = _extend({}, options);
517
518
519 // Export instance
520 el[expando] = this;
521
522 // Default options
523 var defaults = {
524 group: null,
525 sort: true,
526 disabled: false,
527 store: null,
528 handle: null,
529 scroll: true,
530 scrollSensitivity: 30,
531 scrollSpeed: 10,
532 bubbleScroll: true,
533 draggable: /[uo]l/i.test(el.nodeName) ? '>li' : '>*',
534 swapThreshold: 1, // percentage; 0 <= x <= 1
535 invertSwap: false, // invert always
536 invertedSwapThreshold: null, // will be set to same as swapThreshold if default
537 removeCloneOnHide: true,
538 direction: function() {
539 return _detectDirection(el, this.options);
540 },
541 ghostClass: 'sortable-ghost',
542 chosenClass: 'sortable-chosen',
543 dragClass: 'sortable-drag',
544 ignore: 'a, img',
545 filter: null,
546 preventOnFilter: true,
547 animation: 0,
548 easing: null,
549 setData: function (dataTransfer, dragEl) {
550 dataTransfer.setData('Text', dragEl.textContent);
551 },
552 dropBubble: false,
553 dragoverBubble: false,
554 dataIdAttr: 'data-id',
555 delay: 0,
556 touchStartThreshold: parseInt(window.devicePixelRatio, 10) || 1,
557 forceFallback: false,
558 fallbackClass: 'sortable-fallback',
559 fallbackOnBody: false,
560 fallbackTolerance: 0,
561 fallbackOffset: {x: 0, y: 0},
562 supportPointer: Sortable.supportPointer !== false && (
563 ('PointerEvent' in window) ||
564 window.navigator && ('msPointerEnabled' in window.navigator) // microsoft
565 ),
566 emptyInsertThreshold: 5
567 };
568
569
570 // Set default options
571 for (var name in defaults) {
572 !(name in options) && (options[name] = defaults[name]);
573 }
574
575 _prepareGroup(options);
576
577 // Bind all private methods
578 for (var fn in this) {
579 if (fn.charAt(0) === '_' && typeof this[fn] === 'function') {
580 this[fn] = this[fn].bind(this);
581 }
582 }
583
584 // Setup drag mode
585 this.nativeDraggable = options.forceFallback ? false : supportDraggable;
586
587 if (this.nativeDraggable) {
588 // Touch start threshold cannot be greater than the native dragstart threshold
589 this.options.touchStartThreshold = 1;
590 }
591
592 // Bind events
593 if (options.supportPointer) {
594 _on(el, 'pointerdown', this._onTapStart);
595 } else {
596 _on(el, 'mousedown', this._onTapStart);
597 _on(el, 'touchstart', this._onTapStart);
598 }
599
600 if (this.nativeDraggable) {
601 _on(el, 'dragover', this);
602 _on(el, 'dragenter', this);
603 }
604
605 sortables.push(this.el);
606
607 // Restore sorting
608 options.store && options.store.get && this.sort(options.store.get(this) || []);
609 }
610
611 Sortable.prototype = /** @lends Sortable.prototype */ {
612 constructor: Sortable,
613
614 _computeIsAligned: function(evt) {
615 var target;
616
617 if (ghostEl && !supportCssPointerEvents) {
618 _hideGhostForTarget();
619 target = document.elementFromPoint(evt.clientX, evt.clientY);
620 _unhideGhostForTarget();
621 } else {
622 target = evt.target;
623 }
624
625 target = _closest(target, this.options.draggable, this.el, false);
626 if (_alignedSilent) return;
627 if (!dragEl || dragEl.parentNode !== this.el) return;
628
629 var children = this.el.children;
630 for (var i = 0; i < children.length; i++) {
631 // Don't change for target in case it is changed to aligned before onDragOver is fired
632 if (_closest(children[i], this.options.draggable, this.el, false) && children[i] !== target) {
633 children[i].sortableMouseAligned = _isClientInRowColumn(evt.clientX, evt.clientY, children[i], this._getDirection(evt, null), this.options);
634 }
635 }
636 // Used for nulling last target when not in element, nothing to do with checking if aligned
637 if (!_closest(target, this.options.draggable, this.el, true)) {
638 lastTarget = null;
639 }
640
641 _alignedSilent = true;
642 setTimeout(function() {
643 _alignedSilent = false;
644 }, 30);
645
646 },
647
648 _getDirection: function(evt, target) {
649 return (typeof this.options.direction === 'function') ? this.options.direction.call(this, evt, target, dragEl) : this.options.direction;
650 },
651
652 _onTapStart: function (/** Event|TouchEvent */evt) {
653 if (!evt.cancelable) return;
654 var _this = this,
655 el = this.el,
656 options = this.options,
657 preventOnFilter = options.preventOnFilter,
658 type = evt.type,
659 touch = evt.touches && evt.touches[0],
660 target = (touch || evt).target,
661 originalTarget = evt.target.shadowRoot && ((evt.path && evt.path[0]) || (evt.composedPath && evt.composedPath()[0])) || target,
662 filter = options.filter,
663 startIndex;
664
665 _saveInputCheckedState(el);
666
667
668 // IE: Calls events in capture mode if event element is nested. This ensures only correct element's _onTapStart goes through.
669 // This process is also done in _onDragOver
670 if (IE11OrLess && !evt.artificialBubble && !_isTrueParentSortable(el, target)) {
671 return;
672 }
673
674 // Don't trigger start event when an element is been dragged, otherwise the evt.oldindex always wrong when set option.group.
675 if (dragEl) {
676 return;
677 }
678
679 if (/mousedown|pointerdown/.test(type) && evt.button !== 0 || options.disabled) {
680 return; // only left button and enabled
681 }
682
683 // cancel dnd if original target is content editable
684 if (originalTarget.isContentEditable) {
685 return;
686 }
687
688 target = _closest(target, options.draggable, el, false);
689
690 if (!target) {
691 if (IE11OrLess) {
692 _artificalBubble(el, evt, '_onTapStart');
693 }
694 return;
695 }
696
697 if (lastDownEl === target) {
698 // Ignoring duplicate `down`
699 return;
700 }
701
702 // Get the index of the dragged element within its parent
703 startIndex = _index(target, options.draggable);
704
705 // Check filter
706 if (typeof filter === 'function') {
707 if (filter.call(this, evt, target, this)) {
708 _dispatchEvent(_this, originalTarget, 'filter', target, el, el, startIndex);
709 preventOnFilter && evt.cancelable && evt.preventDefault();
710 return; // cancel dnd
711 }
712 }
713 else if (filter) {
714 filter = filter.split(',').some(function (criteria) {
715 criteria = _closest(originalTarget, criteria.trim(), el, false);
716
717 if (criteria) {
718 _dispatchEvent(_this, criteria, 'filter', target, el, el, startIndex);
719 return true;
720 }
721 });
722
723 if (filter) {
724 preventOnFilter && evt.cancelable && evt.preventDefault();
725 return; // cancel dnd
726 }
727 }
728
729 if (options.handle && !_closest(originalTarget, options.handle, el, false)) {
730 return;
731 }
732
733 // Prepare `dragstart`
734 this._prepareDragStart(evt, touch, target, startIndex);
735 },
736
737
738 _handleAutoScroll: function(evt, fallback) {
739 if (!dragEl || !this.options.scroll) return;
740 var x = evt.clientX,
741 y = evt.clientY,
742
743 elem = document.elementFromPoint(x, y),
744 _this = this;
745
746 // IE does not seem to have native autoscroll,
747 // Edge's autoscroll seems too conditional,
748 // MACOS Safari does not have autoscroll,
749 // Firefox and Chrome are good
750 if (fallback || Edge || IE11OrLess || Safari) {
751 _autoScroll(evt, _this.options, elem, fallback);
752
753 // Listener for pointer element change
754 var ogElemScroller = _getParentAutoScrollElement(elem, true);
755 if (
756 scrolling &&
757 (
758 !pointerElemChangedInterval ||
759 x !== lastPointerElemX ||
760 y !== lastPointerElemY
761 )
762 ) {
763
764 pointerElemChangedInterval && clearInterval(pointerElemChangedInterval);
765 // Detect for pointer elem change, emulating native DnD behaviour
766 pointerElemChangedInterval = setInterval(function() {
767 if (!dragEl) return;
768 // could also check if scroll direction on newElem changes due to parent autoscrolling
769 var newElem = _getParentAutoScrollElement(document.elementFromPoint(x, y), true);
770 if (newElem !== ogElemScroller) {
771 ogElemScroller = newElem;
772 _clearAutoScrolls();
773 _autoScroll(evt, _this.options, ogElemScroller, fallback);
774 }
775 }, 10);
776 lastPointerElemX = x;
777 lastPointerElemY = y;
778 }
779
780 } else {
781 // if DnD is enabled (and browser has good autoscrolling), first autoscroll will already scroll, so get parent autoscroll of first autoscroll
782 if (!_this.options.bubbleScroll || _getParentAutoScrollElement(elem, true) === _getWindowScrollingElement()) {
783 _clearAutoScrolls();
784 //return;
785 }
786 _autoScroll(evt, _this.options, _getParentAutoScrollElement(elem, false), false);
787 }
788 },
789
790 _prepareDragStart: function (/** Event */evt, /** Touch */touch, /** HTMLElement */target, /** Number */startIndex) {
791 var _this = this,
792 el = _this.el,
793 options = _this.options,
794 ownerDocument = el.ownerDocument,
795 dragStartFn;
796
797 if (target && !dragEl && (target.parentNode === el)) {
798 rootEl = el;
799 dragEl = target;
800 parentEl = dragEl.parentNode;
801 nextEl = dragEl.nextSibling;
802 lastDownEl = target;
803 activeGroup = options.group;
804 oldIndex = startIndex;
805
806 tapEvt = {
807 target: dragEl,
808 clientX: (touch || evt).clientX,
809 clientY: (touch || evt).clientY
810 };
811
812 this._lastX = (touch || evt).clientX;
813 this._lastY = (touch || evt).clientY;
814
815 dragEl.style['will-change'] = 'all';
816 // undo animation if needed
817 dragEl.style.transition = '';
818 dragEl.style.transform = '';
819
820 dragStartFn = function () {
821 // Delayed drag has been triggered
822 // we can re-enable the events: touchmove/mousemove
823 _this._disableDelayedDragEvents();
824
825 if (!FireFox && _this.nativeDraggable) {
826 dragEl.draggable = true;
827 }
828
829 // Bind the events: dragstart/dragend
830 _this._triggerDragStart(evt, touch);
831
832 // Drag start event
833 _dispatchEvent(_this, rootEl, 'choose', dragEl, rootEl, rootEl, oldIndex);
834
835 // Chosen item
836 _toggleClass(dragEl, options.chosenClass, true);
837 };
838
839 // Disable "draggable"
840 options.ignore.split(',').forEach(function (criteria) {
841 _find(dragEl, criteria.trim(), _disableDraggable);
842 });
843
844 if (options.supportPointer) {
845 _on(ownerDocument, 'pointerup', _this._onDrop);
846 } else {
847 _on(ownerDocument, 'mouseup', _this._onDrop);
848 _on(ownerDocument, 'touchend', _this._onDrop);
849 _on(ownerDocument, 'touchcancel', _this._onDrop);
850 }
851
852 // Make dragEl draggable (must be before delay for FireFox)
853 if (FireFox && this.nativeDraggable) {
854 this.options.touchStartThreshold = 4;
855 dragEl.draggable = true;
856 }
857
858 // Delay is impossible for native DnD in Edge or IE
859 if (options.delay && (!this.nativeDraggable || !(Edge || IE11OrLess))) {
860 // If the user moves the pointer or let go the click or touch
861 // before the delay has been reached:
862 // disable the delayed drag
863 _on(ownerDocument, 'mouseup', _this._disableDelayedDrag);
864 _on(ownerDocument, 'touchend', _this._disableDelayedDrag);
865 _on(ownerDocument, 'touchcancel', _this._disableDelayedDrag);
866 _on(ownerDocument, 'mousemove', _this._delayedDragTouchMoveHandler);
867 _on(ownerDocument, 'touchmove', _this._delayedDragTouchMoveHandler);
868 options.supportPointer && _on(ownerDocument, 'pointermove', _this._delayedDragTouchMoveHandler);
869
870 _this._dragStartTimer = setTimeout(dragStartFn, options.delay);
871 } else {
872 dragStartFn();
873 }
874 }
875 },
876
877 _delayedDragTouchMoveHandler: function (/** TouchEvent|PointerEvent **/e) {
878 var touch = e.touches ? e.touches[0] : e;
879 if (max(abs(touch.clientX - this._lastX), abs(touch.clientY - this._lastY))
880 >= Math.floor(this.options.touchStartThreshold / (this.nativeDraggable && window.devicePixelRatio || 1))
881 ) {
882 this._disableDelayedDrag();
883 }
884 },
885
886 _disableDelayedDrag: function () {
887 dragEl && _disableDraggable(dragEl);
888 clearTimeout(this._dragStartTimer);
889
890 this._disableDelayedDragEvents();
891 },
892
893 _disableDelayedDragEvents: function () {
894 var ownerDocument = this.el.ownerDocument;
895 _off(ownerDocument, 'mouseup', this._disableDelayedDrag);
896 _off(ownerDocument, 'touchend', this._disableDelayedDrag);
897 _off(ownerDocument, 'touchcancel', this._disableDelayedDrag);
898 _off(ownerDocument, 'mousemove', this._delayedDragTouchMoveHandler);
899 _off(ownerDocument, 'touchmove', this._delayedDragTouchMoveHandler);
900 _off(ownerDocument, 'pointermove', this._delayedDragTouchMoveHandler);
901 },
902
903 _triggerDragStart: function (/** Event */evt, /** Touch */touch) {
904 touch = touch || (evt.pointerType == 'touch' ? evt : null);
905
906 if (!this.nativeDraggable || touch) {
907 if (this.options.supportPointer) {
908 _on(document, 'pointermove', this._onTouchMove);
909 } else if (touch) {
910 _on(document, 'touchmove', this._onTouchMove);
911 } else {
912 _on(document, 'mousemove', this._onTouchMove);
913 }
914 } else {
915 _on(dragEl, 'dragend', this);
916 _on(rootEl, 'dragstart', this._onDragStart);
917 }
918
919 try {
920 if (document.selection) {
921 // Timeout neccessary for IE9
922 _nextTick(function () {
923 document.selection.empty();
924 });
925 } else {
926 window.getSelection().removeAllRanges();
927 }
928 } catch (err) {
929 }
930 },
931
932 _dragStarted: function (fallback, evt) {
933 awaitingDragStarted = false;
934 if (rootEl && dragEl) {
935 if (this.nativeDraggable) {
936 _on(document, 'dragover', this._handleAutoScroll);
937 _on(document, 'dragover', _checkAlignment);
938 }
939 var options = this.options;
940
941 // Apply effect
942 !fallback && _toggleClass(dragEl, options.dragClass, false);
943 _toggleClass(dragEl, options.ghostClass, true);
944
945 // In case dragging an animated element
946 _css(dragEl, 'transform', '');
947
948 Sortable.active = this;
949
950 fallback && this._appendGhost();
951
952 // Drag start event
953 _dispatchEvent(this, rootEl, 'start', dragEl, rootEl, rootEl, oldIndex, undefined, evt);
954 } else {
955 this._nulling();
956 }
957 },
958
959 _emulateDragOver: function (forAutoScroll) {
960 if (touchEvt) {
961 if (this._lastX === touchEvt.clientX && this._lastY === touchEvt.clientY && !forAutoScroll) {
962 return;
963 }
964 this._lastX = touchEvt.clientX;
965 this._lastY = touchEvt.clientY;
966
967 _hideGhostForTarget();
968
969 var target = document.elementFromPoint(touchEvt.clientX, touchEvt.clientY);
970 var parent = target;
971
972 while (target && target.shadowRoot) {
973 target = target.shadowRoot.elementFromPoint(touchEvt.clientX, touchEvt.clientY);
974 parent = target;
975 }
976
977 if (parent) {
978 do {
979 if (parent[expando]) {
980 var inserted;
981
982 inserted = parent[expando]._onDragOver({
983 clientX: touchEvt.clientX,
984 clientY: touchEvt.clientY,
985 target: target,
986 rootEl: parent
987 });
988
989 if (inserted && !this.options.dragoverBubble) {
990 break;
991 }
992 }
993
994 target = parent; // store last element
995 }
996 /* jshint boss:true */
997 while (parent = parent.parentNode);
998 }
999 dragEl.parentNode[expando]._computeIsAligned(touchEvt);
1000
1001 _unhideGhostForTarget();
1002 }
1003 },
1004
1005
1006 _onTouchMove: function (/**TouchEvent*/evt, forAutoScroll) {
1007 if (tapEvt) {
1008 var options = this.options,
1009 fallbackTolerance = options.fallbackTolerance,
1010 fallbackOffset = options.fallbackOffset,
1011 touch = evt.touches ? evt.touches[0] : evt,
1012 matrix = ghostEl && _matrix(ghostEl),
1013 scaleX = ghostEl && matrix && matrix.a,
1014 scaleY = ghostEl && matrix && matrix.d,
1015 relativeScrollOffset = PositionGhostAbsolutely && ghostRelativeParent && _getRelativeScrollOffset(ghostRelativeParent),
1016 dx = ((touch.clientX - tapEvt.clientX)
1017 + fallbackOffset.x) / (scaleX || 1)
1018 + (relativeScrollOffset ? (relativeScrollOffset[0] - ghostRelativeParentInitialScroll[0]) : 0) / (scaleX || 1),
1019 dy = ((touch.clientY - tapEvt.clientY)
1020 + fallbackOffset.y) / (scaleY || 1)
1021 + (relativeScrollOffset ? (relativeScrollOffset[1] - ghostRelativeParentInitialScroll[1]) : 0) / (scaleY || 1),
1022 translate3d = evt.touches ? 'translate3d(' + dx + 'px,' + dy + 'px,0)' : 'translate(' + dx + 'px,' + dy + 'px)';
1023
1024 // only set the status to dragging, when we are actually dragging
1025 if (!Sortable.active && !awaitingDragStarted) {
1026 if (fallbackTolerance &&
1027 min(abs(touch.clientX - this._lastX), abs(touch.clientY - this._lastY)) < fallbackTolerance
1028 ) {
1029 return;
1030 }
1031 this._onDragStart(evt, true);
1032 }
1033
1034 !forAutoScroll && this._handleAutoScroll(touch, true);
1035
1036 moved = true;
1037 touchEvt = touch;
1038
1039 _css(ghostEl, 'webkitTransform', translate3d);
1040 _css(ghostEl, 'mozTransform', translate3d);
1041 _css(ghostEl, 'msTransform', translate3d);
1042 _css(ghostEl, 'transform', translate3d);
1043
1044 evt.cancelable && evt.preventDefault();
1045 }
1046 },
1047
1048 _appendGhost: function () {
1049 // Bug if using scale(): https://stackoverflow.com/questions/2637058
1050 // Not being adjusted for
1051 if (!ghostEl) {
1052 var container = this.options.fallbackOnBody ? document.body : rootEl,
1053 rect = _getRect(dragEl, true, container, !PositionGhostAbsolutely),
1054 css = _css(dragEl),
1055 options = this.options;
1056
1057 // Position absolutely
1058 if (PositionGhostAbsolutely) {
1059 // Get relatively positioned parent
1060 ghostRelativeParent = container;
1061
1062 while (
1063 _css(ghostRelativeParent, 'position') === 'static' &&
1064 _css(ghostRelativeParent, 'transform') === 'none' &&
1065 ghostRelativeParent !== document
1066 ) {
1067 ghostRelativeParent = ghostRelativeParent.parentNode;
1068 }
1069
1070 if (ghostRelativeParent !== document) {
1071 var ghostRelativeParentRect = _getRect(ghostRelativeParent, true);
1072
1073 rect.top -= ghostRelativeParentRect.top;
1074 rect.left -= ghostRelativeParentRect.left;
1075 }
1076
1077 if (ghostRelativeParent !== document.body && ghostRelativeParent !== document.documentElement) {
1078 if (ghostRelativeParent === document) ghostRelativeParent = _getWindowScrollingElement();
1079
1080 rect.top += ghostRelativeParent.scrollTop;
1081 rect.left += ghostRelativeParent.scrollLeft;
1082 } else {
1083 ghostRelativeParent = _getWindowScrollingElement();
1084 }
1085 ghostRelativeParentInitialScroll = _getRelativeScrollOffset(ghostRelativeParent);
1086 }
1087
1088
1089 ghostEl = dragEl.cloneNode(true);
1090
1091 _toggleClass(ghostEl, options.ghostClass, false);
1092 _toggleClass(ghostEl, options.fallbackClass, true);
1093 _toggleClass(ghostEl, options.dragClass, true);
1094
1095 _css(ghostEl, 'box-sizing', 'border-box');
1096 _css(ghostEl, 'margin', 0);
1097 _css(ghostEl, 'top', rect.top);
1098 _css(ghostEl, 'left', rect.left);
1099 _css(ghostEl, 'width', rect.width);
1100 _css(ghostEl, 'height', rect.height);
1101 _css(ghostEl, 'opacity', '0.8');
1102 _css(ghostEl, 'position', (PositionGhostAbsolutely ? 'absolute' : 'fixed'));
1103 _css(ghostEl, 'zIndex', '100000');
1104 _css(ghostEl, 'pointerEvents', 'none');
1105
1106 container.appendChild(ghostEl);
1107 }
1108 },
1109
1110 _onDragStart: function (/**Event*/evt, /**boolean*/fallback) {
1111 var _this = this;
1112 var dataTransfer = evt.dataTransfer;
1113 var options = _this.options;
1114
1115 // Setup clone
1116 cloneEl = _clone(dragEl);
1117
1118 cloneEl.draggable = false;
1119 cloneEl.style['will-change'] = '';
1120
1121 this._hideClone();
1122
1123 _toggleClass(cloneEl, _this.options.chosenClass, false);
1124
1125
1126 // #1143: IFrame support workaround
1127 _this._cloneId = _nextTick(function () {
1128 if (!_this.options.removeCloneOnHide) {
1129 rootEl.insertBefore(cloneEl, dragEl);
1130 }
1131 _dispatchEvent(_this, rootEl, 'clone', dragEl);
1132 });
1133
1134
1135 !fallback && _toggleClass(dragEl, options.dragClass, true);
1136
1137 // Set proper drop events
1138 if (fallback) {
1139 ignoreNextClick = true;
1140 _this._loopId = setInterval(_this._emulateDragOver, 50);
1141 } else {
1142 // Undo what was set in _prepareDragStart before drag started
1143 _off(document, 'mouseup', _this._onDrop);
1144 _off(document, 'touchend', _this._onDrop);
1145 _off(document, 'touchcancel', _this._onDrop);
1146
1147 if (dataTransfer) {
1148 dataTransfer.effectAllowed = 'move';
1149 options.setData && options.setData.call(_this, dataTransfer, dragEl);
1150 }
1151
1152 _on(document, 'drop', _this);
1153
1154 // #1276 fix:
1155 _css(dragEl, 'transform', 'translateZ(0)');
1156 }
1157
1158 awaitingDragStarted = true;
1159
1160 _this._dragStartId = _nextTick(_this._dragStarted.bind(_this, fallback, evt));
1161 _on(document, 'selectstart', _this);
1162 if (Safari) {
1163 _css(document.body, 'user-select', 'none');
1164 }
1165 },
1166
1167
1168 // Returns true - if no further action is needed (either inserted or another condition)
1169 _onDragOver: function (/**Event*/evt) {
1170 var el = this.el,
1171 target = evt.target,
1172 dragRect,
1173 targetRect,
1174 revert,
1175 options = this.options,
1176 group = options.group,
1177 activeSortable = Sortable.active,
1178 isOwner = (activeGroup === group),
1179 canSort = options.sort,
1180 _this = this;
1181
1182 if (_silent) return;
1183
1184 // IE event order fix
1185 if (IE11OrLess && !evt.rootEl && !evt.artificialBubble && !_isTrueParentSortable(el, target)) {
1186 return;
1187 }
1188
1189 // Return invocation when dragEl is inserted (or completed)
1190 function completed(insertion) {
1191 if (insertion) {
1192 if (isOwner) {
1193 activeSortable._hideClone();
1194 } else {
1195 activeSortable._showClone(_this);
1196 }
1197
1198 if (activeSortable) {
1199 // Set ghost class to new sortable's ghost class
1200 _toggleClass(dragEl, putSortable ? putSortable.options.ghostClass : activeSortable.options.ghostClass, false);
1201 _toggleClass(dragEl, options.ghostClass, true);
1202 }
1203
1204 if (putSortable !== _this && _this !== Sortable.active) {
1205 putSortable = _this;
1206 } else if (_this === Sortable.active) {
1207 putSortable = null;
1208 }
1209
1210 // Animation
1211 dragRect && _this._animate(dragRect, dragEl);
1212 target && targetRect && _this._animate(targetRect, target);
1213 }
1214
1215
1216 // Null lastTarget if it is not inside a previously swapped element
1217 if ((target === dragEl && !dragEl.animated) || (target === el && !target.animated)) {
1218 lastTarget = null;
1219 }
1220 // no bubbling and not fallback
1221 if (!options.dragoverBubble && !evt.rootEl && target !== document) {
1222 _this._handleAutoScroll(evt);
1223 dragEl.parentNode[expando]._computeIsAligned(evt);
1224 }
1225
1226 !options.dragoverBubble && evt.stopPropagation && evt.stopPropagation();
1227
1228 return true;
1229 }
1230
1231 // Call when dragEl has been inserted
1232 function changed() {
1233 _dispatchEvent(_this, rootEl, 'change', target, el, rootEl, oldIndex, _index(dragEl, options.draggable), evt);
1234 }
1235
1236
1237 if (evt.preventDefault !== void 0) {
1238 evt.cancelable && evt.preventDefault();
1239 }
1240
1241
1242 moved = true;
1243
1244 target = _closest(target, options.draggable, el, true);
1245
1246 // target is dragEl or target is animated
1247 if (!!_closest(evt.target, null, dragEl, true) || target.animated) {
1248 return completed(false);
1249 }
1250
1251 if (target !== dragEl) {
1252 ignoreNextClick = false;
1253 }
1254
1255 if (activeSortable && !options.disabled &&
1256 (isOwner
1257 ? canSort || (revert = !rootEl.contains(dragEl)) // Reverting item into the original list
1258 : (
1259 putSortable === this ||
1260 (
1261 (this.lastPutMode = activeGroup.checkPull(this, activeSortable, dragEl, evt)) &&
1262 group.checkPut(this, activeSortable, dragEl, evt)
1263 )
1264 )
1265 )
1266 ) {
1267 var axis = this._getDirection(evt, target);
1268
1269 dragRect = _getRect(dragEl);
1270
1271 if (revert) {
1272 this._hideClone();
1273 parentEl = rootEl; // actualization
1274
1275 if (nextEl) {
1276 rootEl.insertBefore(dragEl, nextEl);
1277 } else {
1278 rootEl.appendChild(dragEl);
1279 }
1280
1281 return completed(true);
1282 }
1283
1284 var elLastChild = _lastChild(el);
1285
1286 if (!elLastChild || _ghostIsLast(evt, axis, el) && !elLastChild.animated) {
1287 // assign target only if condition is true
1288 if (elLastChild && el === evt.target) {
1289 target = elLastChild;
1290 }
1291
1292 if (target) {
1293 targetRect = _getRect(target);
1294 }
1295
1296 if (isOwner) {
1297 activeSortable._hideClone();
1298 } else {
1299 activeSortable._showClone(this);
1300 }
1301
1302 if (_onMove(rootEl, el, dragEl, dragRect, target, targetRect, evt, !!target) !== false) {
1303 el.appendChild(dragEl);
1304 parentEl = el; // actualization
1305 realDragElRect = null;
1306
1307 changed();
1308 return completed(true);
1309 }
1310 }
1311 else if (target && target !== dragEl && target.parentNode === el) {
1312 var direction = 0,
1313 targetBeforeFirstSwap,
1314 aligned = target.sortableMouseAligned,
1315 differentLevel = dragEl.parentNode !== el,
1316 side1 = axis === 'vertical' ? 'top' : 'left',
1317 scrolledPastTop = _isScrolledPast(target, 'top') || _isScrolledPast(dragEl, 'top'),
1318 scrollBefore = scrolledPastTop ? scrolledPastTop.scrollTop : void 0;
1319
1320
1321 if (lastTarget !== target) {
1322 lastMode = null;
1323 targetBeforeFirstSwap = _getRect(target)[side1];
1324 pastFirstInvertThresh = false;
1325 }
1326
1327 // Reference: https://www.lucidchart.com/documents/view/10fa0e93-e362-4126-aca2-b709ee56bd8b/0
1328 if (
1329 _isElInRowColumn(dragEl, target, axis) && aligned ||
1330 differentLevel ||
1331 scrolledPastTop ||
1332 options.invertSwap ||
1333 lastMode === 'insert' ||
1334 // Needed, in the case that we are inside target and inserted because not aligned... aligned will stay false while inside
1335 // and lastMode will change to 'insert', but we must swap
1336 lastMode === 'swap'
1337 ) {
1338 // New target that we will be inside
1339 if (lastMode !== 'swap') {
1340 isCircumstantialInvert = options.invertSwap || differentLevel;
1341 }
1342
1343 direction = _getSwapDirection(evt, target, axis,
1344 options.swapThreshold, options.invertedSwapThreshold == null ? options.swapThreshold : options.invertedSwapThreshold,
1345 isCircumstantialInvert,
1346 lastTarget === target);
1347 lastMode = 'swap';
1348 } else {
1349 // Insert at position
1350 direction = _getInsertDirection(target);
1351 lastMode = 'insert';
1352 }
1353 if (direction === 0) return completed(false);
1354
1355 realDragElRect = null;
1356 lastTarget = target;
1357
1358 lastDirection = direction;
1359
1360 targetRect = _getRect(target);
1361
1362 var nextSibling = target.nextElementSibling,
1363 after = false;
1364
1365 after = direction === 1;
1366
1367 var moveVector = _onMove(rootEl, el, dragEl, dragRect, target, targetRect, evt, after);
1368
1369 if (moveVector !== false) {
1370 if (moveVector === 1 || moveVector === -1) {
1371 after = (moveVector === 1);
1372 }
1373
1374 _silent = true;
1375 setTimeout(_unsilent, 30);
1376
1377 if (isOwner) {
1378 activeSortable._hideClone();
1379 } else {
1380 activeSortable._showClone(this);
1381 }
1382
1383 if (after && !nextSibling) {
1384 el.appendChild(dragEl);
1385 } else {
1386 target.parentNode.insertBefore(dragEl, after ? nextSibling : target);
1387 }
1388
1389 // Undo chrome's scroll adjustment
1390 if (scrolledPastTop) {
1391 _scrollBy(scrolledPastTop, 0, scrollBefore - scrolledPastTop.scrollTop);
1392 }
1393
1394 parentEl = dragEl.parentNode; // actualization
1395
1396 // must be done before animation
1397 if (targetBeforeFirstSwap !== undefined && !isCircumstantialInvert) {
1398 targetMoveDistance = abs(targetBeforeFirstSwap - _getRect(target)[side1]);
1399 }
1400 changed();
1401
1402 return completed(true);
1403 }
1404 }
1405
1406 if (el.contains(dragEl)) {
1407 return completed(false);
1408 }
1409 }
1410
1411 if (IE11OrLess && !evt.rootEl) {
1412 _artificalBubble(el, evt, '_onDragOver');
1413 }
1414
1415 return false;
1416 },
1417
1418 _animate: function (prevRect, target) {
1419 var ms = this.options.animation;
1420
1421 if (ms) {
1422 var currentRect = _getRect(target);
1423
1424 if (target === dragEl) {
1425 realDragElRect = currentRect;
1426 }
1427
1428 if (prevRect.nodeType === 1) {
1429 prevRect = _getRect(prevRect);
1430 }
1431
1432 // Check if actually moving position
1433 if ((prevRect.left + prevRect.width / 2) !== (currentRect.left + currentRect.width / 2)
1434 || (prevRect.top + prevRect.height / 2) !== (currentRect.top + currentRect.height / 2)
1435 ) {
1436 var matrix = _matrix(this.el),
1437 scaleX = matrix && matrix.a,
1438 scaleY = matrix && matrix.d;
1439
1440 _css(target, 'transition', 'none');
1441 _css(target, 'transform', 'translate3d('
1442 + (prevRect.left - currentRect.left) / (scaleX ? scaleX : 1) + 'px,'
1443 + (prevRect.top - currentRect.top) / (scaleY ? scaleY : 1) + 'px,0)'
1444 );
1445
1446 forRepaintDummy = target.offsetWidth; // repaint
1447 _css(target, 'transition', 'transform ' + ms + 'ms' + (this.options.easing ? ' ' + this.options.easing : ''));
1448 _css(target, 'transform', 'translate3d(0,0,0)');
1449 }
1450
1451 (typeof target.animated === 'number') && clearTimeout(target.animated);
1452 target.animated = setTimeout(function () {
1453 _css(target, 'transition', '');
1454 _css(target, 'transform', '');
1455 target.animated = false;
1456 }, ms);
1457 }
1458 },
1459
1460 _offUpEvents: function () {
1461 var ownerDocument = this.el.ownerDocument;
1462
1463 _off(document, 'touchmove', this._onTouchMove);
1464 _off(document, 'pointermove', this._onTouchMove);
1465 _off(ownerDocument, 'mouseup', this._onDrop);
1466 _off(ownerDocument, 'touchend', this._onDrop);
1467 _off(ownerDocument, 'pointerup', this._onDrop);
1468 _off(ownerDocument, 'touchcancel', this._onDrop);
1469 _off(document, 'selectstart', this);
1470 },
1471
1472 _onDrop: function (/**Event*/evt) {
1473 var el = this.el,
1474 options = this.options;
1475 awaitingDragStarted = false;
1476 scrolling = false;
1477 isCircumstantialInvert = false;
1478 pastFirstInvertThresh = false;
1479
1480 clearInterval(this._loopId);
1481
1482 clearInterval(pointerElemChangedInterval);
1483 _clearAutoScrolls();
1484 _cancelThrottle();
1485
1486 clearTimeout(this._dragStartTimer);
1487
1488 _cancelNextTick(this._cloneId);
1489 _cancelNextTick(this._dragStartId);
1490
1491 // Unbind events
1492 _off(document, 'mousemove', this._onTouchMove);
1493
1494
1495 if (this.nativeDraggable) {
1496 _off(document, 'drop', this);
1497 _off(el, 'dragstart', this._onDragStart);
1498 _off(document, 'dragover', this._handleAutoScroll);
1499 _off(document, 'dragover', _checkAlignment);
1500 }
1501
1502 if (Safari) {
1503 _css(document.body, 'user-select', '');
1504 }
1505
1506 this._offUpEvents();
1507
1508 if (evt) {
1509 if (moved) {
1510 evt.cancelable && evt.preventDefault();
1511 !options.dropBubble && evt.stopPropagation();
1512 }
1513
1514 ghostEl && ghostEl.parentNode && ghostEl.parentNode.removeChild(ghostEl);
1515
1516 if (rootEl === parentEl || (putSortable && putSortable.lastPutMode !== 'clone')) {
1517 // Remove clone
1518 cloneEl && cloneEl.parentNode && cloneEl.parentNode.removeChild(cloneEl);
1519 }
1520
1521 if (dragEl) {
1522 if (this.nativeDraggable) {
1523 _off(dragEl, 'dragend', this);
1524 }
1525
1526 _disableDraggable(dragEl);
1527 dragEl.style['will-change'] = '';
1528
1529 // Remove class's
1530 _toggleClass(dragEl, putSortable ? putSortable.options.ghostClass : this.options.ghostClass, false);
1531 _toggleClass(dragEl, this.options.chosenClass, false);
1532
1533 // Drag stop event
1534 _dispatchEvent(this, rootEl, 'unchoose', dragEl, parentEl, rootEl, oldIndex, null, evt);
1535
1536 if (rootEl !== parentEl) {
1537 newIndex = _index(dragEl, options.draggable);
1538
1539 if (newIndex >= 0) {
1540 // Add event
1541 _dispatchEvent(null, parentEl, 'add', dragEl, parentEl, rootEl, oldIndex, newIndex, evt);
1542
1543 // Remove event
1544 _dispatchEvent(this, rootEl, 'remove', dragEl, parentEl, rootEl, oldIndex, newIndex, evt);
1545
1546 // drag from one list and drop into another
1547 _dispatchEvent(null, parentEl, 'sort', dragEl, parentEl, rootEl, oldIndex, newIndex, evt);
1548 _dispatchEvent(this, rootEl, 'sort', dragEl, parentEl, rootEl, oldIndex, newIndex, evt);
1549 }
1550
1551 putSortable && putSortable.save();
1552 }
1553 else {
1554 if (dragEl.nextSibling !== nextEl) {
1555 // Get the index of the dragged element within its parent
1556 newIndex = _index(dragEl, options.draggable);
1557
1558 if (newIndex >= 0) {
1559 // drag & drop within the same list
1560 _dispatchEvent(this, rootEl, 'update', dragEl, parentEl, rootEl, oldIndex, newIndex, evt);
1561 _dispatchEvent(this, rootEl, 'sort', dragEl, parentEl, rootEl, oldIndex, newIndex, evt);
1562 }
1563 }
1564 }
1565
1566 if (Sortable.active) {
1567 /* jshint eqnull:true */
1568 if (newIndex == null || newIndex === -1) {
1569 newIndex = oldIndex;
1570 }
1571 _dispatchEvent(this, rootEl, 'end', dragEl, parentEl, rootEl, oldIndex, newIndex, evt);
1572
1573 // Save sorting
1574 this.save();
1575 }
1576 }
1577
1578 }
1579 this._nulling();
1580 },
1581
1582 _nulling: function() {
1583 rootEl =
1584 dragEl =
1585 parentEl =
1586 ghostEl =
1587 nextEl =
1588 cloneEl =
1589 lastDownEl =
1590
1591 scrollEl =
1592 scrollParentEl =
1593 autoScrolls.length =
1594
1595 pointerElemChangedInterval =
1596 lastPointerElemX =
1597 lastPointerElemY =
1598
1599 tapEvt =
1600 touchEvt =
1601
1602 moved =
1603 newIndex =
1604 oldIndex =
1605
1606 lastTarget =
1607 lastDirection =
1608
1609 forRepaintDummy =
1610 realDragElRect =
1611
1612 putSortable =
1613 activeGroup =
1614 Sortable.active = null;
1615
1616 savedInputChecked.forEach(function (el) {
1617 el.checked = true;
1618 });
1619
1620 savedInputChecked.length = 0;
1621 },
1622
1623 handleEvent: function (/**Event*/evt) {
1624 switch (evt.type) {
1625 case 'drop':
1626 case 'dragend':
1627 this._onDrop(evt);
1628 break;
1629
1630 case 'dragenter':
1631 case 'dragover':
1632 if (dragEl) {
1633 this._onDragOver(evt);
1634 _globalDragOver(evt);
1635 }
1636 break;
1637
1638 case 'selectstart':
1639 evt.preventDefault();
1640 break;
1641 }
1642 },
1643
1644
1645 /**
1646 * Serializes the item into an array of string.
1647 * @returns {String[]}
1648 */
1649 toArray: function () {
1650 var order = [],
1651 el,
1652 children = this.el.children,
1653 i = 0,
1654 n = children.length,
1655 options = this.options;
1656
1657 for (; i < n; i++) {
1658 el = children[i];
1659 if (_closest(el, options.draggable, this.el, false)) {
1660 order.push(el.getAttribute(options.dataIdAttr) || _generateId(el));
1661 }
1662 }
1663
1664 return order;
1665 },
1666
1667
1668 /**
1669 * Sorts the elements according to the array.
1670 * @param {String[]} order order of the items
1671 */
1672 sort: function (order) {
1673 var items = {}, rootEl = this.el;
1674
1675 this.toArray().forEach(function (id, i) {
1676 var el = rootEl.children[i];
1677
1678 if (_closest(el, this.options.draggable, rootEl, false)) {
1679 items[id] = el;
1680 }
1681 }, this);
1682
1683 order.forEach(function (id) {
1684 if (items[id]) {
1685 rootEl.removeChild(items[id]);
1686 rootEl.appendChild(items[id]);
1687 }
1688 });
1689 },
1690
1691
1692 /**
1693 * Save the current sorting
1694 */
1695 save: function () {
1696 var store = this.options.store;
1697 store && store.set && store.set(this);
1698 },
1699
1700
1701 /**
1702 * 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.
1703 * @param {HTMLElement} el
1704 * @param {String} [selector] default: `options.draggable`
1705 * @returns {HTMLElement|null}
1706 */
1707 closest: function (el, selector) {
1708 return _closest(el, selector || this.options.draggable, this.el, false);
1709 },
1710
1711
1712 /**
1713 * Set/get option
1714 * @param {string} name
1715 * @param {*} [value]
1716 * @returns {*}
1717 */
1718 option: function (name, value) {
1719 var options = this.options;
1720
1721 if (value === void 0) {
1722 return options[name];
1723 } else {
1724 options[name] = value;
1725
1726 if (name === 'group') {
1727 _prepareGroup(options);
1728 }
1729 }
1730 },
1731
1732
1733 /**
1734 * Destroy
1735 */
1736 destroy: function () {
1737 var el = this.el;
1738
1739 el[expando] = null;
1740
1741 _off(el, 'mousedown', this._onTapStart);
1742 _off(el, 'touchstart', this._onTapStart);
1743 _off(el, 'pointerdown', this._onTapStart);
1744
1745 if (this.nativeDraggable) {
1746 _off(el, 'dragover', this);
1747 _off(el, 'dragenter', this);
1748 }
1749 // Remove draggable attributes
1750 Array.prototype.forEach.call(el.querySelectorAll('[draggable]'), function (el) {
1751 el.removeAttribute('draggable');
1752 });
1753
1754 this._onDrop();
1755
1756 sortables.splice(sortables.indexOf(this.el), 1);
1757
1758 this.el = el = null;
1759 },
1760
1761 _hideClone: function() {
1762 if (!cloneEl.cloneHidden) {
1763 _css(cloneEl, 'display', 'none');
1764 cloneEl.cloneHidden = true;
1765 if (cloneEl.parentNode && this.options.removeCloneOnHide) {
1766 cloneEl.parentNode.removeChild(cloneEl);
1767 }
1768 }
1769 },
1770
1771 _showClone: function(putSortable) {
1772 if (putSortable.lastPutMode !== 'clone') {
1773 this._hideClone();
1774 return;
1775 }
1776
1777 if (cloneEl.cloneHidden) {
1778 // show clone at dragEl or original position
1779 if (rootEl.contains(dragEl) && !this.options.group.revertClone) {
1780 rootEl.insertBefore(cloneEl, dragEl);
1781 } else if (nextEl) {
1782 rootEl.insertBefore(cloneEl, nextEl);
1783 } else {
1784 rootEl.appendChild(cloneEl);
1785 }
1786
1787 if (this.options.group.revertClone) {
1788 this._animate(dragEl, cloneEl);
1789 }
1790 _css(cloneEl, 'display', '');
1791 cloneEl.cloneHidden = false;
1792 }
1793 }
1794 };
1795
1796 function _closest(/**HTMLElement*/el, /**String*/selector, /**HTMLElement*/ctx, includeCTX) {
1797 if (el) {
1798 ctx = ctx || document;
1799
1800 do {
1801 if (
1802 selector != null &&
1803 (
1804 selector[0] === '>' && el.parentNode === ctx && _matches(el, selector.substring(1)) ||
1805 _matches(el, selector)
1806 ) ||
1807 includeCTX && el === ctx
1808 ) {
1809 return el;
1810 }
1811
1812 if (el === ctx) break;
1813 /* jshint boss:true */
1814 } while (el = _getParentOrHost(el));
1815 }
1816
1817 return null;
1818 }
1819
1820
1821 function _getParentOrHost(el) {
1822 return (el.host && el !== document && el.host.nodeType)
1823 ? el.host
1824 : el.parentNode;
1825 }
1826
1827
1828 function _globalDragOver(/**Event*/evt) {
1829 if (evt.dataTransfer) {
1830 evt.dataTransfer.dropEffect = 'move';
1831 }
1832 evt.cancelable && evt.preventDefault();
1833 }
1834
1835
1836 function _on(el, event, fn) {
1837 el.addEventListener(event, fn, captureMode);
1838 }
1839
1840
1841 function _off(el, event, fn) {
1842 el.removeEventListener(event, fn, captureMode);
1843 }
1844
1845
1846 function _toggleClass(el, name, state) {
1847 if (el && name) {
1848 if (el.classList) {
1849 el.classList[state ? 'add' : 'remove'](name);
1850 }
1851 else {
1852 var className = (' ' + el.className + ' ').replace(R_SPACE, ' ').replace(' ' + name + ' ', ' ');
1853 el.className = (className + (state ? ' ' + name : '')).replace(R_SPACE, ' ');
1854 }
1855 }
1856 }
1857
1858
1859 function _css(el, prop, val) {
1860 var style = el && el.style;
1861
1862 if (style) {
1863 if (val === void 0) {
1864 if (document.defaultView && document.defaultView.getComputedStyle) {
1865 val = document.defaultView.getComputedStyle(el, '');
1866 }
1867 else if (el.currentStyle) {
1868 val = el.currentStyle;
1869 }
1870
1871 return prop === void 0 ? val : val[prop];
1872 }
1873 else {
1874 if (!(prop in style) && prop.indexOf('webkit') === -1) {
1875 prop = '-webkit-' + prop;
1876 }
1877
1878 style[prop] = val + (typeof val === 'string' ? '' : 'px');
1879 }
1880 }
1881 }
1882
1883 function _matrix(el) {
1884 var appliedTransforms = '';
1885 do {
1886 var transform = _css(el, 'transform');
1887
1888 if (transform && transform !== 'none') {
1889 appliedTransforms = transform + ' ' + appliedTransforms;
1890 }
1891 /* jshint boss:true */
1892 } while (el = el.parentNode);
1893
1894 if (window.DOMMatrix) {
1895 return new DOMMatrix(appliedTransforms);
1896 } else if (window.WebKitCSSMatrix) {
1897 return new WebKitCSSMatrix(appliedTransforms);
1898 } else if (window.CSSMatrix) {
1899 return new CSSMatrix(appliedTransforms);
1900 }
1901 }
1902
1903
1904 function _find(ctx, tagName, iterator) {
1905 if (ctx) {
1906 var list = ctx.getElementsByTagName(tagName), i = 0, n = list.length;
1907
1908 if (iterator) {
1909 for (; i < n; i++) {
1910 iterator(list[i], i);
1911 }
1912 }
1913
1914 return list;
1915 }
1916
1917 return [];
1918 }
1919
1920
1921
1922 function _dispatchEvent(sortable, rootEl, name, targetEl, toEl, fromEl, startIndex, newIndex, originalEvt) {
1923 sortable = (sortable || rootEl[expando]);
1924 var evt,
1925 options = sortable.options,
1926 onName = 'on' + name.charAt(0).toUpperCase() + name.substr(1);
1927 // Support for new CustomEvent feature
1928 if (window.CustomEvent && !IE11OrLess && !Edge) {
1929 evt = new CustomEvent(name, {
1930 bubbles: true,
1931 cancelable: true
1932 });
1933 } else {
1934 evt = document.createEvent('Event');
1935 evt.initEvent(name, true, true);
1936 }
1937
1938 evt.to = toEl || rootEl;
1939 evt.from = fromEl || rootEl;
1940 evt.item = targetEl || rootEl;
1941 evt.clone = cloneEl;
1942
1943 evt.oldIndex = startIndex;
1944 evt.newIndex = newIndex;
1945
1946 evt.originalEvent = originalEvt;
1947 evt.pullMode = putSortable ? putSortable.lastPutMode : undefined;
1948
1949 if (rootEl) {
1950 rootEl.dispatchEvent(evt);
1951 }
1952
1953 if (options[onName]) {
1954 options[onName].call(sortable, evt);
1955 }
1956 }
1957
1958
1959 function _onMove(fromEl, toEl, dragEl, dragRect, targetEl, targetRect, originalEvt, willInsertAfter) {
1960 var evt,
1961 sortable = fromEl[expando],
1962 onMoveFn = sortable.options.onMove,
1963 retVal;
1964 // Support for new CustomEvent feature
1965 if (window.CustomEvent && !IE11OrLess && !Edge) {
1966 evt = new CustomEvent('move', {
1967 bubbles: true,
1968 cancelable: true
1969 });
1970 } else {
1971 evt = document.createEvent('Event');
1972 evt.initEvent('move', true, true);
1973 }
1974
1975 evt.to = toEl;
1976 evt.from = fromEl;
1977 evt.dragged = dragEl;
1978 evt.draggedRect = dragRect;
1979 evt.related = targetEl || toEl;
1980 evt.relatedRect = targetRect || _getRect(toEl);
1981 evt.willInsertAfter = willInsertAfter;
1982
1983 evt.originalEvent = originalEvt;
1984
1985 fromEl.dispatchEvent(evt);
1986
1987 if (onMoveFn) {
1988 retVal = onMoveFn.call(sortable, evt, originalEvt);
1989 }
1990
1991 return retVal;
1992 }
1993
1994 function _disableDraggable(el) {
1995 el.draggable = false;
1996 }
1997
1998 function _unsilent() {
1999 _silent = false;
2000 }
2001
2002 /**
2003 * Gets nth child of el, ignoring hidden children, sortable's elements (does not ignore clone if it's visible)
2004 * and non-draggable elements
2005 * @param {HTMLElement} el The parent element
2006 * @param {Number} childNum The index of the child
2007 * @param {Object} options Parent Sortable's options
2008 * @return {HTMLElement} The child at index childNum, or null if not found
2009 */
2010 function _getChild(el, childNum, options) {
2011 var currentChild = 0,
2012 i = 0,
2013 children = el.children;
2014
2015 while (i < children.length) {
2016 if (
2017 children[i].style.display !== 'none' &&
2018 children[i] !== ghostEl &&
2019 children[i] !== dragEl &&
2020 _closest(children[i], options.draggable, el, false)
2021 ) {
2022 if (currentChild === childNum) {
2023 return children[i];
2024 }
2025 currentChild++;
2026 }
2027
2028 i++;
2029 }
2030 return null;
2031 }
2032
2033 /**
2034 * Gets the last child in the el, ignoring ghostEl or invisible elements (clones)
2035 * @param {HTMLElement} el Parent element
2036 * @return {HTMLElement} The last child, ignoring ghostEl
2037 */
2038 function _lastChild(el) {
2039 var last = el.lastElementChild;
2040
2041 while (last && (last === ghostEl || last.style.display === 'none')) {
2042 last = last.previousElementSibling;
2043 }
2044
2045 return last || null;
2046 }
2047
2048 function _ghostIsLast(evt, axis, el) {
2049 var elRect = _getRect(_lastChild(el)),
2050 mouseOnAxis = axis === 'vertical' ? evt.clientY : evt.clientX,
2051 mouseOnOppAxis = axis === 'vertical' ? evt.clientX : evt.clientY,
2052 targetS2 = axis === 'vertical' ? elRect.bottom : elRect.right,
2053 targetS1Opp = axis === 'vertical' ? elRect.left : elRect.top,
2054 targetS2Opp = axis === 'vertical' ? elRect.right : elRect.bottom,
2055 spacer = 10;
2056
2057 return (
2058 axis === 'vertical' ?
2059 (mouseOnOppAxis > targetS2Opp + spacer || mouseOnOppAxis <= targetS2Opp && mouseOnAxis > targetS2 && mouseOnOppAxis >= targetS1Opp) :
2060 (mouseOnAxis > targetS2 && mouseOnOppAxis > targetS1Opp || mouseOnAxis <= targetS2 && mouseOnOppAxis > targetS2Opp + spacer)
2061 );
2062 }
2063
2064 function _getSwapDirection(evt, target, axis, swapThreshold, invertedSwapThreshold, invertSwap, isLastTarget) {
2065 var targetRect = _getRect(target),
2066 mouseOnAxis = axis === 'vertical' ? evt.clientY : evt.clientX,
2067 targetLength = axis === 'vertical' ? targetRect.height : targetRect.width,
2068 targetS1 = axis === 'vertical' ? targetRect.top : targetRect.left,
2069 targetS2 = axis === 'vertical' ? targetRect.bottom : targetRect.right,
2070 dragRect = _getRect(dragEl),
2071 invert = false;
2072
2073
2074 if (!invertSwap) {
2075 // Never invert or create dragEl shadow when target movemenet causes mouse to move past the end of regular swapThreshold
2076 if (isLastTarget && targetMoveDistance < targetLength * swapThreshold) { // multiplied only by swapThreshold because mouse will already be inside target by (1 - threshold) * targetLength / 2
2077 // check if past first invert threshold on side opposite of lastDirection
2078 if (!pastFirstInvertThresh &&
2079 (lastDirection === 1 ?
2080 (
2081 mouseOnAxis > targetS1 + targetLength * invertedSwapThreshold / 2
2082 ) :
2083 (
2084 mouseOnAxis < targetS2 - targetLength * invertedSwapThreshold / 2
2085 )
2086 )
2087 )
2088 {
2089 // past first invert threshold, do not restrict inverted threshold to dragEl shadow
2090 pastFirstInvertThresh = true;
2091 }
2092
2093 if (!pastFirstInvertThresh) {
2094 var dragS1 = axis === 'vertical' ? dragRect.top : dragRect.left,
2095 dragS2 = axis === 'vertical' ? dragRect.bottom : dragRect.right;
2096 // dragEl shadow (target move distance shadow)
2097 if (
2098 lastDirection === 1 ?
2099 (
2100 mouseOnAxis < targetS1 + targetMoveDistance // over dragEl shadow
2101 ) :
2102 (
2103 mouseOnAxis > targetS2 - targetMoveDistance
2104 )
2105 )
2106 {
2107 return lastDirection * -1;
2108 }
2109 } else {
2110 invert = true;
2111 }
2112 } else {
2113 // Regular
2114 if (
2115 mouseOnAxis > targetS1 + (targetLength * (1 - swapThreshold) / 2) &&
2116 mouseOnAxis < targetS2 - (targetLength * (1 - swapThreshold) / 2)
2117 ) {
2118 return _getInsertDirection(target);
2119 }
2120 }
2121 }
2122
2123 invert = invert || invertSwap;
2124
2125 if (invert) {
2126 // Invert of regular
2127 if (
2128 mouseOnAxis < targetS1 + (targetLength * invertedSwapThreshold / 2) ||
2129 mouseOnAxis > targetS2 - (targetLength * invertedSwapThreshold / 2)
2130 )
2131 {
2132 return ((mouseOnAxis > targetS1 + targetLength / 2) ? 1 : -1);
2133 }
2134 }
2135
2136 return 0;
2137 }
2138
2139 /**
2140 * Gets the direction dragEl must be swapped relative to target in order to make it
2141 * seem that dragEl has been "inserted" into that element's position
2142 * @param {HTMLElement} target The target whose position dragEl is being inserted at
2143 * @return {Number} Direction dragEl must be swapped
2144 */
2145 function _getInsertDirection(target) {
2146 var dragElIndex = _index(dragEl),
2147 targetIndex = _index(target);
2148
2149 if (dragElIndex < targetIndex) {
2150 return 1;
2151 } else {
2152 return -1;
2153 }
2154 }
2155
2156
2157 /**
2158 * Generate id
2159 * @param {HTMLElement} el
2160 * @returns {String}
2161 * @private
2162 */
2163 function _generateId(el) {
2164 var str = el.tagName + el.className + el.src + el.href + el.textContent,
2165 i = str.length,
2166 sum = 0;
2167
2168 while (i--) {
2169 sum += str.charCodeAt(i);
2170 }
2171
2172 return sum.toString(36);
2173 }
2174
2175 /**
2176 * Returns the index of an element within its parent for a selected set of
2177 * elements
2178 * @param {HTMLElement} el
2179 * @param {selector} selector
2180 * @return {number}
2181 */
2182 function _index(el, selector) {
2183 var index = 0;
2184
2185 if (!el || !el.parentNode) {
2186 return -1;
2187 }
2188
2189 while (el && (el = el.previousElementSibling)) {
2190 if ((el.nodeName.toUpperCase() !== 'TEMPLATE') && el !== cloneEl) {
2191 index++;
2192 }
2193 }
2194
2195 return index;
2196 }
2197
2198 function _matches(/**HTMLElement*/el, /**String*/selector) {
2199 if (el) {
2200 try {
2201 if (el.matches) {
2202 return el.matches(selector);
2203 } else if (el.msMatchesSelector) {
2204 return el.msMatchesSelector(selector);
2205 } else if (el.webkitMatchesSelector) {
2206 return el.webkitMatchesSelector(selector);
2207 }
2208 } catch(_) {
2209 return false;
2210 }
2211 }
2212
2213 return false;
2214 }
2215
2216 var _throttleTimeout;
2217 function _throttle(callback, ms) {
2218 return function () {
2219 if (!_throttleTimeout) {
2220 var args = arguments,
2221 _this = this;
2222
2223 _throttleTimeout = setTimeout(function () {
2224 if (args.length === 1) {
2225 callback.call(_this, args[0]);
2226 } else {
2227 callback.apply(_this, args);
2228 }
2229
2230 _throttleTimeout = void 0;
2231 }, ms);
2232 }
2233 };
2234 }
2235
2236 function _cancelThrottle() {
2237 clearTimeout(_throttleTimeout);
2238 _throttleTimeout = void 0;
2239 }
2240
2241 function _extend(dst, src) {
2242 if (dst && src) {
2243 for (var key in src) {
2244 if (src.hasOwnProperty(key)) {
2245 dst[key] = src[key];
2246 }
2247 }
2248 }
2249
2250 return dst;
2251 }
2252
2253 function _clone(el) {
2254 if (Polymer && Polymer.dom) {
2255 return Polymer.dom(el).cloneNode(true);
2256 }
2257 else if ($) {
2258 return $(el).clone(true)[0];
2259 }
2260 else {
2261 return el.cloneNode(true);
2262 }
2263 }
2264
2265 function _saveInputCheckedState(root) {
2266 savedInputChecked.length = 0;
2267
2268 var inputs = root.getElementsByTagName('input');
2269 var idx = inputs.length;
2270
2271 while (idx--) {
2272 var el = inputs[idx];
2273 el.checked && savedInputChecked.push(el);
2274 }
2275 }
2276
2277 function _nextTick(fn) {
2278 return setTimeout(fn, 0);
2279 }
2280
2281 function _cancelNextTick(id) {
2282 return clearTimeout(id);
2283 }
2284
2285
2286 /**
2287 * Returns the "bounding client rect" of given element
2288 * @param {HTMLElement} el The element whose boundingClientRect is wanted
2289 * @param {[HTMLElement]} container the parent the element will be placed in
2290 * @param {[Boolean]} adjustForTransform Whether the rect should compensate for parent's transform
2291 * @return {Object} The boundingClientRect of el
2292 */
2293 function _getRect(el, adjustForTransform, container, adjustForFixed) {
2294 if (!el.getBoundingClientRect && el !== win) return;
2295
2296 var elRect,
2297 top,
2298 left,
2299 bottom,
2300 right,
2301 height,
2302 width;
2303
2304 if (el !== win && el !== _getWindowScrollingElement()) {
2305 elRect = el.getBoundingClientRect();
2306 top = elRect.top;
2307 left = elRect.left;
2308 bottom = elRect.bottom;
2309 right = elRect.right;
2310 height = elRect.height;
2311 width = elRect.width;
2312 } else {
2313 top = 0;
2314 left = 0;
2315 bottom = window.innerHeight;
2316 right = window.innerWidth;
2317 height = window.innerHeight;
2318 width = window.innerWidth;
2319 }
2320
2321 if (adjustForFixed && el !== win) {
2322 // Adjust for translate()
2323 container = container || el.parentNode;
2324
2325 // solves #1123 (see: https://stackoverflow.com/a/37953806/6088312)
2326 // Not needed on <= IE11
2327 if (!IE11OrLess) {
2328 do {
2329 if (container && container.getBoundingClientRect && _css(container, 'transform') !== 'none') {
2330 var containerRect = container.getBoundingClientRect();
2331
2332 // Set relative to edges of padding box of container
2333 top -= containerRect.top + parseInt(_css(container, 'border-top-width'));
2334 left -= containerRect.left + parseInt(_css(container, 'border-left-width'));
2335 bottom = top + elRect.height;
2336 right = left + elRect.width;
2337
2338 break;
2339 }
2340 /* jshint boss:true */
2341 } while (container = container.parentNode);
2342 }
2343 }
2344
2345 if (adjustForTransform && el !== win) {
2346 // Adjust for scale()
2347 var matrix = _matrix(container || el),
2348 scaleX = matrix && matrix.a,
2349 scaleY = matrix && matrix.d;
2350
2351 if (matrix) {
2352 top /= scaleY;
2353 left /= scaleX;
2354
2355 width /= scaleX;
2356 height /= scaleY;
2357
2358 bottom = top + height;
2359 right = left + width;
2360 }
2361 }
2362
2363 return {
2364 top: top,
2365 left: left,
2366 bottom: bottom,
2367 right: right,
2368 width: width,
2369 height: height
2370 };
2371 }
2372
2373
2374 /**
2375 * Checks if a side of an element is scrolled past a side of it's parents
2376 * @param {HTMLElement} el The element who's side being scrolled out of view is in question
2377 * @param {String} side Side of the element in question ('top', 'left', 'right', 'bottom')
2378 * @return {HTMLElement} The parent scroll element that the el's side is scrolled past, or null if there is no such element
2379 */
2380 function _isScrolledPast(el, side) {
2381 var parent = _getParentAutoScrollElement(el, true),
2382 elSide = _getRect(el)[side];
2383
2384 /* jshint boss:true */
2385 while (parent) {
2386 var parentSide = _getRect(parent)[side],
2387 visible;
2388
2389 if (side === 'top' || side === 'left') {
2390 visible = elSide >= parentSide;
2391 } else {
2392 visible = elSide <= parentSide;
2393 }
2394
2395 if (!visible) return parent;
2396
2397 if (parent === _getWindowScrollingElement()) break;
2398
2399 parent = _getParentAutoScrollElement(parent, false);
2400 }
2401
2402 return false;
2403 }
2404
2405 /**
2406 * Returns the scroll offset of the given element, added with all the scroll offsets of parent elements.
2407 * The value is returned in real pixels.
2408 * @param {HTMLElement} el
2409 * @return {Array} Offsets in the format of [left, top]
2410 */
2411 function _getRelativeScrollOffset(el) {
2412 var offsetLeft = 0,
2413 offsetTop = 0,
2414 winScroller = _getWindowScrollingElement();
2415
2416 if (el) {
2417 do {
2418 var matrix = _matrix(el),
2419 scaleX = matrix.a,
2420 scaleY = matrix.d;
2421
2422 offsetLeft += el.scrollLeft * scaleX;
2423 offsetTop += el.scrollTop * scaleY;
2424 } while (el !== winScroller && (el = el.parentNode));
2425 }
2426
2427 return [offsetLeft, offsetTop];
2428 }
2429
2430 // Fixed #973:
2431 _on(document, 'touchmove', function(evt) {
2432 if ((Sortable.active || awaitingDragStarted) && evt.cancelable) {
2433 evt.preventDefault();
2434 }
2435 });
2436
2437
2438 // Export utils
2439 Sortable.utils = {
2440 on: _on,
2441 off: _off,
2442 css: _css,
2443 find: _find,
2444 is: function (el, selector) {
2445 return !!_closest(el, selector, el, false);
2446 },
2447 extend: _extend,
2448 throttle: _throttle,
2449 closest: _closest,
2450 toggleClass: _toggleClass,
2451 clone: _clone,
2452 index: _index,
2453 nextTick: _nextTick,
2454 cancelNextTick: _cancelNextTick,
2455 detectDirection: _detectDirection,
2456 getChild: _getChild
2457 };
2458
2459
2460 /**
2461 * Create sortable instance
2462 * @param {HTMLElement} el
2463 * @param {Object} [options]
2464 */
2465 Sortable.create = function (el, options) {
2466 return new Sortable(el, options);
2467 };
2468
2469
2470 // Export
2471 Sortable.version = '1.8.4';
2472 return Sortable;
2473 });
2474