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