11 if (typeof define ===
"function" && define.amd) {
14 else if (typeof module !=
"undefined" && typeof module.exports !=
"undefined") {
15 module.exports = factory();
17 else if (typeof Package !==
"undefined") {
22 window[
"Sortable"] = factory();
55 expando =
'Sortable' + (
new Date).getTime(),
58 document = win.document,
59 parseInt = win.parseInt,
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';
73 touchDragOverListeners = [],
75 _autoScroll = _throttle(
function (evt, options, rootEl) {
77 if (rootEl && options.scroll) {
80 sens = options.scrollSensitivity,
81 speed = options.scrollSpeed,
86 winWidth = window.innerWidth,
87 winHeight = window.innerHeight,
94 if (scrollParentEl !== rootEl) {
95 scrollEl = options.scroll;
96 scrollParentEl = rootEl;
98 if (scrollEl ===
true) {
102 if ((scrollEl.offsetWidth < scrollEl.scrollWidth) ||
103 (scrollEl.offsetHeight < scrollEl.scrollHeight)
108 }
while (scrollEl = scrollEl.parentNode);
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);
121 vx = (winWidth - x <= sens) - (x <= sens);
122 vy = (winHeight - y <= sens) - (y <= sens);
125 (vx || vy) && (el = win);
129 if (autoScroll.vx !== vx || autoScroll.vy !== vy || autoScroll.el !== el) {
134 clearInterval(autoScroll.pid);
137 autoScroll.pid = setInterval(
function () {
139 win.scrollTo(win.pageXOffset + vx * speed, win.pageYOffset + vy * speed);
141 vy && (el.scrollTop += vy * speed);
142 vx && (el.scrollLeft += vx * speed);
150 _prepareGroup =
function (options) {
151 var group = options.group;
153 if (!group || typeof group !=
'object') {
154 group = options.group = {name: group};
157 [
'pull',
'put'].forEach(
function (key) {
158 if (!(key in group)) {
163 options.groups =
' ' + group.name + (group.put.join ?
' ' + group.put.join(
' ') :
'') +
' ';
175 if (!(el && el.nodeType && el.nodeType === 1)) {
176 throw 'Sortable: `el` must be HTMLElement, and not ' + {}.toString.call(el);
180 this.options = options = _extend({}, options);
189 group: Math.random(),
195 scrollSensitivity: 30,
197 draggable: /[uo]l/i.test(el.nodeName) ?
'li' :
'>*',
198 ghostClass:
'sortable-ghost',
199 chosenClass:
'sortable-chosen',
203 setData:
function (dataTransfer, dragEl) {
204 dataTransfer.setData(
'Text', dragEl.textContent);
207 dragoverBubble:
false,
208 dataIdAttr:
'data-id',
210 forceFallback:
false,
211 fallbackClass:
'sortable-fallback',
212 fallbackOnBody:
false
217 for (var name in defaults) {
218 !(name in options) && (options[name] = defaults[name]);
221 _prepareGroup(options);
224 for (var fn in
this) {
225 if (fn.charAt(0) ===
'_') {
226 this[fn] =
this[fn].bind(
this);
231 this.nativeDraggable = options.forceFallback ?
false : supportDraggable;
234 _on(el,
'mousedown', this._onTapStart);
235 _on(el,
'touchstart', this._onTapStart);
237 if (this.nativeDraggable) {
238 _on(el,
'dragover',
this);
239 _on(el,
'dragenter',
this);
242 touchDragOverListeners.push(this._onDragOver);
245 options.store && this.sort(options.store.get(
this));
252 _onTapStart:
function (evt) {
255 options = this.options,
257 touch = evt.touches && evt.touches[0],
258 target = (touch || evt).target,
259 originalTarget = target,
260 filter = options.filter;
263 if (type ===
'mousedown' && evt.button !== 0 || options.disabled) {
267 target = _closest(target, options.draggable, el);
274 oldIndex = _index(target);
277 if (typeof filter ===
'function') {
278 if (filter.call(
this, evt, target,
this)) {
279 _dispatchEvent(_this, originalTarget,
'filter', target, el, oldIndex);
280 evt.preventDefault();
285 filter = filter.split(
',').some(
function (criteria) {
286 criteria = _closest(originalTarget, criteria.trim(), el);
289 _dispatchEvent(_this, criteria,
'filter', target, el, oldIndex);
295 evt.preventDefault();
301 if (options.handle && !_closest(originalTarget, options.handle, el)) {
307 this._prepareDragStart(evt, touch, target);
310 _prepareDragStart:
function (evt, touch, target) {
313 options = _this.options,
314 ownerDocument = el.ownerDocument,
317 if (target && !dragEl && (target.parentNode === el)) {
322 parentEl = dragEl.parentNode;
323 nextEl = dragEl.nextSibling;
324 activeGroup = options.group;
326 dragStartFn =
function () {
329 _this._disableDelayedDrag();
332 dragEl.draggable =
true;
335 _toggleClass(dragEl, _this.options.chosenClass,
true);
338 _this._triggerDragStart(touch);
342 options.ignore.split(
',').forEach(
function (criteria) {
343 _find(dragEl, criteria.trim(), _disableDraggable);
346 _on(ownerDocument,
'mouseup', _this._onDrop);
347 _on(ownerDocument,
'touchend', _this._onDrop);
348 _on(ownerDocument,
'touchcancel', _this._onDrop);
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);
360 _this._dragStartTimer = setTimeout(dragStartFn, options.delay);
367 _disableDelayedDrag:
function () {
368 var ownerDocument = this.el.ownerDocument;
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);
378 _triggerDragStart:
function (touch) {
383 clientX: touch.clientX,
384 clientY: touch.clientY
387 this._onDragStart(tapEvt,
'touch');
389 else if (!this.nativeDraggable) {
390 this._onDragStart(tapEvt,
true);
393 _on(dragEl,
'dragend',
this);
394 _on(rootEl,
'dragstart', this._onDragStart);
398 if (document.selection) {
399 document.selection.empty();
401 window.getSelection().removeAllRanges();
407 _dragStarted:
function () {
408 if (rootEl && dragEl) {
410 _toggleClass(dragEl, this.options.ghostClass,
true);
415 _dispatchEvent(
this, rootEl,
'start', dragEl, rootEl, oldIndex);
419 _emulateDragOver:
function () {
421 if (this._lastX === touchEvt.clientX &&
this._lastY === touchEvt.clientY) {
425 this._lastX = touchEvt.clientX;
426 this._lastY = touchEvt.clientY;
428 if (!supportCssPointerEvents) {
429 _css(ghostEl,
'display',
'none');
432 var target = document.elementFromPoint(touchEvt.clientX, touchEvt.clientY),
434 groupName =
' ' + this.options.group.name +
'',
435 i = touchDragOverListeners.length;
439 if (parent[expando] && parent[expando].options.groups.indexOf(groupName) > -1) {
441 touchDragOverListeners[i]({
442 clientX: touchEvt.clientX,
443 clientY: touchEvt.clientY,
455 while (parent = parent.parentNode);
458 if (!supportCssPointerEvents) {
459 _css(ghostEl,
'display',
'');
465 _onTouchMove:
function (evt) {
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)';
483 _css(ghostEl,
'webkitTransform', translate3d);
484 _css(ghostEl,
'mozTransform', translate3d);
485 _css(ghostEl,
'msTransform', translate3d);
486 _css(ghostEl,
'transform', translate3d);
488 evt.preventDefault();
492 _appendGhost:
function () {
494 var rect = dragEl.getBoundingClientRect(),
496 options = this.options,
499 ghostEl = dragEl.cloneNode(
true);
501 _toggleClass(ghostEl, options.ghostClass,
false);
502 _toggleClass(ghostEl, options.fallbackClass,
true);
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');
513 options.fallbackOnBody && document.body.appendChild(ghostEl) || rootEl.appendChild(ghostEl);
516 ghostRect = ghostEl.getBoundingClientRect();
517 _css(ghostEl,
'width', rect.width * 2 - ghostRect.width);
518 _css(ghostEl,
'height', rect.height * 2 - ghostRect.height);
522 _onDragStart:
function (evt, useFallback) {
523 var dataTransfer = evt.dataTransfer,
524 options = this.options;
528 if (activeGroup.pull ==
'clone') {
529 cloneEl = dragEl.cloneNode(
true);
530 _css(cloneEl,
'display',
'none');
531 rootEl.insertBefore(cloneEl, dragEl);
536 if (useFallback ===
'touch') {
538 _on(document,
'touchmove', this._onTouchMove);
539 _on(document,
'touchend', this._onDrop);
540 _on(document,
'touchcancel', this._onDrop);
543 _on(document,
'mousemove', this._onTouchMove);
544 _on(document,
'mouseup', this._onDrop);
547 this._loopId = setInterval(this._emulateDragOver, 50);
551 dataTransfer.effectAllowed =
'move';
552 options.setData && options.setData.call(
this, dataTransfer, dragEl);
555 _on(document,
'drop',
this);
556 setTimeout(this._dragStarted, 0);
560 _onDragOver:
function (evt) {
565 options = this.options,
566 group = options.group,
567 groupPut = group.put,
568 isOwner = (activeGroup === group),
569 canSort = options.sort;
571 if (evt.preventDefault !==
void 0) {
572 evt.preventDefault();
573 !options.dragoverBubble && evt.stopPropagation();
578 if (activeGroup && !options.disabled &&
580 ? canSort || (revert = !rootEl.contains(dragEl))
581 : activeGroup.pull && groupPut && (
582 (activeGroup.name === group.name) ||
583 (groupPut.indexOf && ~groupPut.indexOf(activeGroup.name))
586 (evt.rootEl ===
void 0 || evt.rootEl ===
this.el)
589 _autoScroll(evt, options, this.el);
595 target = _closest(evt.target, options.draggable, el);
596 dragRect = dragEl.getBoundingClientRect();
601 if (cloneEl || nextEl) {
602 rootEl.insertBefore(dragEl, cloneEl || nextEl);
605 rootEl.appendChild(dragEl);
612 if ((el.children.length === 0) || (el.children[0] === ghostEl) ||
613 (el === evt.target) && (target = _ghostIsLast(el, evt))
617 if (target.animated) {
621 targetRect = target.getBoundingClientRect();
626 if (_onMove(rootEl, el, dragEl, dragRect, target, targetRect) !==
false) {
627 if (!dragEl.contains(el)) {
628 el.appendChild(dragEl);
632 this._animate(dragRect, dragEl);
633 target && this._animate(targetRect, target);
636 else if (target && !target.animated && target !== dragEl && (target.parentNode[expando] !==
void 0)) {
637 if (lastEl !== target) {
639 lastCSS = _css(target);
640 lastParentCSS = _css(target.parentNode);
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),
657 if (moveVector !==
false) {
659 setTimeout(_unsilent, 30);
663 if (moveVector === 1 || moveVector === -1) {
664 after = (moveVector === 1);
667 var elTop = dragEl.offsetTop,
668 tgTop = target.offsetTop;
670 if (elTop === tgTop) {
671 after = (target.previousElementSibling === dragEl) && !isWide || halfway && isWide;
673 after = tgTop > elTop;
676 after = (nextSibling !== dragEl) && !isLong || halfway && isLong;
679 if (!dragEl.contains(el)) {
680 if (after && !nextSibling) {
681 el.appendChild(dragEl);
683 target.parentNode.insertBefore(dragEl, after ? nextSibling : target);
687 parentEl = dragEl.parentNode;
689 this._animate(dragRect, dragEl);
690 this._animate(targetRect, target);
696 _animate:
function (prevRect, target) {
697 var ms = this.options.animation;
700 var currentRect = target.getBoundingClientRect();
702 _css(target,
'transition',
'none');
703 _css(target,
'transform',
'translate3d('
704 + (prevRect.left - currentRect.left) +
'px,'
705 + (prevRect.top - currentRect.top) +
'px,0)'
710 _css(target,
'transition',
'all ' + ms +
'ms');
711 _css(target,
'transform',
'translate3d(0,0,0)');
713 clearTimeout(target.animated);
714 target.animated = setTimeout(
function () {
715 _css(target,
'transition',
'');
716 _css(target,
'transform',
'');
717 target.animated =
false;
722 _offUpEvents:
function () {
723 var ownerDocument = this.el.ownerDocument;
725 _off(document,
'touchmove', this._onTouchMove);
726 _off(ownerDocument,
'mouseup', this._onDrop);
727 _off(ownerDocument,
'touchend', this._onDrop);
728 _off(ownerDocument,
'touchcancel', this._onDrop);
731 _onDrop:
function (evt) {
733 options = this.options;
735 clearInterval(this._loopId);
736 clearInterval(autoScroll.pid);
737 clearTimeout(this._dragStartTimer);
740 _off(document,
'mousemove', this._onTouchMove);
742 if (this.nativeDraggable) {
743 _off(document,
'drop',
this);
744 _off(el,
'dragstart', this._onDragStart);
751 evt.preventDefault();
752 !options.dropBubble && evt.stopPropagation();
755 ghostEl && ghostEl.parentNode.removeChild(ghostEl);
758 if (this.nativeDraggable) {
759 _off(dragEl,
'dragend',
this);
762 _disableDraggable(dragEl);
765 _toggleClass(dragEl, this.options.ghostClass,
false);
766 _toggleClass(dragEl, this.options.chosenClass,
false);
768 if (rootEl !== parentEl) {
769 newIndex = _index(dragEl);
773 _dispatchEvent(null, parentEl,
'sort', dragEl, rootEl, oldIndex, newIndex);
774 _dispatchEvent(
this, rootEl,
'sort', dragEl, rootEl, oldIndex, newIndex);
777 _dispatchEvent(null, parentEl,
'add', dragEl, rootEl, oldIndex, newIndex);
780 _dispatchEvent(
this, rootEl,
'remove', dragEl, rootEl, oldIndex, newIndex);
785 cloneEl && cloneEl.parentNode.removeChild(cloneEl);
787 if (dragEl.nextSibling !== nextEl) {
789 newIndex = _index(dragEl);
793 _dispatchEvent(
this, rootEl,
'update', dragEl, rootEl, oldIndex, newIndex);
794 _dispatchEvent(
this, rootEl,
'sort', dragEl, rootEl, oldIndex, newIndex);
800 if (newIndex === null || newIndex === -1) {
804 _dispatchEvent(
this, rootEl,
'end', dragEl, rootEl, oldIndex, newIndex);
837 handleEvent:
function (evt) {
840 if (type ===
'dragover' || type ===
'dragenter') {
842 this._onDragOver(evt);
843 _globalDragOver(evt);
846 else if (type ===
'drop' || type ===
'dragend') {
856 toArray:
function () {
859 children = this.el.children,
862 options = this.options;
866 if (_closest(el, options.draggable,
this.el)) {
867 order.push(el.getAttribute(options.dataIdAttr) || _generateId(el));
879 sort:
function (order) {
880 var items = {}, rootEl = this.el;
882 this.toArray().forEach(
function (
id, i) {
883 var el = rootEl.children[i];
885 if (_closest(el, this.options.draggable, rootEl)) {
890 order.forEach(
function (
id) {
892 rootEl.removeChild(items[
id]);
893 rootEl.appendChild(items[
id]);
903 var store = this.options.store;
904 store && store.set(
this);
914 closest:
function (el, selector) {
915 return _closest(el, selector || this.options.draggable,
this.el);
925 option:
function (name, value) {
926 var options = this.options;
928 if (value ===
void 0) {
929 return options[name];
931 options[name] = value;
933 if (name ===
'group') {
934 _prepareGroup(options);
943 destroy:
function () {
948 _off(el,
'mousedown', this._onTapStart);
949 _off(el,
'touchstart', this._onTapStart);
951 if (this.nativeDraggable) {
952 _off(el,
'dragover',
this);
953 _off(el,
'dragenter',
this);
957 Array.prototype.forEach.call(el.querySelectorAll(
'[draggable]'),
function (el) {
958 el.removeAttribute(
'draggable');
961 touchDragOverListeners.splice(touchDragOverListeners.indexOf(
this._onDragOver), 1);
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;
979 function _closest(el, selector, ctx) {
981 ctx = ctx || document;
982 selector = selector.split(
'.');
984 var tag = selector.shift().toUpperCase(),
985 re =
new RegExp(
'\\s(' + selector.join(
'|') +
')(?=\\s)',
'g');
989 (tag ===
'>*' && el.parentNode === ctx) || (
990 (tag ===
'' || el.nodeName.toUpperCase() == tag) &&
991 (!selector.length || ((
' ' + el.className +
' ').match(re) || []).length == selector.length)
997 while (el !== ctx && (el = el.parentNode));
1004 function _globalDragOver(evt) {
1005 if (evt.dataTransfer) {
1006 evt.dataTransfer.dropEffect =
'move';
1008 evt.preventDefault();
1012 function _on(el, event, fn) {
1013 el.addEventListener(event, fn,
false);
1017 function _off(el, event, fn) {
1018 el.removeEventListener(event, fn,
false);
1022 function _toggleClass(el, name, state) {
1025 el.classList[state ?
'add' :
'remove'](name);
1028 var className = (
' ' + el.className +
' ').replace(RSPACE,
' ').replace(
' ' + name +
' ',
' ');
1029 el.className = (className + (state ?
' ' + name :
'')).replace(RSPACE,
' ');
1035 function _css(el, prop, val) {
1036 var style = el && el.style;
1039 if (val ===
void 0) {
1040 if (document.defaultView && document.defaultView.getComputedStyle) {
1041 val = document.defaultView.getComputedStyle(el,
'');
1043 else if (el.currentStyle) {
1044 val = el.currentStyle;
1047 return prop ===
void 0 ? val : val[prop];
1050 if (!(prop in style)) {
1051 prop =
'-webkit-' + prop;
1054 style[prop] = val + (typeof val ===
'string' ?
'' :
'px');
1060 function _find(ctx, tagName, iterator) {
1062 var list = ctx.getElementsByTagName(tagName), i = 0, n = list.length;
1065 for (; i < n; i++) {
1066 iterator(list[i], i);
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);
1083 evt.initEvent(name,
true,
true);
1086 evt.from = fromEl || rootEl;
1087 evt.item = targetEl || rootEl;
1088 evt.clone = cloneEl;
1090 evt.oldIndex = startIndex;
1091 evt.newIndex = newIndex;
1093 rootEl.dispatchEvent(evt);
1095 if (options[onName]) {
1096 options[onName].call(sortable, evt);
1101 function _onMove(fromEl, toEl, dragEl, dragRect, targetEl, targetRect) {
1103 sortable = fromEl[expando],
1104 onMoveFn = sortable.options.onMove,
1107 evt = document.createEvent(
'Event');
1108 evt.initEvent(
'move',
true,
true);
1112 evt.dragged = dragEl;
1113 evt.draggedRect = dragRect;
1114 evt.related = targetEl || toEl;
1115 evt.relatedRect = targetRect || toEl.getBoundingClientRect();
1117 fromEl.dispatchEvent(evt);
1120 retVal = onMoveFn.call(sortable, evt);
1127 function _disableDraggable(el) {
1128 el.draggable =
false;
1132 function _unsilent() {
1138 function _ghostIsLast(el, evt) {
1139 var lastEl = el.lastElementChild,
1140 rect = lastEl.getBoundingClientRect();
1142 return ((evt.clientY - (rect.top + rect.height) > 5) || (evt.clientX - (rect.right + rect.width) > 5)) && lastEl;
1152 function _generateId(el) {
1153 var str = el.tagName + el.className + el.src + el.href + el.textContent,
1158 sum += str.charCodeAt(i);
1161 return sum.toString(36);
1169 function _index(el) {
1172 if (!el || !el.parentNode) {
1176 while (el && (el = el.previousElementSibling)) {
1177 if (el.nodeName.toUpperCase() !==
'TEMPLATE') {
1185 function _throttle(callback, ms) {
1188 return function () {
1189 if (args ===
void 0) {
1193 setTimeout(
function () {
1194 if (args.length === 1) {
1195 callback.call(_this, args[0]);
1197 callback.apply(_this, args);
1206 function _extend(dst, src) {
1208 for (var key in src) {
1209 if (src.hasOwnProperty(key)) {
1210 dst[key] = src[key];
1225 is:
function (el, selector) {
1226 return !!_closest(el, selector, el);
1229 throttle: _throttle,
1231 toggleClass: _toggleClass,
1241 Sortable.create =
function (el, options) {