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