1/**! 2 * Sortable 3 * @author RubaXa <[email protected]> 4 * @license MIT 5 */ 6 7 8(function(factory) { 9 "use strict"; 10 11 if (typeof define === "function" && define.amd) { 12 define(factory); 13 } else if (typeof module != "undefined" && typeof module.exports != "undefined") { 14 module.exports = factory(); 15 } else if (typeof Package !== "undefined") { 16 Sortable = factory(); // export for Meteor.js 17 } else { 18 /* jshint sub:true */ 19 window["Sortable"] = factory(); 20 } 21})(function() { 22 "use strict"; 23 24 var dragEl, 25 parentEl, 26 ghostEl, 27 cloneEl, 28 rootEl, 29 nextEl, 30 31 scrollEl, 32 scrollParentEl, 33 34 lastEl, 35 lastCSS, 36 lastParentCSS, 37 38 oldIndex, 39 newIndex, 40 41 activeGroup, 42 autoScroll = {}, 43 44 tapEvt, 45 touchEvt, 46 47 moved, 48 49 /** @const */ 50 RSPACE = /\s+/g, 51 52 expando = 'Sortable' + (new Date).getTime(), 53 54 win = window, 55 document = win.document, 56 parseInt = win.parseInt, 57 58 supportDraggable = !!('draggable' in document.createElement('div')), 59 supportCssPointerEvents = (function(el) { 60 el = document.createElement('x'); 61 el.style.cssText = 'pointer-events:auto'; 62 return el.style.pointerEvents === 'auto'; 63 })(), 64 65 _silent = false, 66 67 abs = Math.abs, 68 slice = [].slice, 69 70 touchDragOverListeners = [], 71 72 _autoScroll = _throttle(function( /**Event*/ evt, /**Object*/ options, /**HTMLElement*/ rootEl) { 73 // Bug: https://bugzilla.mozilla.org/show_bug.cgi?id=505521 74 if (rootEl && options.scroll) { 75 var el, 76 rect, 77 sens = options.scrollSensitivity, 78 speed = options.scrollSpeed, 79 80 x = evt.clientX, 81 y = evt.clientY, 82 83 winWidth = window.innerWidth, 84 winHeight = window.innerHeight, 85 86 vx, 87 vy; 88 89 // Delect scrollEl 90 if (scrollParentEl !== rootEl) { 91 scrollEl = options.scroll; 92 scrollParentEl = rootEl; 93 94 if (scrollEl === true) { 95 scrollEl = rootEl; 96 97 do { 98 if ((scrollEl.offsetWidth < scrollEl.scrollWidth) || 99 (scrollEl.offsetHeight < scrollEl.scrollHeight) 100 ) { 101 break; 102 } 103 /* jshint boss:true */ 104 } while (scrollEl = scrollEl.parentNode); 105 } 106 } 107 108 if (scrollEl) { 109 el = scrollEl; 110 rect = scrollEl.getBoundingClientRect(); 111 vx = (abs(rect.right - x) <= sens) - (abs(rect.left - x) <= sens); 112 vy = (abs(rect.bottom - y) <= sens) - (abs(rect.top - y) <= sens); 113 } 114 115 116 if (!(vx || vy)) { 117 vx = (winWidth - x <= sens) - (x <= sens); 118 vy = (winHeight - y <= sens) - (y <= sens); 119 120 /* jshint expr:true */ 121 (vx || vy) && (el = win); 122 } 123 124 125 if (autoScroll.vx !== vx || autoScroll.vy !== vy || autoScroll.el !== el) { 126 autoScroll.el = el; 127 autoScroll.vx = vx; 128 autoScroll.vy = vy; 129 130 clearInterval(autoScroll.pid); 131 132 if (el) { 133 autoScroll.pid = setInterval(function() { 134 if (el === win) { 135 win.scrollTo(win.pageXOffset + vx * speed, win.pageYOffset + vy * speed); 136 } else { 137 vy && (el.scrollTop += vy * speed); 138 vx && (el.scrollLeft += vx * speed); 139 } 140 }, 24); 141 } 142 } 143 } 144 }, 30), 145 146 _prepareGroup = function(options) { 147 var group = options.group; 148 149 if (!group || typeof group != 'object') { 150 group = options.group = { 151 name: group 152 }; 153 } 154 155 ['pull', 'put'].forEach(function(key) { 156 if (!(key in group)) { 157 group[key] = true; 158 } 159 }); 160 161 options.groups = ' ' + group.name + (group.put.join ? ' ' + group.put.join(' ') : '') + ' '; 162 }; 163 164 165 166 /** 167 * @class Sortable 168 * @param {HTMLElement} el 169 * @param {Object} [options] 170 */ 171 function Sortable(el, options) { 172 if (!(el && el.nodeType && el.nodeType === 1)) { 173 throw 'Sortable: `el` must be HTMLElement, and not ' + {}.toString.call(el); 174 } 175 176 this.el = el; // root element 177 this.options = options = _extend({}, options); 178 179 180 // Export instance 181 el[expando] = this; 182 183 184 // Default options 185 var defaults = { 186 group: Math.random(), 187 sort: true, 188 disabled: false, 189 store: null, 190 handle: null, 191 scroll: true, 192 scrollSensitivity: 30, 193 scrollSpeed: 10, 194 draggable: /[uo]l/i.test(el.nodeName) ? 'li' : '>*', 195 ghostClass: 'sortable-ghost', 196 chosenClass: 'sortable-chosen', 197 dragClass: 'sortable-drag', 198 ignore: 'a, img', 199 filter: null, 200 animation: 0, 201 setData: function(dataTransfer, dragEl) { 202 dataTransfer.setData('Text', dragEl.textContent); 203 }, 204 dropBubble: false, 205 dragoverBubble: false, 206 dataIdAttr: 'data-id', 207 delay: 0, 208 forceFallback: false, 209 fallbackClass: 'sortable-fallback', 210 fallbackOnBody: false 211 }; 212 213 214 // Set default options 215 for (var name in defaults) { 216 !(name in options) && (options[name] = defaults[name]); 217 } 218 219 _prepareGroup(options); 220 221 // Bind all private methods 222 for (var fn in this) { 223 if (fn.charAt(0) === '_') { 224 this[fn] = this[fn].bind(this); 225 } 226 } 227 228 // Setup drag mode 229 this.nativeDraggable = options.forceFallback ? false : supportDraggable; 230 231 // Bind events 232 _on(el, 'mousedown', this._onTapStart); 233 _on(el, 'touchstart', this._onTapStart); 234 235 if (this.nativeDraggable) { 236 _on(el, 'dragover', this); 237 _on(el, 'dragenter', this); 238 } 239 240 touchDragOverListeners.push(this._onDragOver); 241 242 // Restore sorting 243 options.store && this.sort(options.store.get(this)); 244 } 245 246
247 Sortable.prototype = /** @lends Sortable.prototype */ { 248 constructor: Sortable, 249 250 _onTapStart: function( /** Event|TouchEvent */ evt) { 251 var _this = this, 252 el = this.el, 253 options = this.options, 254 type = evt.type, 255 touch = evt.touches && evt.touches[0], 256 target = (touch || evt).target, 257 originalTarget = target, 258 filter = options.filter; 259 260 261 if (type === 'mousedown' && evt.button !== 0 || options.disabled) { 262 return; // only left button or enabled 263 } 264 265 target = _closest(target, options.draggable, el); 266 267 if (!target) { 268 return; 269 } 270 271 // get the index of the dragged element within its parent 272 oldIndex = _index(target); 273 274 // Check filter 275 if (typeof filter === 'function') { 276 if (filter.call(this, evt, target, this)) { 277 _dispatchEvent(_this, originalTarget, 'filter', target, el, oldIndex); 278 evt.preventDefault(); 279 return; // cancel dnd 280 } 281 } else if (filter) { 282 filter = filter.split(',').some(function(criteria) { 283 criteria = _closest(originalTarget, criteria.trim(), el); 284 285 if (criteria) { 286 _dispatchEvent(_this, criteria, 'filter', target, el, oldIndex); 287 return true; 288 } 289 }); 290 291 if (filter) { 292 evt.preventDefault(); 293 return; // cancel dnd 294 } 295 } 296 297 298 if (options.handle && !_closest(originalTarget, options.handle, el)) { 299 return; 300 } 301 302 303 // Prepare `dragstart` 304 this._prepareDragStart(evt, touch, target); 305 }, 306 307 _prepareDragStart: function( /** Event */ evt, /** Touch */ touch, /** HTMLElement */ target) { 308 var _this = this, 309 el = _this.el, 310 options = _this.options, 311 ownerDocument = el.ownerDocument, 312 dragStartFn; 313 314 if (target && !dragEl && (target.parentNode === el)) { 315 tapEvt = evt; 316 317 rootEl = el; 318 dragEl = target; 319 parentEl = dragEl.parentNode; 320 nextEl = dragEl.nextSibling; 321 activeGroup = options.group; 322 323 dragStartFn = function() { 324 // Delayed drag has been triggered 325 // we can re-enable the events: touchmove/mousemove 326 _this._disableDelayedDrag(); 327 328 // Make the element draggable 329 dragEl.draggable = true; 330 331 // Chosen item 332 _toggleClass(dragEl, _this.options.chosenClass, true); 333 334 // Bind the events: dragstart/dragend 335 _this._triggerDragStart(touch); 336 }; 337 338 // Disable "draggable" 339 options.ignore.split(',').forEach(function(criteria) { 340 _find(dragEl, criteria.trim(), _disableDraggable); 341 }); 342 343 _on(ownerDocument, 'mouseup', _this._onDrop); 344 _on(ownerDocument, 'touchend', _this._onDrop); 345 _on(ownerDocument, 'touchcancel', _this._onDrop); 346 347 if (options.delay) { 348 // If the user moves the pointer or let go the click or touch 349 // before the delay has been reached: 350 // disable the delayed drag 351 _on(ownerDocument, 'mouseup', _this._disableDelayedDrag); 352 _on(ownerDocument, 'touchend', _this._disableDelayedDrag); 353 _on(ownerDocument, 'touchcancel', _this._disableDelayedDrag); 354 _on(ownerDocument, 'mousemove', _this._disableDelayedDrag); 355 _on(ownerDocument, 'touchmove', _this._disableDelayedDrag); 356 357 _this._dragStartTimer = setTimeout(dragStartFn, options.delay); 358 } else { 359 dragStartFn(); 360 } 361 } 362 }, 363 364 _disableDelayedDrag: function() { 365 var ownerDocument = this.el.ownerDocument; 366 367 clearTimeout(this._dragStartTimer); 368 _off(ownerDocument, 'mouseup', this._disableDelayedDrag); 369 _off(ownerDocument, 'touchend', this._disableDelayedDrag); 370 _off(ownerDocument, 'touchcancel', this._disableDelayedDrag); 371 _off(ownerDocument, 'mousemove', this._disableDelayedDrag); 372 _off(ownerDocument, 'touchmove', this._disableDelayedDrag); 373 }, 374 375 _triggerDragStart: function( /** Touch */ touch) { 376 if (touch) { 377 // Touch device support 378 tapEvt = { 379 target: dragEl, 380 clientX: touch.clientX, 381 clientY: touch.clientY 382 }; 383 384 this._onDragStart(tapEvt, 'touch'); 385 } else if (!this.nativeDraggable) { 386 this._onDragStart(tapEvt, true); 387 } else { 388 _on(dragEl, 'dragend', this); 389 _on(rootEl, 'dragstart', this._onDragStart); 390 } 391 392 try { 393 if (document.selection) { 394 document.selection.empty(); 395 } else { 396 window.getSelection().removeAllRanges(); 397 } 398 } catch (err) {} 399 }, 400 401 _dragStarted: function() { 402 if (rootEl && dragEl) { 403 // Apply effect 404 _toggleClass(dragEl, this.options.ghostClass, true); 405 _toggleClass(dragEl, this.options.dragClass, false); 406 407 Sortable.active = this; 408 409 // Drag start event 410 _dispatchEvent(this, rootEl, 'start', dragEl, rootEl, oldIndex); 411 } 412 }, 413 414 _emulateDragOver: function() { 415 if (touchEvt) { 416 if (this._lastX === touchEvt.clientX && this._lastY === touchEvt.clientY) { 417 return; 418 } 419 420 this._lastX = touchEvt.clientX; 421 this._lastY = touchEvt.clientY; 422 423 if (!supportCssPointerEvents) { 424 _css(ghostEl, 'display', 'none'); 425 } 426 427 var target = document.elementFromPoint(touchEvt.clientX, touchEvt.clientY), 428 parent = target, 429 groupName = ' ' + this.options.group.name + '', 430 i = touchDragOverListeners.length; 431 432 if (parent) { 433 do { 434 if (parent[expando] && parent[expando].options.groups.indexOf(groupName) > -1) { 435 while (i--) { 436 touchDragOverListeners[i]({ 437 clientX: touchEvt.clientX, 438 clientY: touchEvt.clientY, 439 target: target, 440 rootEl: parent 441 }); 442 } 443 444 break; 445 } 446 447 target = parent; // store last element 448 } 449 /* jshint boss:true */ 450 while (parent = parent.parentNode); 451 } 452 453 if (!supportCssPointerEvents) { 454 _css(ghostEl, 'display', ''); 455 } 456 } 457 }, 458 459 460 _onTouchMove: function( /**TouchEvent*/ evt) { 461 if (tapEvt) { 462 // only set the status to dragging, when we are actually dragging 463 if (!Sortable.active) { 464 this._dragStarted(); 465 } 466 467 // as well as creating the ghost element on the document body 468 this._appendGhost(); 469 470 var touch = evt.touches ? evt.touches[0] : evt, 471 dx = touch.clientX - tapEvt.clientX, 472 dy = touch.clientY - tapEvt.clientY, 473 translate3d = evt.touches ? 'translate3d(' + dx + 'px,' + dy + 'px,0)' : 'translate(' + dx + 'px,' + dy + 'px)'; 474 475 moved = true; 476 touchEvt = touch; 477 478 _css(ghostEl, 'webkitTransform', translate3d); 479 _css(ghostEl, 'mozTransform', translate3d); 480 _css(ghostEl, 'msTransform', translate3d); 481 _css(ghostEl, 'transform', translate3d); 482 483 evt.preventDefault(); 484 } 485 }, 486 487 _appendGhost: function() { 488 if (!ghostEl) { 489 var rect = dragEl.getBoundingClientRect(), 490 css = _css(dragEl), 491 ghostRect; 492 493 ghostEl = dragEl.cloneNode(true); 494 495 _toggleClass(ghostEl, this.options.ghostClass, false); 496 _toggleClass(ghostEl, this.options.fallbackClass, true); 497 _toggleClass(ghostEl, this.options.dragClass, true); 498 499 _css(ghostEl, 'top', rect.top - parseInt(css.marginTop, 10)); 500 _css(ghostEl, 'left', rect.left - parseInt(css.marginLeft, 10)); 501 _css(ghostEl, 'width', rect.width);
502 _css(ghostEl, 'height', rect.height); 503 _css(ghostEl, 'opacity', '0.8'); 504 _css(ghostEl, 'position', 'fixed'); 505 _css(ghostEl, 'zIndex', '100000'); 506 _css(ghostEl, 'pointerEvents', 'none'); 507 508 this.options.fallbackOnBody && document.body.appendChild(ghostEl) || rootEl.appendChild(ghostEl); 509 510 // Fixing dimensions. 511 ghostRect = ghostEl.getBoundingClientRect(); 512 _css(ghostEl, 'width', rect.width * 2 - ghostRect.width); 513 _css(ghostEl, 'height', rect.height * 2 - ghostRect.height); 514 } 515 }, 516 517 _onDragStart: function( /**Event*/ evt, /**boolean*/ useFallback) { 518 var dataTransfer = evt.dataTransfer, 519 options = this.options; 520 521 this._offUpEvents(); 522 523 if (activeGroup.pull == 'clone') { 524 cloneEl = dragEl.cloneNode(true); 525 _css(cloneEl, 'display', 'none'); 526 rootEl.insertBefore(cloneEl, dragEl); 527 } 528 529 _toggleClass(dragEl, options.dragClass, true); 530 531 if (useFallback) { 532 533 if (useFallback === 'touch') { 534 // Bind touch events 535 _on(document, 'touchmove', this._onTouchMove); 536 _on(document, 'touchend', this._onDrop); 537 _on(document, 'touchcancel', this._onDrop); 538 } else { 539 // Old brwoser 540 _on(document, 'mousemove', this._onTouchMove); 541 _on(document, 'mouseup', this._onDrop); 542 } 543 544 this._loopId = setInterval(this._emulateDragOver, 50); 545 } else { 546 if (dataTransfer) { 547 dataTransfer.effectAllowed = 'move'; 548 options.setData && options.setData.call(this, dataTransfer, dragEl); 549 } 550 551 _on(document, 'drop', this); 552 setTimeout(this._dragStarted, 0); 553 } 554 }, 555 556 _onDragOver: function( /**Event*/ evt) { 557 var el = this.el, 558 target, 559 dragRect, 560 revert, 561 options = this.options, 562 group = options.group, 563 groupPut = group.put, 564 isOwner = (activeGroup === group), 565 canSort = options.sort; 566 567 if (evt.preventDefault !== void 0) { 568 evt.preventDefault(); 569 !options.dragoverBubble && evt.stopPropagation(); 570 } 571 572 moved = true; 573 574 if (activeGroup && !options.disabled && 575 (isOwner ? canSort || (revert = !rootEl.contains(dragEl)) // Reverting item into the original list 576 : activeGroup.pull && groupPut && ( 577 (activeGroup.name === group.name) || // by Name 578 (groupPut.indexOf && ~groupPut.indexOf(activeGroup.name)) // by Array 579 ) 580 ) && 581 (evt.rootEl === void 0 || evt.rootEl === this.el) // touch fallback 582 ) { 583 // Smart auto-scrolling 584 _autoScroll(evt, options, this.el); 585 586 if (_silent) { 587 return; 588 } 589 590 target = _closest(evt.target, options.draggable, el); 591 dragRect = dragEl.getBoundingClientRect(); 592 593 if (revert) { 594 _cloneHide(true); 595 596 if (cloneEl || nextEl) { 597 rootEl.insertBefore(dragEl, cloneEl || nextEl); 598 } else if (!canSort) { 599 rootEl.appendChild(dragEl); 600 } 601 602 return; 603 } 604 605 606 if ((el.children.length === 0) || (el.children[0] === ghostEl) || 607 (el === evt.target) && (target = _ghostIsLast(el, evt)) 608 ) { 609 610 if (target) { 611 if (target.animated) { 612 return; 613 } 614 615 targetRect = target.getBoundingClientRect(); 616 } 617 618 _cloneHide(isOwner); 619 620 if (_onMove(rootEl, el, dragEl, dragRect, target, targetRect) !== false) { 621 if (!dragEl.contains(el)) { 622 el.appendChild(dragEl); 623 parentEl = el; // actualization 624 } 625 626 this._animate(dragRect, dragEl); 627 target && this._animate(targetRect, target); 628 } 629 } else if (target && !target.animated && target !== dragEl && (target.parentNode[expando] !== void 0)) { 630 if (lastEl !== target) { 631 lastEl = target; 632 lastCSS = _css(target); 633 lastParentCSS = _css(target.parentNode); 634 } 635 636 637 var targetRect = target.getBoundingClientRect(), 638 width = targetRect.right - targetRect.left, 639 height = targetRect.bottom - targetRect.top, 640 floating = /left|right|inline/.test(lastCSS.cssFloat + lastCSS.display) || (lastParentCSS.display == 'flex' && lastParentCSS['flex-direction'].indexOf('row') === 0), 641 isWide = (target.offsetWidth > dragEl.offsetWidth), 642 isLong = (target.offsetHeight > dragEl.offsetHeight), 643 halfway = (floating ? (evt.clientX - targetRect.left) / width : (evt.clientY - targetRect.top) / height) > 0.5, 644 nextSibling = target.nextElementSibling, 645 moveVector = _onMove(rootEl, el, dragEl, dragRect, target, targetRect), 646 after; 647 648 if (moveVector !== false) { 649 _silent = true; 650 setTimeout(_unsilent, 30); 651 652 _cloneHide(isOwner); 653 654 if (moveVector === 1 || moveVector === -1) { 655 after = (moveVector === 1); 656 } else if (floating) { 657 var elTop = dragEl.offsetTop, 658 tgTop = target.offsetTop; 659 660 if (elTop === tgTop) { 661 after = (target.previousElementSibling === dragEl) && !isWide || halfway && isWide; 662 } else { 663 after = tgTop > elTop; 664 } 665 } else { 666 after = (nextSibling !== dragEl) && !isLong || halfway && isLong; 667 } 668 669 if (!dragEl.contains(el)) { 670 if (after && !nextSibling) { 671 el.appendChild(dragEl); 672 } else { 673 target.parentNode.insertBefore(dragEl, after ? nextSibling : target); 674 } 675 } 676 677 parentEl = dragEl.parentNode; // actualization 678 679 this._animate(dragRect, dragEl); 680 this._animate(targetRect, target); 681 } 682 } 683 } else { 684 isOwner ? canSort || (revert = !rootEl.contains(dragEl)) // Reverting item into the original list 685 : activeGroup.pull && groupPut && ( 686 (activeGroup.name === group.name) || // by Name 687 (groupPut.indexOf && ~groupPut.indexOf(activeGroup.name)) // by Array 688 ) 689 if (isOwner) { 690 _onMoveToSelf(rootEl, dragEl); 691 } 692 } 693 }, 694 695 _animate: function(prevRect, target) { 696 var ms = this.options.animation; 697 698 if (ms) { 699 var currentRect = target.getBoundingClientRect(); 700 701 _css(target, 'transition', 'none'); 702 _css(target, 'transform', 'translate3d(' + (prevRect.left - currentRect.left) + 'px,' + (prevRect.top - currentRect.top) + 'px,0)'); 703 704 target.offsetWidth; // repaint 705 706 _css(target, 'transition', 'all ' + ms + 'ms');
707 _css(target, 'transform', 'translate3d(0,0,0)'); 708 709 clearTimeout(target.animated); 710 target.animated = setTimeout(function() { 711 _css(target, 'transition', ''); 712 _css(target, 'transform', ''); 713 target.animated = false; 714 }, ms); 715 } 716 }, 717 718 _offUpEvents: function() { 719 var ownerDocument = this.el.ownerDocument; 720 721 _off(document, 'touchmove', this._onTouchMove); 722 _off(ownerDocument, 'mouseup', this._onDrop); 723 _off(ownerDocument, 'touchend', this._onDrop); 724 _off(ownerDocument, 'touchcancel', this._onDrop); 725 }, 726 727 _onDrop: function( /**Event*/ evt) { 728 var el = this.el, 729 options = this.options; 730 731 clearInterval(this._loopId); 732 clearInterval(autoScroll.pid); 733 clearTimeout(this._dragStartTimer); 734 735 // Unbind events 736 _off(document, 'mousemove', this._onTouchMove); 737 738 if (this.nativeDraggable) { 739 _off(document, 'drop', this); 740 _off(el, 'dragstart', this._onDragStart); 741 } 742 743 this._offUpEvents(); 744 745 if (evt) { 746 if (moved) { 747 evt.preventDefault(); 748 !options.dropBubble && evt.stopPropagation(); 749 } 750 751 ghostEl && ghostEl.parentNode.removeChild(ghostEl); 752 753 if (dragEl) { 754 if (this.nativeDraggable) { 755 _off(dragEl, 'dragend', this); 756 } 757 758 _disableDraggable(dragEl); 759 760 // Remove class's 761 _toggleClass(dragEl, this.options.ghostClass, false); 762 _toggleClass(dragEl, this.options.chosenClass, false); 763 764 if (rootEl !== parentEl) { 765 newIndex = _index(dragEl); 766 767 if (newIndex >= 0) { 768 // drag from one list and drop into another 769 _dispatchEvent(null, parentEl, 'sort', dragEl, rootEl, oldIndex, newIndex); 770 _dispatchEvent(this, rootEl, 'sort', dragEl, rootEl, oldIndex, newIndex); 771 772 // Add event 773 _dispatchEvent(null, parentEl, 'add', dragEl, rootEl, oldIndex, newIndex); 774 775 // Remove event 776 _dispatchEvent(this, rootEl, 'remove', dragEl, rootEl, oldIndex, newIndex); 777 } 778 } else { 779 // Remove clone 780 cloneEl && cloneEl.parentNode.removeChild(cloneEl); 781 782 if (dragEl.nextSibling !== nextEl) { 783 // Get the index of the dragged element within its parent 784 newIndex = _index(dragEl); 785 786 if (newIndex >= 0) { 787 // drag & drop within the same list 788 _dispatchEvent(this, rootEl, 'update', dragEl, rootEl, oldIndex, newIndex); 789 _dispatchEvent(this, rootEl, 'sort', dragEl, rootEl, oldIndex, newIndex); 790 } 791 } 792 } 793 794 if (Sortable.active) { 795 if (newIndex === null || newIndex === -1) { 796 newIndex = oldIndex; 797 } 798 799 _dispatchEvent(this, rootEl, 'end', dragEl, rootEl, oldIndex, newIndex); 800 801 // Save sorting 802 this.save(); 803 } 804 } 805 806 // Nulling 807 rootEl = 808 dragEl = 809 parentEl = 810 ghostEl = 811 nextEl = 812 cloneEl = 813 814 scrollEl = 815 scrollParentEl = 816 817 tapEvt = 818 touchEvt = 819 820 moved = 821 newIndex = 822 823 lastEl = 824 lastCSS = 825 826 activeGroup = 827 Sortable.active = null; 828 } 829 }, 830 831 832 handleEvent: function( /**Event*/ evt) { 833 var type = evt.type; 834 835 if (type === 'dragover' || type === 'dragenter') { 836 if (dragEl) { 837 this._onDragOver(evt); 838 _globalDragOver(evt); 839 } 840 } else if (type === 'drop' || type === 'dragend') { 841 this._onDrop(evt); 842 } 843 }, 844 845 846 /** 847 * Serializes the item into an array of string. 848 * @returns {String[]} 849 */ 850 toArray: function() { 851 var order = [], 852 el, 853 children = this.el.children, 854 i = 0, 855 n = children.length, 856 options = this.options; 857 858 for (; i < n; i++) { 859 el = children[i]; 860 if (_closest(el, options.draggable, this.el)) { 861 order.push(el.getAttribute(options.dataIdAttr) || _generateId(el)); 862 } 863 } 864 865 return order; 866 }, 867 868 869 /** 870 * Sorts the elements according to the array. 871 * @param {String[]} order order of the items 872 */ 873 sort: function(order) { 874 var items = {}, 875 rootEl = this.el; 876 877 this.toArray().forEach(function(id, i) { 878 var el = rootEl.children[i]; 879 880 if (_closest(el, this.options.draggable, rootEl)) { 881 items[id] = el; 882 } 883 }, this); 884 885 order.forEach(function(id) { 886 if (items[id]) { 887 rootEl.removeChild(items[id]); 888 rootEl.appendChild(items[id]); 889 } 890 }); 891 }, 892 893 894 /** 895 * Save the current sorting 896 */ 897 save: function() { 898 var store = this.options.store; 899 store && store.set(this); 900 }, 901 902 903 /** 904 * 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. 905 * @param {HTMLElement} el
906 * @param {String} [selector] default: `options.draggable` 907 * @returns {HTMLElement|null} 908 */ 909 closest: function(el, selector) { 910 return _closest(el, selector || this.options.draggable, this.el); 911 }, 912 913 914 /** 915 * Set/get option 916 * @param {string} name 917 * @param {*} [value] 918 * @returns {*} 919 */ 920 option: function(name, value) { 921 var options = this.options; 922 923 if (value === void 0) { 924 return options[name]; 925 } else { 926 options[name] = value; 927 928 if (name === 'group') { 929 _prepareGroup(options); 930 } 931 } 932 }, 933 934 935 /** 936 * Destroy 937 */ 938 destroy: function() { 939 var el = this.el; 940 941 el[expando] = null; 942 943 _off(el, 'mousedown', this._onTapStart); 944 _off(el, 'touchstart', this._onTapStart); 945 946 if (this.nativeDraggable) { 947 _off(el, 'dragover', this); 948 _off(el, 'dragenter', this); 949 } 950 951 // Remove draggable attributes 952 Array.prototype.forEach.call(el.querySelectorAll('[draggable]'), function(el) { 953 el.removeAttribute('draggable'); 954 }); 955 956 touchDragOverListeners.splice(touchDragOverListeners.indexOf(this._onDragOver), 1); 957 958 this._onDrop(); 959 960 this.el = el = null; 961 } 962 }; 963 964 965 function _cloneHide(state) { 966 if (cloneEl && (cloneEl.state !== state)) { 967 _css(cloneEl, 'display', state ? 'none' : ''); 968 !state && cloneEl.state && rootEl.insertBefore(cloneEl, dragEl); 969 cloneEl.state = state; 970 } 971 } 972 973 974 function _closest( /**HTMLElement*/ el, /**String*/ selector, /**HTMLElement*/ ctx) { 975 if (el) { 976 ctx = ctx || document; 977 selector = selector.split('.'); 978 979 var tag = selector.shift().toUpperCase(), 980 re = new RegExp('\\s(' + selector.join('|') + ')(?=\\s)', 'g'); 981 982 do { 983 if ( 984 (tag === '>*' && el.parentNode === ctx) || ( 985 (tag === '' || el.nodeName.toUpperCase() == tag) && 986 (!selector.length || ((' ' + el.className + ' ').match(re) || []).length == selector.length) 987 ) 988 ) { 989 return el; 990 } 991 } 992 while (el !== ctx && (el = el.parentNode)); 993 } 994 995 return null; 996 } 997 998 999 function _globalDragOver( /**Event*/ evt) { 1000 if (evt.dataTransfer) { 1001 evt.dataTransfer.dropEffect = 'move'; 1002 } 1003 evt.preventDefault(); 1004 } 1005 1006 1007 function _on(el, event, fn) { 1008 el.addEventListener(event, fn, false); 1009 } 1010 1011 1012 function _off(el, event, fn) { 1013 el.removeEventListener(event, fn, false); 1014 } 1015 1016 1017 function _toggleClass(el, name, state) { 1018 if (el) { 1019 if (el.classList) { 1020 el.classList[state ? 'add' : 'remove'](name); 1021 } else { 1022 var className = (' ' + el.className + ' ').replace(RSPACE, ' ').replace(' ' + name + ' ', ' '); 1023 el.className = (className + (state ? ' ' + name : '')).replace(RSPACE, ' '); 1024 } 1025 } 1026 } 1027 1028 1029 function _css(el, prop, val) { 1030 var style = el && el.style; 1031 1032 if (style) { 1033 if (val === void 0) { 1034 if (document.defaultView && document.defaultView.getComputedStyle) { 1035 val = document.defaultView.getComputedStyle(el, ''); 1036 } else if (el.currentStyle) { 1037 val = el.currentStyle; 1038 } 1039 1040 return prop === void 0 ? val : val[prop]; 1041 } else { 1042 if (!(prop in style)) { 1043 prop = '-webkit-' + prop; 1044 } 1045 1046 style[prop] = val + (typeof val === 'string' ? '' : 'px'); 1047 } 1048 } 1049 } 1050 1051 1052 function _find(ctx, tagName, iterator) { 1053 if (ctx) { 1054 var list = ctx.getElementsByTagName(tagName), 1055 i = 0, 1056 n = list.length; 1057 1058 if (iterator) { 1059 for (; i < n; i++) { 1060 iterator(list[i], i); 1061 } 1062 } 1063 1064 return list; 1065 } 1066 1067 return []; 1068 } 1069 1070 1071 1072 function _dispatchEvent(sortable, rootEl, name, targetEl, fromEl, startIndex, newIndex) { 1073 var evt = document.createEvent('Event'), 1074 options = (sortable || rootEl[expando]).options, 1075 onName = 'on' + name.charAt(0).toUpperCase() + name.substr(1); 1076 1077 evt.initEvent(name, true, true); 1078 1079 evt.to = rootEl; 1080 evt.from = fromEl || rootEl; 1081 evt.item = targetEl || rootEl; 1082 evt.clone = cloneEl; 1083 1084 evt.oldIndex = startIndex; 1085 evt.newIndex = newIndex; 1086 1087 rootEl.dispatchEvent(evt); 1088 1089 if (options[onName]) { 1090 options[onName].call(sortable, evt); 1091 } 1092 } 1093 1094 1095 function _onMove(fromEl, toEl, dragEl, dragRect, targetEl, targetRect) { 1096 var evt, 1097 sortable = fromEl[expando], 1098 onMoveFn = sortable.options.onMove, 1099 retVal; 1100 1101 evt = document.createEvent('Event'); 1102 evt.initEvent('move', true, true); 1103
1104 evt.to = toEl; 1105 evt.from = fromEl; 1106 evt.dragged = dragEl; 1107 evt.draggedRect = dragRect; 1108 evt.related = targetEl || toEl; 1109 evt.relatedRect = targetRect || toEl.getBoundingClientRect(); 1110 1111 fromEl.dispatchEvent(evt); 1112 1113 if (onMoveFn) { 1114 retVal = onMoveFn.call(sortable, evt); 1115 } 1116 1117 return retVal; 1118 } 1119 1120 function _onMoveToSelf(fromEl, _dragEl) { 1121 var sortable = fromEl[expando]; 1122 var fun = sortable.options.onMoveToSelf; 1123 if (fun) { 1124 var evt = document.createEvent('Event'); 1125 evt.initEvent('moveToSelf', true, true); 1126 evt.dragged = _dragEl; 1127 fun.call(sortable, evt); 1128 } 1129 } 1130 1131 1132 function _disableDraggable(el) { 1133 el.draggable = false; 1134 } 1135 1136 1137 function _unsilent() { 1138 _silent = false; 1139 } 1140 1141 1142 /** @returns {HTMLElement|false} */ 1143 function _ghostIsLast(el, evt) { 1144 var lastEl = el.lastElementChild, 1145 rect = lastEl.getBoundingClientRect(); 1146 1147 return ((evt.clientY - (rect.top + rect.height) > 5) || (evt.clientX - (rect.right + rect.width) > 5)) && lastEl; // min delta 1148 } 1149 1150 1151 /** 1152 * Generate id 1153 * @param {HTMLElement} el 1154 * @returns {String} 1155 * @private 1156 */ 1157 function _generateId(el) { 1158 var str = el.tagName + el.className + el.src + el.href + el.textContent, 1159 i = str.length, 1160 sum = 0; 1161 1162 while (i--) { 1163 sum += str.charCodeAt(i); 1164 } 1165 1166 return sum.toString(36); 1167 } 1168 1169 /** 1170 * Returns the index of an element within its parent 1171 * @param {HTMLElement} el 1172 * @return {number} 1173 */ 1174 function _index(el) { 1175 var index = 0; 1176 1177 if (!el || !el.parentNode) { 1178 return -1; 1179 } 1180 1181 while (el && (el = el.previousElementSibling)) { 1182 if (el.nodeName.toUpperCase() !== 'TEMPLATE') { 1183 index++; 1184 } 1185 } 1186 1187 return index; 1188 } 1189 1190 function _throttle(callback, ms) { 1191 var args, _this; 1192 1193 return function() { 1194 if (args === void 0) { 1195 args = arguments; 1196 _this = this; 1197 1198 setTimeout(function() { 1199 if (args.length === 1) { 1200 callback.call(_this, args[0]); 1201 } else { 1202 callback.apply(_this, args); 1203 } 1204 1205 args = void 0; 1206 }, ms); 1207 } 1208 }; 1209 } 1210 1211 function _extend(dst, src) { 1212 if (dst && src) { 1213 for (var key in src) { 1214 if (src.hasOwnProperty(key)) { 1215 dst[key] = src[key]; 1216 } 1217 } 1218 } 1219 1220 return dst; 1221 } 1222 1223 1224 // Export utils 1225 Sortable.utils = { 1226 on: _on, 1227 off: _off, 1228 css: _css, 1229 find: _find, 1230 is: function(el, selector) { 1231 return !!_closest(el, selector, el); 1232 }, 1233 extend: _extend, 1234 throttle: _throttle, 1235 closest: _closest, 1236 toggleClass: _toggleClass, 1237 index: _index 1238 }; 1239 1240 1241 /** 1242 * Create sortable instance 1243 * @param {HTMLElement} el 1244 * @param {Object} [options] 1245 */ 1246 Sortable.create = function(el, options) { 1247 return new Sortable(el, options); 1248 }; 1249 1250 1251 // Export 1252 Sortable.version = '1.3.0'; 1253 return Sortable; 1254});
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.