PluginProbe
Booking Calendar / 11.8.2
Booking Calendar v11.8.2
11.8.3 11.8.2 11.8.1 11.8 11.7 11.6.1 11.6 11.5 11.4.3 11.4.2 11.4.1 11.4 11.3 11.2.1 11.2 11.1 11.0 10.15.7 10.15.6 10.1.3 10.10 10.10.1 10.10.2 10.11 10.11.2 All 203 releases
booking / vendors / sortablejs / src / Sortable.js

Sortable.js in Booking Calendar 11.8.2, at vendors/sortablejs/src/Sortable.js

2,029 lines 52.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 import { version } from '../package.json';
9
10 import { IE11OrLess, Edge, FireFox, Safari, IOS, ChromeForAndroid } from './BrowserInfo.js';
11
12 import AnimationStateManager from './Animation.js';
13
14 import PluginManager from './PluginManager.js';
15
16 import dispatchEvent from './EventDispatcher.js';
17
18 import {
19 on,
20 off,
21 closest,
22 toggleClass,
23 css,
24 matrix,
25 find,
26 getWindowScrollingElement,
27 getRect,
28 isScrolledPast,
29 getChild,
30 lastChild,
31 index,
32 getRelativeScrollOffset,
33 extend,
34 throttle,
35 scrollBy,
36 clone,
37 expando,
38 getChildContainingRectFromElement,
39 getParentOrHost
40 } from './utils.js';
41
42
43 let pluginEvent = function(eventName, sortable, { evt: originalEvent, ...data } = {}) {
44 PluginManager.pluginEvent.bind(Sortable)(eventName, sortable, {
45 dragEl,
46 parentEl,
47 ghostEl,
48 rootEl,
49 nextEl,
50 lastDownEl,
51 cloneEl,
52 cloneHidden,
53 dragStarted: moved,
54 putSortable,
55 activeSortable: Sortable.active,
56 originalEvent,
57
58 oldIndex,
59 oldDraggableIndex,
60 newIndex,
61 newDraggableIndex,
62
63 hideGhostForTarget: _hideGhostForTarget,
64 unhideGhostForTarget: _unhideGhostForTarget,
65
66
67 cloneNowHidden() {
68 cloneHidden = true;
69 },
70 cloneNowShown() {
71 cloneHidden = false;
72 },
73
74 dispatchSortableEvent(name) {
75 _dispatchEvent({ sortable, name, originalEvent });
76 },
77
78 ...data
79 });
80 };
81
82 function _dispatchEvent(info) {
83 dispatchEvent({
84 putSortable,
85 cloneEl,
86 targetEl: dragEl,
87 rootEl,
88 oldIndex,
89 oldDraggableIndex,
90 newIndex,
91 newDraggableIndex,
92 ...info
93 });
94 }
95
96
97 let dragEl,
98 parentEl,
99 ghostEl,
100 rootEl,
101 nextEl,
102 lastDownEl,
103
104 cloneEl,
105 cloneHidden,
106
107 oldIndex,
108 newIndex,
109 oldDraggableIndex,
110 newDraggableIndex,
111
112 activeGroup,
113 putSortable,
114
115 awaitingDragStarted = false,
116 ignoreNextClick = false,
117 sortables = [],
118
119 tapEvt,
120 touchEvt,
121 lastDx,
122 lastDy,
123 tapDistanceLeft,
124 tapDistanceTop,
125
126 moved,
127
128 lastTarget,
129 lastDirection,
130 pastFirstInvertThresh = false,
131 isCircumstantialInvert = false,
132
133 targetMoveDistance,
134
135 // For positioning ghost absolutely
136 ghostRelativeParent,
137 ghostRelativeParentInitialScroll = [], // (left, top)
138
139 _silent = false,
140 savedInputChecked = [];
141
142 /** @const */
143 const documentExists = typeof document !== 'undefined',
144
145 PositionGhostAbsolutely = IOS,
146
147 CSSFloatProperty = Edge || IE11OrLess ? 'cssFloat' : 'float',
148
149 // This will not pass for IE9, because IE9 DnD only works on anchors
150 supportDraggable = documentExists && !ChromeForAndroid && !IOS && ('draggable' in document.createElement('div')),
151
152 supportCssPointerEvents = (function() {
153 if (!documentExists) return;
154 // false when <= IE11
155 if (IE11OrLess) {
156 return false;
157 }
158 let el = document.createElement('x');
159 el.style.cssText = 'pointer-events:auto';
160 return el.style.pointerEvents === 'auto';
161 })(),
162
163 _detectDirection = function(el, options) {
164 let elCSS = css(el),
165 elWidth = parseInt(elCSS.width)
166 - parseInt(elCSS.paddingLeft)
167 - parseInt(elCSS.paddingRight)
168 - parseInt(elCSS.borderLeftWidth)
169 - parseInt(elCSS.borderRightWidth),
170 child1 = getChild(el, 0, options),
171 child2 = getChild(el, 1, options),
172 firstChildCSS = child1 && css(child1),
173 secondChildCSS = child2 && css(child2),
174 firstChildWidth = firstChildCSS && parseInt(firstChildCSS.marginLeft) + parseInt(firstChildCSS.marginRight) + getRect(child1).width,
175 secondChildWidth = secondChildCSS && parseInt(secondChildCSS.marginLeft) + parseInt(secondChildCSS.marginRight) + getRect(child2).width;
176
177 if (elCSS.display === 'flex') {
178 return elCSS.flexDirection === 'column' || elCSS.flexDirection === 'column-reverse'
179 ? 'vertical' : 'horizontal';
180 }
181
182 if (elCSS.display === 'grid') {
183 return elCSS.gridTemplateColumns.split(' ').length <= 1 ? 'vertical' : 'horizontal';
184 }
185
186 if (child1 && firstChildCSS.float && firstChildCSS.float !== 'none') {
187 let touchingSideChild2 = firstChildCSS.float === 'left' ? 'left' : 'right';
188
189 return child2 && (secondChildCSS.clear === 'both' || secondChildCSS.clear === touchingSideChild2) ?
190 'vertical' : 'horizontal';
191 }
192
193 return (child1 &&
194 (
195 firstChildCSS.display === 'block' ||
196 firstChildCSS.display === 'flex' ||
197 firstChildCSS.display === 'table' ||
198 firstChildCSS.display === 'grid' ||
199 firstChildWidth >= elWidth &&
200 elCSS[CSSFloatProperty] === 'none' ||
201 child2 &&
202 elCSS[CSSFloatProperty] === 'none' &&
203 firstChildWidth + secondChildWidth > elWidth
204 ) ?
205 'vertical' : 'horizontal'
206 );
207 },
208
209 _dragElInRowColumn = function(dragRect, targetRect, vertical) {
210 let dragElS1Opp = vertical ? dragRect.left : dragRect.top,
211 dragElS2Opp = vertical ? dragRect.right : dragRect.bottom,
212 dragElOppLength = vertical ? dragRect.width : dragRect.height,
213 targetS1Opp = vertical ? targetRect.left : targetRect.top,
214 targetS2Opp = vertical ? targetRect.right : targetRect.bottom,
215 targetOppLength = vertical ? targetRect.width : targetRect.height;
216
217 return (
218 dragElS1Opp === targetS1Opp ||
219 dragElS2Opp === targetS2Opp ||
220 (dragElS1Opp + dragElOppLength / 2) === (targetS1Opp + targetOppLength / 2)
221 );
222 },
223
224 /**
225 * Detects first nearest empty sortable to X and Y position using emptyInsertThreshold.
226 * @param {Number} x X position
227 * @param {Number} y Y position
228 * @return {HTMLElement} Element of the first found nearest Sortable
229 */
230 _detectNearestEmptySortable = function(x, y) {
231 let ret;
232 sortables.some((sortable) => {
233 const threshold = sortable[expando].options.emptyInsertThreshold;
234 if (!threshold || lastChild(sortable)) return;
235
236 const rect = getRect(sortable),
237 insideHorizontally = x >= (rect.left - threshold) && x <= (rect.right + threshold),
238 insideVertically = y >= (rect.top - threshold) && y <= (rect.bottom + threshold);
239
240 if (insideHorizontally && insideVertically) {
241 return (ret = sortable);
242 }
243 });
244 return ret;
245 },
246
247 _prepareGroup = function (options) {
248 function toFn(value, pull) {
249 return function(to, from, dragEl, evt) {
250 let sameGroup = to.options.group.name &&
251 from.options.group.name &&
252 to.options.group.name === from.options.group.name;
253
254 if (value == null && (pull || sameGroup)) {
255 // Default pull value
256 // Default pull and put value if same group
257 return true;
258 } else if (value == null || value === false) {
259 return false;
260 } else if (pull && value === 'clone') {
261 return value;
262 } else if (typeof value === 'function') {
263 return toFn(value(to, from, dragEl, evt), pull)(to, from, dragEl, evt);
264 } else {
265 let otherGroup = (pull ? to : from).options.group.name;
266
267 return (value === true ||
268 (typeof value === 'string' && value === otherGroup) ||
269 (value.join && value.indexOf(otherGroup) > -1));
270 }
271 };
272 }
273
274 let group = {};
275 let originalGroup = options.group;
276
277 if (!originalGroup || typeof originalGroup != 'object') {
278 originalGroup = {name: originalGroup};
279 }
280
281 group.name = originalGroup.name;
282 group.checkPull = toFn(originalGroup.pull, true);
283 group.checkPut = toFn(originalGroup.put);
284 group.revertClone = originalGroup.revertClone;
285
286 options.group = group;
287 },
288
289 _hideGhostForTarget = function() {
290 if (!supportCssPointerEvents && ghostEl) {
291 css(ghostEl, 'display', 'none');
292 }
293 },
294
295 _unhideGhostForTarget = function() {
296 if (!supportCssPointerEvents && ghostEl) {
297 css(ghostEl, 'display', '');
298 }
299 };
300
301
302 // #1184 fix - Prevent click event on fallback if dragged but item not changed position
303 if (documentExists && !ChromeForAndroid) {
304 document.addEventListener('click', function(evt) {
305 if (ignoreNextClick) {
306 evt.preventDefault();
307 evt.stopPropagation && evt.stopPropagation();
308 evt.stopImmediatePropagation && evt.stopImmediatePropagation();
309 ignoreNextClick = false;
310 return false;
311 }
312 }, true);
313 }
314
315 let nearestEmptyInsertDetectEvent = function(evt) {
316 if (dragEl) {
317 evt = evt.touches ? evt.touches[0] : evt;
318 let nearest = _detectNearestEmptySortable(evt.clientX, evt.clientY);
319
320 if (nearest) {
321 // Create imitation event
322 let event = {};
323 for (let i in evt) {
324 if (evt.hasOwnProperty(i)) {
325 event[i] = evt[i];
326 }
327 }
328 event.target = event.rootEl = nearest;
329 event.preventDefault = void 0;
330 event.stopPropagation = void 0;
331 nearest[expando]._onDragOver(event);
332 }
333 }
334 };
335
336
337 let _checkOutsideTargetEl = function(evt) {
338 if (dragEl) {
339 dragEl.parentNode[expando]._isOutsideThisEl(evt.target);
340 }
341 };
342
343
344 /**
345 * @class Sortable
346 * @param {HTMLElement} el
347 * @param {Object} [options]
348 */
349 function Sortable(el, options) {
350 if (!(el && el.nodeType && el.nodeType === 1)) {
351 throw `Sortable: \`el\` must be an HTMLElement, not ${ {}.toString.call(el) }`;
352 }
353
354 this.el = el; // root element
355 this.options = options = Object.assign({}, options);
356
357
358 // Export instance
359 el[expando] = this;
360
361 let defaults = {
362 group: null,
363 sort: true,
364 disabled: false,
365 store: null,
366 handle: null,
367 draggable: /^[uo]l$/i.test(el.nodeName) ? '>li' : '>*',
368 swapThreshold: 1, // percentage; 0 <= x <= 1
369 invertSwap: false, // invert always
370 invertedSwapThreshold: null, // will be set to same as swapThreshold if default
371 removeCloneOnHide: true,
372 direction: function() {
373 return _detectDirection(el, this.options);
374 },
375 ghostClass: 'sortable-ghost',
376 chosenClass: 'sortable-chosen',
377 dragClass: 'sortable-drag',
378 ignore: 'a, img',
379 filter: null,
380 preventOnFilter: true,
381 animation: 0,
382 easing: null,
383 setData: function (dataTransfer, dragEl) {
384 dataTransfer.setData('Text', dragEl.textContent);
385 },
386 dropBubble: false,
387 dragoverBubble: false,
388 dataIdAttr: 'data-id',
389 delay: 0,
390 delayOnTouchOnly: false,
391 touchStartThreshold: (Number.parseInt ? Number : window).parseInt(window.devicePixelRatio, 10) || 1,
392 forceFallback: false,
393 fallbackClass: 'sortable-fallback',
394 fallbackOnBody: false,
395 fallbackTolerance: 0,
396 fallbackOffset: {x: 0, y: 0},
397 // Disabled on Safari: #1571; Enabled on Safari IOS: #2244
398 supportPointer: Sortable.supportPointer !== false && ('PointerEvent' in window) && (!Safari || IOS),
399 emptyInsertThreshold: 5
400 };
401
402 PluginManager.initializePlugins(this, el, defaults);
403
404 // Set default options
405 for (let name in defaults) {
406 !(name in options) && (options[name] = defaults[name]);
407 }
408
409 _prepareGroup(options);
410
411 // Bind all private methods
412 for (let fn in this) {
413 if (fn.charAt(0) === '_' && typeof this[fn] === 'function') {
414 this[fn] = this[fn].bind(this);
415 }
416 }
417
418 // Setup drag mode
419 this.nativeDraggable = options.forceFallback ? false : supportDraggable;
420
421 if (this.nativeDraggable) {
422 // Touch start threshold cannot be greater than the native dragstart threshold
423 this.options.touchStartThreshold = 1;
424 }
425
426 // Bind events
427 if (options.supportPointer) {
428 on(el, 'pointerdown', this._onTapStart);
429 } else {
430 on(el, 'mousedown', this._onTapStart);
431 on(el, 'touchstart', this._onTapStart);
432 }
433
434 if (this.nativeDraggable) {
435 on(el, 'dragover', this);
436 on(el, 'dragenter', this);
437 }
438
439 sortables.push(this.el);
440
441 // Restore sorting
442 options.store && options.store.get && this.sort(options.store.get(this) || []);
443
444 // Add animation state manager
445 Object.assign(this, AnimationStateManager());
446 }
447
448 Sortable.prototype = /** @lends Sortable.prototype */ {
449 constructor: Sortable,
450
451 _isOutsideThisEl: function(target) {
452 if (!this.el.contains(target) && target !== this.el) {
453 lastTarget = null;
454 }
455 },
456
457 _getDirection: function(evt, target) {
458 return (typeof this.options.direction === 'function') ? this.options.direction.call(this, evt, target, dragEl) : this.options.direction;
459 },
460
461 _onTapStart: function (/** Event|TouchEvent */evt) {
462 if (!evt.cancelable) return;
463 let _this = this,
464 el = this.el,
465 options = this.options,
466 preventOnFilter = options.preventOnFilter,
467 type = evt.type,
468 touch = (evt.touches && evt.touches[0]) || (evt.pointerType && evt.pointerType === 'touch' && evt),
469 target = (touch || evt).target,
470 originalTarget = evt.target.shadowRoot && ((evt.path && evt.path[0]) || (evt.composedPath && evt.composedPath()[0])) || target,
471 filter = options.filter;
472
473 _saveInputCheckedState(el);
474
475
476 // Don't trigger start event when an element is been dragged, otherwise the evt.oldindex always wrong when set option.group.
477 if (dragEl) {
478 return;
479 }
480
481 if (/mousedown|pointerdown/.test(type) && evt.button !== 0 || options.disabled) {
482 return; // only left button and enabled
483 }
484
485 // cancel dnd if original target is content editable
486 if (originalTarget.isContentEditable) {
487 return;
488 }
489
490 // Safari ignores further event handling after mousedown
491 if (!this.nativeDraggable && Safari && target && target.tagName.toUpperCase() === 'SELECT') {
492 return;
493 }
494
495 target = closest(target, options.draggable, el, false);
496
497
498 if (target && target.animated) {
499 return;
500 }
501
502 if (lastDownEl === target) {
503 // Ignoring duplicate `down`
504 return;
505 }
506
507 // Get the index of the dragged element within its parent
508 oldIndex = index(target);
509 oldDraggableIndex = index(target, options.draggable);
510
511 // Check filter
512 if (typeof filter === 'function') {
513 if (filter.call(this, evt, target, this)) {
514 _dispatchEvent({
515 sortable: _this,
516 rootEl: originalTarget,
517 name: 'filter',
518 targetEl: target,
519 toEl: el,
520 fromEl: el
521 });
522 pluginEvent('filter', _this, { evt });
523 preventOnFilter && evt.preventDefault();
524 return; // cancel dnd
525 }
526 }
527 else if (filter) {
528 filter = filter.split(',').some(function (criteria) {
529 criteria = closest(originalTarget, criteria.trim(), el, false);
530
531 if (criteria) {
532 _dispatchEvent({
533 sortable: _this,
534 rootEl: criteria,
535 name: 'filter',
536 targetEl: target,
537 fromEl: el,
538 toEl: el
539 });
540 pluginEvent('filter', _this, { evt });
541 return true;
542 }
543 });
544
545 if (filter) {
546 preventOnFilter && evt.preventDefault();
547 return; // cancel dnd
548 }
549 }
550
551 if (options.handle && !closest(originalTarget, options.handle, el, false)) {
552 return;
553 }
554
555 // Prepare `dragstart`
556 this._prepareDragStart(evt, touch, target);
557 },
558
559 _prepareDragStart: function (/** Event */evt, /** Touch */touch, /** HTMLElement */target) {
560 let _this = this,
561 el = _this.el,
562 options = _this.options,
563 ownerDocument = el.ownerDocument,
564 dragStartFn;
565
566 if (target && !dragEl && (target.parentNode === el)) {
567 let dragRect = getRect(target);
568 rootEl = el;
569 dragEl = target;
570 parentEl = dragEl.parentNode;
571 nextEl = dragEl.nextSibling;
572 lastDownEl = target;
573 activeGroup = options.group;
574
575 Sortable.dragged = dragEl;
576
577 tapEvt = {
578 target: dragEl,
579 clientX: (touch || evt).clientX,
580 clientY: (touch || evt).clientY
581 };
582
583 tapDistanceLeft = tapEvt.clientX - dragRect.left;
584 tapDistanceTop = tapEvt.clientY - dragRect.top;
585
586 this._lastX = (touch || evt).clientX;
587 this._lastY = (touch || evt).clientY;
588
589 dragEl.style['will-change'] = 'all';
590
591 dragStartFn = function () {
592 pluginEvent('delayEnded', _this, { evt });
593 if (Sortable.eventCanceled) {
594 _this._onDrop();
595 return;
596 }
597 // Delayed drag has been triggered
598 // we can re-enable the events: touchmove/mousemove
599 _this._disableDelayedDragEvents();
600
601 if (!FireFox && _this.nativeDraggable) {
602 dragEl.draggable = true;
603 }
604
605 // Bind the events: dragstart/dragend
606 _this._triggerDragStart(evt, touch);
607
608 // Drag start event
609 _dispatchEvent({
610 sortable: _this,
611 name: 'choose',
612 originalEvent: evt
613 });
614
615 // Chosen item
616 toggleClass(dragEl, options.chosenClass, true);
617 };
618
619 // Disable "draggable"
620 options.ignore.split(',').forEach(function (criteria) {
621 find(dragEl, criteria.trim(), _disableDraggable);
622 });
623
624 on(ownerDocument, 'dragover', nearestEmptyInsertDetectEvent);
625 on(ownerDocument, 'mousemove', nearestEmptyInsertDetectEvent);
626 on(ownerDocument, 'touchmove', nearestEmptyInsertDetectEvent);
627
628 if (options.supportPointer) {
629 on(ownerDocument, 'pointerup', _this._onDrop);
630 // Native D&D triggers pointercancel
631 !this.nativeDraggable && on(ownerDocument, 'pointercancel', _this._onDrop);
632 } else {
633 on(ownerDocument, 'mouseup', _this._onDrop);
634 on(ownerDocument, 'touchend', _this._onDrop);
635 on(ownerDocument, 'touchcancel', _this._onDrop);
636 }
637
638 // Make dragEl draggable (must be before delay for FireFox)
639 if (FireFox && this.nativeDraggable) {
640 this.options.touchStartThreshold = 4;
641 dragEl.draggable = true;
642 }
643
644 pluginEvent('delayStart', this, { evt });
645
646 // Delay is impossible for native DnD in Edge or IE
647 if (options.delay && (!options.delayOnTouchOnly || touch) && (!this.nativeDraggable || !(Edge || IE11OrLess))) {
648 if (Sortable.eventCanceled) {
649 this._onDrop();
650 return;
651 }
652 // If the user moves the pointer or let go the click or touch
653 // before the delay has been reached:
654 // disable the delayed drag
655 if (options.supportPointer) {
656 on(ownerDocument, 'pointerup', _this._disableDelayedDrag);
657 on(ownerDocument, 'pointercancel', _this._disableDelayedDrag);
658 } else {
659 on(ownerDocument, 'mouseup', _this._disableDelayedDrag);
660 on(ownerDocument, 'touchend', _this._disableDelayedDrag);
661 on(ownerDocument, 'touchcancel', _this._disableDelayedDrag);
662 }
663 on(ownerDocument, 'mousemove', _this._delayedDragTouchMoveHandler);
664 on(ownerDocument, 'touchmove', _this._delayedDragTouchMoveHandler);
665 options.supportPointer && on(ownerDocument, 'pointermove', _this._delayedDragTouchMoveHandler);
666
667 _this._dragStartTimer = setTimeout(dragStartFn, options.delay);
668 } else {
669 dragStartFn();
670 }
671 }
672 },
673
674 _delayedDragTouchMoveHandler: function (/** TouchEvent|PointerEvent **/e) {
675 let touch = e.touches ? e.touches[0] : e;
676 if (Math.max(Math.abs(touch.clientX - this._lastX), Math.abs(touch.clientY - this._lastY))
677 >= Math.floor(this.options.touchStartThreshold / (this.nativeDraggable && window.devicePixelRatio || 1))
678 ) {
679 this._disableDelayedDrag();
680 }
681 },
682
683 _disableDelayedDrag: function () {
684 dragEl && _disableDraggable(dragEl);
685 clearTimeout(this._dragStartTimer);
686
687 this._disableDelayedDragEvents();
688 },
689
690 _disableDelayedDragEvents: function () {
691 let ownerDocument = this.el.ownerDocument;
692 off(ownerDocument, 'mouseup', this._disableDelayedDrag);
693 off(ownerDocument, 'touchend', this._disableDelayedDrag);
694 off(ownerDocument, 'touchcancel', this._disableDelayedDrag);
695 off(ownerDocument, 'pointerup', this._disableDelayedDrag);
696 off(ownerDocument, 'pointercancel', this._disableDelayedDrag);
697 off(ownerDocument, 'mousemove', this._delayedDragTouchMoveHandler);
698 off(ownerDocument, 'touchmove', this._delayedDragTouchMoveHandler);
699 off(ownerDocument, 'pointermove', this._delayedDragTouchMoveHandler);
700 },
701
702 _triggerDragStart: function (/** Event */evt, /** Touch */touch) {
703 touch = touch || (evt.pointerType == 'touch' && evt);
704
705 if (!this.nativeDraggable || touch) {
706 if (this.options.supportPointer) {
707 on(document, 'pointermove', this._onTouchMove);
708 } else if (touch) {
709 on(document, 'touchmove', this._onTouchMove);
710 } else {
711 on(document, 'mousemove', this._onTouchMove);
712 }
713 } else {
714 on(dragEl, 'dragend', this);
715 on(rootEl, 'dragstart', this._onDragStart);
716 }
717
718 try {
719
720 if (document.selection) {
721 _nextTick(() => {
722 document.selection.empty();
723 });
724 } else {
725 window.getSelection().removeAllRanges();
726 }
727 } catch (err) {
728 }
729 },
730
731 _dragStarted: function (fallback, evt) {
732 let _this = this;
733 awaitingDragStarted = false;
734 if (rootEl && dragEl) {
735 pluginEvent('dragStarted', this, { evt });
736
737 if (this.nativeDraggable) {
738 on(document, 'dragover', _checkOutsideTargetEl);
739 }
740 let options = this.options;
741
742 // Apply effect
743 !fallback && toggleClass(dragEl, options.dragClass, false);
744 toggleClass(dragEl, options.ghostClass, true);
745
746 Sortable.active = this;
747
748 fallback && this._appendGhost();
749
750 // Drag start event
751 _dispatchEvent({
752 sortable: this,
753 name: 'start',
754 originalEvent: evt
755 });
756 } else {
757 this._nulling();
758 }
759 },
760
761 _emulateDragOver: function () {
762 if (touchEvt) {
763 this._lastX = touchEvt.clientX;
764 this._lastY = touchEvt.clientY;
765
766 _hideGhostForTarget();
767
768 let target = document.elementFromPoint(touchEvt.clientX, touchEvt.clientY);
769 let parent = target;
770
771 while (target && target.shadowRoot) {
772 target = target.shadowRoot.elementFromPoint(touchEvt.clientX, touchEvt.clientY);
773 if (target === parent) break;
774 parent = target;
775 }
776
777 dragEl.parentNode[expando]._isOutsideThisEl(target);
778
779 if (parent) {
780 do {
781 if (parent[expando]) {
782 let inserted;
783
784 inserted = parent[expando]._onDragOver({
785 clientX: touchEvt.clientX,
786 clientY: touchEvt.clientY,
787 target: target,
788 rootEl: parent
789 });
790
791 if (inserted && !this.options.dragoverBubble) {
792 break;
793 }
794 }
795
796 target = parent; // store last element
797 }
798 /* jshint boss:true */
799 while (parent = getParentOrHost(parent));
800 }
801
802 _unhideGhostForTarget();
803 }
804 },
805
806
807 _onTouchMove: function (/**TouchEvent*/evt) {
808 if (tapEvt) {
809 let options = this.options,
810 fallbackTolerance = options.fallbackTolerance,
811 fallbackOffset = options.fallbackOffset,
812 touch = evt.touches ? evt.touches[0] : evt,
813 ghostMatrix = ghostEl && matrix(ghostEl, true),
814 scaleX = ghostEl && ghostMatrix && ghostMatrix.a,
815 scaleY = ghostEl && ghostMatrix && ghostMatrix.d,
816 relativeScrollOffset = PositionGhostAbsolutely && ghostRelativeParent && getRelativeScrollOffset(ghostRelativeParent),
817 dx = ((touch.clientX - tapEvt.clientX)
818 + fallbackOffset.x) / (scaleX || 1)
819 + (relativeScrollOffset ? (relativeScrollOffset[0] - ghostRelativeParentInitialScroll[0]) : 0) / (scaleX || 1),
820 dy = ((touch.clientY - tapEvt.clientY)
821 + fallbackOffset.y) / (scaleY || 1)
822 + (relativeScrollOffset ? (relativeScrollOffset[1] - ghostRelativeParentInitialScroll[1]) : 0) / (scaleY || 1);
823
824 // only set the status to dragging, when we are actually dragging
825 if (!Sortable.active && !awaitingDragStarted) {
826 if (fallbackTolerance &&
827 Math.max(Math.abs(touch.clientX - this._lastX), Math.abs(touch.clientY - this._lastY)) < fallbackTolerance
828 ) {
829 return;
830 }
831 this._onDragStart(evt, true);
832 }
833
834 if (ghostEl) {
835 if (ghostMatrix) {
836 ghostMatrix.e += dx - (lastDx || 0);
837 ghostMatrix.f += dy - (lastDy || 0);
838 } else {
839 ghostMatrix = {
840 a: 1,
841 b: 0,
842 c: 0,
843 d: 1,
844 e: dx,
845 f: dy
846 };
847 }
848
849 let cssMatrix = `matrix(${ghostMatrix.a},${ghostMatrix.b},${ghostMatrix.c},${ghostMatrix.d},${ghostMatrix.e},${ghostMatrix.f})`;
850
851 css(ghostEl, 'webkitTransform', cssMatrix);
852 css(ghostEl, 'mozTransform', cssMatrix);
853 css(ghostEl, 'msTransform', cssMatrix);
854 css(ghostEl, 'transform', cssMatrix);
855
856 lastDx = dx;
857 lastDy = dy;
858
859 touchEvt = touch;
860 }
861
862 evt.cancelable && evt.preventDefault();
863 }
864 },
865
866 _appendGhost: function () {
867 // Bug if using scale(): https://stackoverflow.com/questions/2637058
868 // Not being adjusted for
869 if (!ghostEl) {
870 let container = this.options.fallbackOnBody ? document.body : rootEl,
871 rect = getRect(dragEl, true, PositionGhostAbsolutely, true, container),
872 options = this.options;
873
874 // Position absolutely
875 if (PositionGhostAbsolutely) {
876 // Get relatively positioned parent
877 ghostRelativeParent = container;
878
879 while (
880 css(ghostRelativeParent, 'position') === 'static' &&
881 css(ghostRelativeParent, 'transform') === 'none' &&
882 ghostRelativeParent !== document
883 ) {
884 ghostRelativeParent = ghostRelativeParent.parentNode;
885 }
886
887 if (ghostRelativeParent !== document.body && ghostRelativeParent !== document.documentElement) {
888 if (ghostRelativeParent === document) ghostRelativeParent = getWindowScrollingElement();
889
890 rect.top += ghostRelativeParent.scrollTop;
891 rect.left += ghostRelativeParent.scrollLeft;
892 } else {
893 ghostRelativeParent = getWindowScrollingElement();
894 }
895 ghostRelativeParentInitialScroll = getRelativeScrollOffset(ghostRelativeParent);
896 }
897
898
899 ghostEl = dragEl.cloneNode(true);
900
901 toggleClass(ghostEl, options.ghostClass, false);
902 toggleClass(ghostEl, options.fallbackClass, true);
903 toggleClass(ghostEl, options.dragClass, true);
904
905 css(ghostEl, 'transition', '');
906 css(ghostEl, 'transform', '');
907
908 css(ghostEl, 'box-sizing', 'border-box');
909 css(ghostEl, 'margin', 0);
910 css(ghostEl, 'top', rect.top);
911 css(ghostEl, 'left', rect.left);
912 css(ghostEl, 'width', rect.width);
913 css(ghostEl, 'height', rect.height);
914 css(ghostEl, 'opacity', '0.8');
915 css(ghostEl, 'position', (PositionGhostAbsolutely ? 'absolute' : 'fixed'));
916 css(ghostEl, 'zIndex', '100000');
917 css(ghostEl, 'pointerEvents', 'none');
918
919
920 Sortable.ghost = ghostEl;
921
922 container.appendChild(ghostEl);
923
924 // Set transform-origin
925 css(ghostEl, 'transform-origin', (tapDistanceLeft / parseInt(ghostEl.style.width) * 100) + '% ' + (tapDistanceTop / parseInt(ghostEl.style.height) * 100) + '%');
926 }
927 },
928
929 _onDragStart: function (/**Event*/evt, /**boolean*/fallback) {
930 let _this = this;
931 let dataTransfer = evt.dataTransfer;
932 let options = _this.options;
933
934 pluginEvent('dragStart', this, { evt });
935 if (Sortable.eventCanceled) {
936 this._onDrop();
937 return;
938 }
939
940 pluginEvent('setupClone', this);
941 if (!Sortable.eventCanceled) {
942 cloneEl = clone(dragEl);
943 cloneEl.removeAttribute("id");
944 cloneEl.draggable = false;
945 cloneEl.style['will-change'] = '';
946
947 this._hideClone();
948
949 toggleClass(cloneEl, this.options.chosenClass, false);
950 Sortable.clone = cloneEl;
951 }
952
953
954 // #1143: IFrame support workaround
955 _this.cloneId = _nextTick(function() {
956 pluginEvent('clone', _this);
957 if (Sortable.eventCanceled) return;
958
959 if (!_this.options.removeCloneOnHide) {
960 rootEl.insertBefore(cloneEl, dragEl);
961 }
962 _this._hideClone();
963
964 _dispatchEvent({
965 sortable: _this,
966 name: 'clone'
967 });
968 });
969
970
971 !fallback && toggleClass(dragEl, options.dragClass, true);
972
973 // Set proper drop events
974 if (fallback) {
975 ignoreNextClick = true;
976 _this._loopId = setInterval(_this._emulateDragOver, 50);
977 } else {
978 // Undo what was set in _prepareDragStart before drag started
979 off(document, 'mouseup', _this._onDrop);
980 off(document, 'touchend', _this._onDrop);
981 off(document, 'touchcancel', _this._onDrop);
982
983 if (dataTransfer) {
984 dataTransfer.effectAllowed = 'move';
985 options.setData && options.setData.call(_this, dataTransfer, dragEl);
986 }
987
988 on(document, 'drop', _this);
989
990 // #1276 fix:
991 css(dragEl, 'transform', 'translateZ(0)');
992 }
993
994 awaitingDragStarted = true;
995
996 _this._dragStartId = _nextTick(_this._dragStarted.bind(_this, fallback, evt));
997 on(document, 'selectstart', _this);
998
999 moved = true;
1000
1001 window.getSelection().removeAllRanges();
1002
1003 if (Safari) {
1004 css(document.body, 'user-select', 'none');
1005 }
1006 },
1007
1008
1009 // Returns true - if no further action is needed (either inserted or another condition)
1010 _onDragOver: function (/**Event*/evt) {
1011 let el = this.el,
1012 target = evt.target,
1013 dragRect,
1014 targetRect,
1015 revert,
1016 options = this.options,
1017 group = options.group,
1018 activeSortable = Sortable.active,
1019 isOwner = (activeGroup === group),
1020 canSort = options.sort,
1021 fromSortable = (putSortable || activeSortable),
1022 vertical,
1023 _this = this,
1024 completedFired = false;
1025
1026 if (_silent) return;
1027
1028 function dragOverEvent(name, extra) {
1029 pluginEvent(name, _this, {
1030 evt,
1031 isOwner,
1032 axis: vertical ? 'vertical' : 'horizontal',
1033 revert,
1034 dragRect,
1035 targetRect,
1036 canSort,
1037 fromSortable,
1038 target,
1039 completed,
1040 onMove(target, after) {
1041 return onMove(rootEl, el, dragEl, dragRect, target, getRect(target), evt, after);
1042 },
1043 changed,
1044 ...extra
1045 });
1046 }
1047
1048 // Capture animation state
1049 function capture() {
1050 dragOverEvent('dragOverAnimationCapture');
1051
1052 _this.captureAnimationState();
1053 if (_this !== fromSortable) {
1054 fromSortable.captureAnimationState();
1055 }
1056 }
1057
1058 // Return invocation when dragEl is inserted (or completed)
1059 function completed(insertion) {
1060 dragOverEvent('dragOverCompleted', { insertion });
1061
1062 if (insertion) {
1063 // Clones must be hidden before folding animation to capture dragRectAbsolute properly
1064 if (isOwner) {
1065 activeSortable._hideClone();
1066 } else {
1067 activeSortable._showClone(_this);
1068 }
1069
1070 if (_this !== fromSortable) {
1071 // Set ghost class to new sortable's ghost class
1072 toggleClass(dragEl, putSortable ? putSortable.options.ghostClass : activeSortable.options.ghostClass, false);
1073 toggleClass(dragEl, options.ghostClass, true);
1074 }
1075
1076 if (putSortable !== _this && _this !== Sortable.active) {
1077 putSortable = _this;
1078 } else if (_this === Sortable.active && putSortable) {
1079 putSortable = null;
1080 }
1081
1082 // Animation
1083 if (fromSortable === _this) {
1084 _this._ignoreWhileAnimating = target;
1085 }
1086 _this.animateAll(function() {
1087 dragOverEvent('dragOverAnimationComplete');
1088 _this._ignoreWhileAnimating = null;
1089 });
1090 if (_this !== fromSortable) {
1091 fromSortable.animateAll();
1092 fromSortable._ignoreWhileAnimating = null;
1093 }
1094 }
1095
1096
1097 // Null lastTarget if it is not inside a previously swapped element
1098 if ((target === dragEl && !dragEl.animated) || (target === el && !target.animated)) {
1099 lastTarget = null;
1100 }
1101
1102 // no bubbling and not fallback
1103 if (!options.dragoverBubble && !evt.rootEl && target !== document) {
1104 dragEl.parentNode[expando]._isOutsideThisEl(evt.target);
1105
1106 // Do not detect for empty insert if already inserted
1107 !insertion && nearestEmptyInsertDetectEvent(evt);
1108 }
1109
1110 !options.dragoverBubble && evt.stopPropagation && evt.stopPropagation();
1111
1112 return (completedFired = true);
1113 }
1114
1115 // Call when dragEl has been inserted
1116 function changed() {
1117 newIndex = index(dragEl);
1118 newDraggableIndex = index(dragEl, options.draggable);
1119 _dispatchEvent({
1120 sortable: _this,
1121 name: 'change',
1122 toEl: el,
1123 newIndex,
1124 newDraggableIndex,
1125 originalEvent: evt
1126 });
1127 }
1128
1129
1130 if (evt.preventDefault !== void 0) {
1131 evt.cancelable && evt.preventDefault();
1132 }
1133
1134
1135 target = closest(target, options.draggable, el, true);
1136
1137 dragOverEvent('dragOver');
1138 if (Sortable.eventCanceled) return completedFired;
1139
1140 if (
1141 dragEl.contains(evt.target) ||
1142 target.animated && target.animatingX && target.animatingY ||
1143 _this._ignoreWhileAnimating === target
1144 ) {
1145 return completed(false);
1146 }
1147
1148 ignoreNextClick = false;
1149
1150 if (activeSortable && !options.disabled &&
1151 (isOwner
1152 ? canSort || (revert = parentEl !== rootEl) // Reverting item into the original list
1153 : (
1154 putSortable === this ||
1155 (
1156 (this.lastPutMode = activeGroup.checkPull(this, activeSortable, dragEl, evt)) &&
1157 group.checkPut(this, activeSortable, dragEl, evt)
1158 )
1159 )
1160 )
1161 ) {
1162 vertical = this._getDirection(evt, target) === 'vertical';
1163
1164 dragRect = getRect(dragEl);
1165
1166 dragOverEvent('dragOverValid');
1167 if (Sortable.eventCanceled) return completedFired;
1168
1169 if (revert) {
1170 parentEl = rootEl; // actualization
1171 capture();
1172
1173 this._hideClone();
1174
1175 dragOverEvent('revert');
1176
1177 if (!Sortable.eventCanceled) {
1178 if (nextEl) {
1179 rootEl.insertBefore(dragEl, nextEl);
1180 } else {
1181 rootEl.appendChild(dragEl);
1182 }
1183 }
1184
1185 return completed(true);
1186 }
1187
1188 let elLastChild = lastChild(el, options.draggable);
1189
1190 if (!elLastChild || _ghostIsLast(evt, vertical, this) && !elLastChild.animated) {
1191 // Insert to end of list
1192
1193 // If already at end of list: Do not insert
1194 if (elLastChild === dragEl) {
1195 return completed(false);
1196 }
1197
1198 // if there is a last element, it is the target
1199 if (elLastChild && el === evt.target) {
1200 target = elLastChild;
1201 }
1202
1203 if (target) {
1204 targetRect = getRect(target);
1205 }
1206
1207 if (onMove(rootEl, el, dragEl, dragRect, target, targetRect, evt, !!target) !== false) {
1208 capture();
1209 if (elLastChild && elLastChild.nextSibling) { // the last draggable element is not the last node
1210 el.insertBefore(dragEl, elLastChild.nextSibling);
1211 }
1212 else {
1213 el.appendChild(dragEl);
1214 }
1215 parentEl = el; // actualization
1216
1217 changed();
1218 return completed(true);
1219 }
1220 }
1221 else if (elLastChild && _ghostIsFirst(evt, vertical, this)) {
1222 // Insert to start of list
1223 let firstChild = getChild(el, 0, options, true);
1224 if (firstChild === dragEl) {
1225 return completed(false);
1226 }
1227 target = firstChild;
1228 targetRect = getRect(target);
1229
1230 if (onMove(rootEl, el, dragEl, dragRect, target, targetRect, evt, false) !== false) {
1231 capture();
1232 el.insertBefore(dragEl, firstChild);
1233 parentEl = el; // actualization
1234
1235 changed();
1236 return completed(true);
1237 }
1238 }
1239 else if (target.parentNode === el) {
1240 targetRect = getRect(target);
1241 let direction = 0,
1242 targetBeforeFirstSwap,
1243 differentLevel = dragEl.parentNode !== el,
1244 differentRowCol = !_dragElInRowColumn(dragEl.animated && dragEl.toRect || dragRect, target.animated && target.toRect || targetRect, vertical),
1245 side1 = vertical ? 'top' : 'left',
1246 scrolledPastTop = isScrolledPast(target, 'top', 'top') || isScrolledPast(dragEl, 'top', 'top'),
1247 scrollBefore = scrolledPastTop ? scrolledPastTop.scrollTop : void 0;
1248
1249
1250 if (lastTarget !== target) {
1251 targetBeforeFirstSwap = targetRect[side1];
1252 pastFirstInvertThresh = false;
1253 isCircumstantialInvert = (!differentRowCol && options.invertSwap) || differentLevel;
1254 }
1255
1256 direction = _getSwapDirection(
1257 evt, target, targetRect, vertical,
1258 differentRowCol ? 1 : options.swapThreshold,
1259 options.invertedSwapThreshold == null ? options.swapThreshold : options.invertedSwapThreshold,
1260 isCircumstantialInvert,
1261 lastTarget === target
1262 );
1263
1264 let sibling;
1265
1266 if (direction !== 0) {
1267 // Check if target is beside dragEl in respective direction (ignoring hidden elements)
1268 let dragIndex = index(dragEl);
1269
1270 do {
1271 dragIndex -= direction;
1272 sibling = parentEl.children[dragIndex];
1273 } while (sibling && (css(sibling, 'display') === 'none' || sibling === ghostEl));
1274 }
1275 // If dragEl is already beside target: Do not insert
1276 if (
1277 direction === 0 ||
1278 sibling === target
1279 ) {
1280 return completed(false);
1281 }
1282
1283 lastTarget = target;
1284
1285 lastDirection = direction;
1286
1287 let nextSibling = target.nextElementSibling,
1288 after = false;
1289
1290 after = direction === 1;
1291
1292 let moveVector = onMove(rootEl, el, dragEl, dragRect, target, targetRect, evt, after);
1293
1294 if (moveVector !== false) {
1295 if (moveVector === 1 || moveVector === -1) {
1296 after = (moveVector === 1);
1297 }
1298
1299 _silent = true;
1300 setTimeout(_unsilent, 30);
1301
1302 capture();
1303
1304 if (after && !nextSibling) {
1305 el.appendChild(dragEl);
1306 } else {
1307 target.parentNode.insertBefore(dragEl, after ? nextSibling : target);
1308 }
1309
1310 // Undo chrome's scroll adjustment (has no effect on other browsers)
1311 if (scrolledPastTop) {
1312 scrollBy(scrolledPastTop, 0, scrollBefore - scrolledPastTop.scrollTop);
1313 }
1314
1315 parentEl = dragEl.parentNode; // actualization
1316
1317 // must be done before animation
1318 if (targetBeforeFirstSwap !== undefined && !isCircumstantialInvert) {
1319 targetMoveDistance = Math.abs(targetBeforeFirstSwap - getRect(target)[side1]);
1320 }
1321 changed();
1322
1323 return completed(true);
1324 }
1325 }
1326
1327 if (el.contains(dragEl)) {
1328 return completed(false);
1329 }
1330 }
1331
1332 return false;
1333 },
1334
1335 _ignoreWhileAnimating: null,
1336
1337 _offMoveEvents: function() {
1338 off(document, 'mousemove', this._onTouchMove);
1339 off(document, 'touchmove', this._onTouchMove);
1340 off(document, 'pointermove', this._onTouchMove);
1341 off(document, 'dragover', nearestEmptyInsertDetectEvent);
1342 off(document, 'mousemove', nearestEmptyInsertDetectEvent);
1343 off(document, 'touchmove', nearestEmptyInsertDetectEvent);
1344 },
1345
1346 _offUpEvents: function () {
1347 let ownerDocument = this.el.ownerDocument;
1348
1349 off(ownerDocument, 'mouseup', this._onDrop);
1350 off(ownerDocument, 'touchend', this._onDrop);
1351 off(ownerDocument, 'pointerup', this._onDrop);
1352 off(ownerDocument, 'pointercancel', this._onDrop);
1353 off(ownerDocument, 'touchcancel', this._onDrop);
1354 off(document, 'selectstart', this);
1355 },
1356
1357 _onDrop: function (/**Event*/evt) {
1358 let el = this.el,
1359 options = this.options;
1360
1361 // Get the index of the dragged element within its parent
1362 newIndex = index(dragEl);
1363 newDraggableIndex = index(dragEl, options.draggable);
1364
1365 pluginEvent('drop', this, {
1366 evt
1367 });
1368
1369 parentEl = dragEl && dragEl.parentNode;
1370
1371 // Get again after plugin event
1372 newIndex = index(dragEl);
1373 newDraggableIndex = index(dragEl, options.draggable);
1374
1375 if (Sortable.eventCanceled) {
1376 this._nulling();
1377 return;
1378 }
1379
1380 awaitingDragStarted = false;
1381 isCircumstantialInvert = false;
1382 pastFirstInvertThresh = false;
1383
1384 clearInterval(this._loopId);
1385
1386 clearTimeout(this._dragStartTimer);
1387
1388 _cancelNextTick(this.cloneId);
1389 _cancelNextTick(this._dragStartId);
1390
1391 // Unbind events
1392 if (this.nativeDraggable) {
1393 off(document, 'drop', this);
1394 off(el, 'dragstart', this._onDragStart);
1395 }
1396 this._offMoveEvents();
1397 this._offUpEvents();
1398
1399
1400 if (Safari) {
1401 css(document.body, 'user-select', '');
1402 }
1403
1404 css(dragEl, 'transform', '');
1405
1406 if (evt) {
1407 if (moved) {
1408 evt.cancelable && evt.preventDefault();
1409 !options.dropBubble && evt.stopPropagation();
1410 }
1411
1412 ghostEl && ghostEl.parentNode && ghostEl.parentNode.removeChild(ghostEl);
1413
1414 if (rootEl === parentEl || (putSortable && putSortable.lastPutMode !== 'clone')) {
1415 // Remove clone(s)
1416 cloneEl && cloneEl.parentNode && cloneEl.parentNode.removeChild(cloneEl);
1417 }
1418
1419 if (dragEl) {
1420 if (this.nativeDraggable) {
1421 off(dragEl, 'dragend', this);
1422 }
1423
1424 _disableDraggable(dragEl);
1425 dragEl.style['will-change'] = '';
1426
1427 // Remove classes
1428 // ghostClass is added in dragStarted
1429 if (moved && !awaitingDragStarted) {
1430 toggleClass(dragEl, putSortable ? putSortable.options.ghostClass : this.options.ghostClass, false);
1431 }
1432 toggleClass(dragEl, this.options.chosenClass, false);
1433
1434 // Drag stop event
1435 _dispatchEvent({
1436 sortable: this,
1437 name: 'unchoose',
1438 toEl: parentEl,
1439 newIndex: null,
1440 newDraggableIndex: null,
1441 originalEvent: evt
1442 });
1443
1444
1445 if (rootEl !== parentEl) {
1446
1447 if (newIndex >= 0) {
1448 // Add event
1449 _dispatchEvent({
1450 rootEl: parentEl,
1451 name: 'add',
1452 toEl: parentEl,
1453 fromEl: rootEl,
1454 originalEvent: evt
1455 });
1456
1457 // Remove event
1458 _dispatchEvent({
1459 sortable: this,
1460 name: 'remove',
1461 toEl: parentEl,
1462 originalEvent: evt
1463 });
1464
1465 // drag from one list and drop into another
1466 _dispatchEvent({
1467 rootEl: parentEl,
1468 name: 'sort',
1469 toEl: parentEl,
1470 fromEl: rootEl,
1471 originalEvent: evt
1472 });
1473
1474 _dispatchEvent({
1475 sortable: this,
1476 name: 'sort',
1477 toEl: parentEl,
1478 originalEvent: evt
1479 });
1480 }
1481
1482 putSortable && putSortable.save();
1483 } else {
1484 if (newIndex !== oldIndex) {
1485 if (newIndex >= 0) {
1486 // drag & drop within the same list
1487 _dispatchEvent({
1488 sortable: this,
1489 name: 'update',
1490 toEl: parentEl,
1491 originalEvent: evt
1492 });
1493
1494 _dispatchEvent({
1495 sortable: this,
1496 name: 'sort',
1497 toEl: parentEl,
1498 originalEvent: evt
1499 });
1500 }
1501 }
1502 }
1503
1504 if (Sortable.active) {
1505 /* jshint eqnull:true */
1506 if (newIndex == null || newIndex === -1) {
1507 newIndex = oldIndex;
1508 newDraggableIndex = oldDraggableIndex;
1509 }
1510
1511 _dispatchEvent({
1512 sortable: this,
1513 name: 'end',
1514 toEl: parentEl,
1515 originalEvent: evt
1516 });
1517
1518 // Save sorting
1519 this.save();
1520 }
1521 }
1522
1523 }
1524 this._nulling();
1525 },
1526
1527 _nulling: function() {
1528 pluginEvent('nulling', this);
1529
1530 rootEl =
1531 dragEl =
1532 parentEl =
1533 ghostEl =
1534 nextEl =
1535 cloneEl =
1536 lastDownEl =
1537 cloneHidden =
1538
1539 tapEvt =
1540 touchEvt =
1541
1542 moved =
1543 newIndex =
1544 newDraggableIndex =
1545 oldIndex =
1546 oldDraggableIndex =
1547
1548 lastTarget =
1549 lastDirection =
1550
1551 putSortable =
1552 activeGroup =
1553 Sortable.dragged =
1554 Sortable.ghost =
1555 Sortable.clone =
1556 Sortable.active = null;
1557
1558 savedInputChecked.forEach(function (el) {
1559 el.checked = true;
1560 });
1561
1562 savedInputChecked.length =
1563 lastDx =
1564 lastDy = 0;
1565 },
1566
1567 handleEvent: function (/**Event*/evt) {
1568 switch (evt.type) {
1569 case 'drop':
1570 case 'dragend':
1571 this._onDrop(evt);
1572 break;
1573
1574 case 'dragenter':
1575 case 'dragover':
1576 if (dragEl) {
1577 this._onDragOver(evt);
1578 _globalDragOver(evt);
1579 }
1580 break;
1581
1582 case 'selectstart':
1583 evt.preventDefault();
1584 break;
1585 }
1586 },
1587
1588
1589 /**
1590 * Serializes the item into an array of string.
1591 * @returns {String[]}
1592 */
1593 toArray: function () {
1594 let order = [],
1595 el,
1596 children = this.el.children,
1597 i = 0,
1598 n = children.length,
1599 options = this.options;
1600
1601 for (; i < n; i++) {
1602 el = children[i];
1603 if (closest(el, options.draggable, this.el, false)) {
1604 order.push(el.getAttribute(options.dataIdAttr) || _generateId(el));
1605 }
1606 }
1607
1608 return order;
1609 },
1610
1611
1612 /**
1613 * Sorts the elements according to the array.
1614 * @param {String[]} order order of the items
1615 */
1616 sort: function (order, useAnimation) {
1617 let items = {}, rootEl = this.el;
1618
1619 this.toArray().forEach(function (id, i) {
1620 let el = rootEl.children[i];
1621
1622 if (closest(el, this.options.draggable, rootEl, false)) {
1623 items[id] = el;
1624 }
1625 }, this);
1626
1627 useAnimation && this.captureAnimationState();
1628 order.forEach(function (id) {
1629 if (items[id]) {
1630 rootEl.removeChild(items[id]);
1631 rootEl.appendChild(items[id]);
1632 }
1633 });
1634 useAnimation && this.animateAll();
1635 },
1636
1637
1638 /**
1639 * Save the current sorting
1640 */
1641 save: function () {
1642 let store = this.options.store;
1643 store && store.set && store.set(this);
1644 },
1645
1646
1647 /**
1648 * 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.
1649 * @param {HTMLElement} el
1650 * @param {String} [selector] default: `options.draggable`
1651 * @returns {HTMLElement|null}
1652 */
1653 closest: function (el, selector) {
1654 return closest(el, selector || this.options.draggable, this.el, false);
1655 },
1656
1657
1658 /**
1659 * Set/get option
1660 * @param {string} name
1661 * @param {*} [value]
1662 * @returns {*}
1663 */
1664 option: function (name, value) {
1665 let options = this.options;
1666
1667 if (value === void 0) {
1668 return options[name];
1669 } else {
1670 let modifiedValue = PluginManager.modifyOption(this, name, value);
1671 if (typeof modifiedValue !== 'undefined') {
1672 options[name] = modifiedValue;
1673 } else {
1674 options[name] = value;
1675 }
1676
1677 if (name === 'group') {
1678 _prepareGroup(options);
1679 }
1680 }
1681 },
1682
1683
1684 /**
1685 * Destroy
1686 */
1687 destroy: function () {
1688 pluginEvent('destroy', this);
1689 let el = this.el;
1690
1691 el[expando] = null;
1692
1693 off(el, 'mousedown', this._onTapStart);
1694 off(el, 'touchstart', this._onTapStart);
1695 off(el, 'pointerdown', this._onTapStart);
1696
1697 if (this.nativeDraggable) {
1698 off(el, 'dragover', this);
1699 off(el, 'dragenter', this);
1700 }
1701 // Remove draggable attributes
1702 Array.prototype.forEach.call(el.querySelectorAll('[draggable]'), function (el) {
1703 el.removeAttribute('draggable');
1704 });
1705
1706 this._onDrop();
1707
1708 this._disableDelayedDragEvents();
1709
1710 sortables.splice(sortables.indexOf(this.el), 1);
1711
1712 this.el = el = null;
1713 },
1714
1715 _hideClone: function() {
1716 if (!cloneHidden) {
1717 pluginEvent('hideClone', this);
1718 if (Sortable.eventCanceled) return;
1719
1720
1721 css(cloneEl, 'display', 'none');
1722 if (this.options.removeCloneOnHide && cloneEl.parentNode) {
1723 cloneEl.parentNode.removeChild(cloneEl);
1724 }
1725 cloneHidden = true;
1726 }
1727 },
1728
1729 _showClone: function(putSortable) {
1730 if (putSortable.lastPutMode !== 'clone') {
1731 this._hideClone();
1732 return;
1733 }
1734
1735
1736 if (cloneHidden) {
1737 pluginEvent('showClone', this);
1738 if (Sortable.eventCanceled) return;
1739
1740 // show clone at dragEl or original position
1741 if (dragEl.parentNode == rootEl && !this.options.group.revertClone) {
1742 rootEl.insertBefore(cloneEl, dragEl);
1743 } else if (nextEl) {
1744 rootEl.insertBefore(cloneEl, nextEl);
1745 } else {
1746 rootEl.appendChild(cloneEl);
1747 }
1748
1749 if (this.options.group.revertClone) {
1750 this.animate(dragEl, cloneEl);
1751 }
1752
1753 css(cloneEl, 'display', '');
1754 cloneHidden = false;
1755 }
1756 }
1757 };
1758
1759 function _globalDragOver(/**Event*/evt) {
1760 if (evt.dataTransfer) {
1761 evt.dataTransfer.dropEffect = 'move';
1762 }
1763 evt.cancelable && evt.preventDefault();
1764 }
1765
1766 function onMove(fromEl, toEl, dragEl, dragRect, targetEl, targetRect, originalEvent, willInsertAfter) {
1767 let evt,
1768 sortable = fromEl[expando],
1769 onMoveFn = sortable.options.onMove,
1770 retVal;
1771 // Support for new CustomEvent feature
1772 if (window.CustomEvent && !IE11OrLess && !Edge) {
1773 evt = new CustomEvent('move', {
1774 bubbles: true,
1775 cancelable: true
1776 });
1777 } else {
1778 evt = document.createEvent('Event');
1779 evt.initEvent('move', true, true);
1780 }
1781
1782 evt.to = toEl;
1783 evt.from = fromEl;
1784 evt.dragged = dragEl;
1785 evt.draggedRect = dragRect;
1786 evt.related = targetEl || toEl;
1787 evt.relatedRect = targetRect || getRect(toEl);
1788 evt.willInsertAfter = willInsertAfter;
1789
1790 evt.originalEvent = originalEvent;
1791
1792 fromEl.dispatchEvent(evt);
1793
1794 if (onMoveFn) {
1795 retVal = onMoveFn.call(sortable, evt, originalEvent);
1796 }
1797
1798 return retVal;
1799 }
1800
1801 function _disableDraggable(el) {
1802 el.draggable = false;
1803 }
1804
1805 function _unsilent() {
1806 _silent = false;
1807 }
1808
1809 function _ghostIsFirst(evt, vertical, sortable) {
1810 let firstElRect = getRect(getChild(sortable.el, 0, sortable.options, true));
1811 const childContainingRect = getChildContainingRectFromElement(sortable.el, sortable.options, ghostEl);
1812 const spacer = 10;
1813
1814 return vertical ?
1815 (evt.clientX < childContainingRect.left - spacer || evt.clientY < firstElRect.top && evt.clientX < firstElRect.right) :
1816 (evt.clientY < childContainingRect.top - spacer || evt.clientY < firstElRect.bottom && evt.clientX < firstElRect.left)
1817 }
1818
1819 function _ghostIsLast(evt, vertical, sortable) {
1820 const lastElRect = getRect(lastChild(sortable.el, sortable.options.draggable));
1821 const childContainingRect = getChildContainingRectFromElement(sortable.el, sortable.options, ghostEl);
1822 const spacer = 10;
1823
1824 return vertical ?
1825 (evt.clientX > childContainingRect.right + spacer || evt.clientY > lastElRect.bottom && evt.clientX > lastElRect.left) :
1826 (evt.clientY > childContainingRect.bottom + spacer || evt.clientX > lastElRect.right && evt.clientY > lastElRect.top);
1827 }
1828
1829 function _getSwapDirection(evt, target, targetRect, vertical, swapThreshold, invertedSwapThreshold, invertSwap, isLastTarget) {
1830 let mouseOnAxis = vertical ? evt.clientY : evt.clientX,
1831 targetLength = vertical ? targetRect.height : targetRect.width,
1832 targetS1 = vertical ? targetRect.top : targetRect.left,
1833 targetS2 = vertical ? targetRect.bottom : targetRect.right,
1834 invert = false;
1835
1836
1837 if (!invertSwap) {
1838 // Never invert or create dragEl shadow when target movemenet causes mouse to move past the end of regular swapThreshold
1839 if (isLastTarget && targetMoveDistance < targetLength * swapThreshold) { // multiplied only by swapThreshold because mouse will already be inside target by (1 - threshold) * targetLength / 2
1840 // check if past first invert threshold on side opposite of lastDirection
1841 if (!pastFirstInvertThresh &&
1842 (lastDirection === 1 ?
1843 (
1844 mouseOnAxis > targetS1 + targetLength * invertedSwapThreshold / 2
1845 ) :
1846 (
1847 mouseOnAxis < targetS2 - targetLength * invertedSwapThreshold / 2
1848 )
1849 )
1850 )
1851 {
1852 // past first invert threshold, do not restrict inverted threshold to dragEl shadow
1853 pastFirstInvertThresh = true;
1854 }
1855
1856 if (!pastFirstInvertThresh) {
1857 // dragEl shadow (target move distance shadow)
1858 if (
1859 lastDirection === 1 ?
1860 (
1861 mouseOnAxis < targetS1 + targetMoveDistance // over dragEl shadow
1862 ) :
1863 (
1864 mouseOnAxis > targetS2 - targetMoveDistance
1865 )
1866 )
1867 {
1868 return -lastDirection;
1869 }
1870 } else {
1871 invert = true;
1872 }
1873 } else {
1874 // Regular
1875 if (
1876 mouseOnAxis > targetS1 + (targetLength * (1 - swapThreshold) / 2) &&
1877 mouseOnAxis < targetS2 - (targetLength * (1 - swapThreshold) / 2)
1878 ) {
1879 return _getInsertDirection(target);
1880 }
1881 }
1882 }
1883
1884 invert = invert || invertSwap;
1885
1886 if (invert) {
1887 // Invert of regular
1888 if (
1889 mouseOnAxis < targetS1 + (targetLength * invertedSwapThreshold / 2) ||
1890 mouseOnAxis > targetS2 - (targetLength * invertedSwapThreshold / 2)
1891 )
1892 {
1893 return ((mouseOnAxis > targetS1 + targetLength / 2) ? 1 : -1);
1894 }
1895 }
1896
1897 return 0;
1898 }
1899
1900 /**
1901 * Gets the direction dragEl must be swapped relative to target in order to make it
1902 * seem that dragEl has been "inserted" into that element's position
1903 * @param {HTMLElement} target The target whose position dragEl is being inserted at
1904 * @return {Number} Direction dragEl must be swapped
1905 */
1906 function _getInsertDirection(target) {
1907 if (index(dragEl) < index(target)) {
1908 return 1;
1909 } else {
1910 return -1;
1911 }
1912 }
1913
1914
1915 /**
1916 * Generate id
1917 * @param {HTMLElement} el
1918 * @returns {String}
1919 * @private
1920 */
1921 function _generateId(el) {
1922 let str = el.tagName + el.className + el.src + el.href + el.textContent,
1923 i = str.length,
1924 sum = 0;
1925
1926 while (i--) {
1927 sum += str.charCodeAt(i);
1928 }
1929
1930 return sum.toString(36);
1931 }
1932
1933 function _saveInputCheckedState(root) {
1934 savedInputChecked.length = 0;
1935
1936 let inputs = root.getElementsByTagName('input');
1937 let idx = inputs.length;
1938
1939 while (idx--) {
1940 let el = inputs[idx];
1941 el.checked && savedInputChecked.push(el);
1942 }
1943 }
1944
1945 function _nextTick(fn) {
1946 return setTimeout(fn, 0);
1947 }
1948
1949 function _cancelNextTick(id) {
1950 return clearTimeout(id);
1951 }
1952
1953 // Fixed #973:
1954 if (documentExists) {
1955 on(document, 'touchmove', function(evt) {
1956 if ((Sortable.active || awaitingDragStarted) && evt.cancelable) {
1957 evt.preventDefault();
1958 }
1959 });
1960 }
1961
1962
1963 // Export utils
1964 Sortable.utils = {
1965 on,
1966 off,
1967 css,
1968 find,
1969 is: function (el, selector) {
1970 return !!closest(el, selector, el, false);
1971 },
1972 extend,
1973 throttle,
1974 closest,
1975 toggleClass,
1976 clone,
1977 index,
1978 nextTick: _nextTick,
1979 cancelNextTick: _cancelNextTick,
1980 detectDirection: _detectDirection,
1981 getChild,
1982 expando
1983 };
1984
1985
1986 /**
1987 * Get the Sortable instance of an element
1988 * @param {HTMLElement} element The element
1989 * @return {Sortable|undefined} The instance of Sortable
1990 */
1991 Sortable.get = function(element) {
1992 return element[expando];
1993 };
1994
1995 /**
1996 * Mount a plugin to Sortable
1997 * @param {...SortablePlugin|SortablePlugin[]} plugins Plugins being mounted
1998 */
1999 Sortable.mount = function(...plugins) {
2000 if (plugins[0].constructor === Array) plugins = plugins[0];
2001
2002 plugins.forEach((plugin) => {
2003 if (!plugin.prototype || !plugin.prototype.constructor) {
2004 throw `Sortable: Mounted plugin must be a constructor function, not ${ {}.toString.call(plugin) }`;
2005 }
2006 if (plugin.utils) Sortable.utils = { ...Sortable.utils, ...plugin.utils };
2007
2008 PluginManager.mount(plugin);
2009 });
2010 };
2011
2012
2013
2014 /**
2015 * Create sortable instance
2016 * @param {HTMLElement} el
2017 * @param {Object} [options]
2018 */
2019 Sortable.create = function (el, options) {
2020 return new Sortable(el, options);
2021 };
2022
2023
2024 // Export
2025 Sortable.version = version;
2026
2027
2028 export default Sortable;
2029