/**! * Sortable * @author RubaXa * @license MIT */ ;(function sortableModule(factory) { 'use strict' if (typeof define === 'function' && define.amd) { define(factory) } else if ( typeof module != 'undefined' && typeof module.exports != 'undefined' ) { module.exports = factory() } else { /* jshint sub:true */ window['Sortable'] = factory() } })(function sortableFactory() { 'use strict' if (typeof window === 'undefined' || !window.document) { return function sortableError() { throw new Error('Sortable.js requires a window with a document') } } var dragEl, parentEl, ghostEl, cloneEl, rootEl, nextEl, lastDownEl, scrollEl, scrollParentEl, scrollCustomFn, lastEl, lastCSS, lastParentCSS, oldIndex, newIndex, activeGroup, putSortable, autoScroll = {}, tapEvt, touchEvt, moved, /** @const */ R_SPACE = /\s+/g, R_FLOAT = /left|right|inline/, expando = 'Sortable' + new Date().getTime(), win = window, document = win.document, parseInt = win.parseInt, setTimeout = win.setTimeout, $ = win.jQuery || win.Zepto, Polymer = win.Polymer, captureMode = false, passiveMode = false, supportDraggable = 'draggable' in document.createElement('div'), supportCssPointerEvents = (function(el) { // false when IE11 if (!!navigator.userAgent.match(/(?:Trident.*rv[ :]?11\.|msie)/i)) { return false } el = document.createElement('x') el.style.cssText = 'pointer-events:auto' return el.style.pointerEvents === 'auto' })(), _silent = false, abs = Math.abs, min = Math.min, savedInputChecked = [], touchDragOverListeners = [], _autoScroll = _throttle(function( /**Event*/ evt, /**Object*/ options, /**HTMLElement*/ rootEl ) { // Bug: https://bugzilla.mozilla.org/show_bug.cgi?id=505521 if (rootEl && options.scroll) { var _this = rootEl[expando], el, rect, sens = options.scrollSensitivity, speed = options.scrollSpeed, x = evt.clientX, y = evt.clientY, winWidth = window.innerWidth, winHeight = window.innerHeight, vx, vy, scrollOffsetX, scrollOffsetY // Delect scrollEl if (scrollParentEl !== rootEl) { scrollEl = options.scroll scrollParentEl = rootEl scrollCustomFn = options.scrollFn if (scrollEl === true) { scrollEl = rootEl do { if ( scrollEl.offsetWidth < scrollEl.scrollWidth || scrollEl.offsetHeight < scrollEl.scrollHeight ) { break } /* jshint boss:true */ } while ((scrollEl = scrollEl.parentNode)) } } if (scrollEl) { el = scrollEl rect = scrollEl.getBoundingClientRect() vx = (abs(rect.right - x) <= sens) - (abs(rect.left - x) <= sens) vy = (abs(rect.bottom - y) <= sens) - (abs(rect.top - y) <= sens) } if (!(vx || vy)) { vx = (winWidth - x <= sens) - (x <= sens) vy = (winHeight - y <= sens) - (y <= sens) /* jshint expr:true */ ;(vx || vy) && (el = win) } if ( autoScroll.vx !== vx || autoScroll.vy !== vy || autoScroll.el !== el ) { autoScroll.el = el autoScroll.vx = vx autoScroll.vy = vy clearInterval(autoScroll.pid) if (el) { autoScroll.pid = setInterval(function() { scrollOffsetY = vy ? vy * speed : 0 scrollOffsetX = vx ? vx * speed : 0 if ('function' === typeof scrollCustomFn) { return scrollCustomFn.call( _this, scrollOffsetX, scrollOffsetY, evt ) } if (el === win) { win.scrollTo( win.pageXOffset + scrollOffsetX, win.pageYOffset + scrollOffsetY ) } else { el.scrollTop += scrollOffsetY el.scrollLeft += scrollOffsetX } }, 24) } } } }, 30), _prepareGroup = function(options) { function toFn(value, pull) { if (value === void 0 || value === true) { value = group.name } if (typeof value === 'function') { return value } else { return function(to, from) { var fromGroup = from.options.group.name return pull ? value : value && (value.join ? value.indexOf(fromGroup) > -1 : fromGroup == value) } } } var group = {} var originalGroup = options.group if (!originalGroup || typeof originalGroup != 'object') { originalGroup = { name: originalGroup } } group.name = originalGroup.name group.checkPull = toFn(originalGroup.pull, true) group.checkPut = toFn(originalGroup.put) group.revertClone = originalGroup.revertClone options.group = group } // Detect support a passive mode try { window.addEventListener( 'test', null, Object.defineProperty({}, 'passive', { get: function() { // `false`, because everything starts to work incorrectly and instead of d'n'd, // begins the page has scrolled. passiveMode = false captureMode = { capture: false, passive: passiveMode, } }, }) ) } catch (err) {} /** * @class Sortable * @param {HTMLElement} el * @param {Object} [options] */ function Sortable(el, options) { if (!(el && el.nodeType && el.nodeType === 1)) { throw 'Sortable: `el` must be HTMLElement, and not ' + {}.toString.call(el) } this.el = el // root element this.options = options = _extend({}, options) // Export instance el[expando] = this // Default options var defaults = { group: Math.random(), sort: true, disabled: false, store: null, handle: null, scroll: true, scrollSensitivity: 30, scrollSpeed: 10, draggable: /[uo]l/i.test(el.nodeName) ? 'li' : '>*', ghostClass: 'sortable-ghost', chosenClass: 'sortable-chosen', dragClass: 'sortable-drag', ignore: 'a, img', filter: null, preventOnFilter: true, animation: 0, setData: function(dataTransfer, dragEl) { dataTransfer.setData('Text', dragEl.textContent) }, dropBubble: false, dragoverBubble: false, dataIdAttr: 'data-id', delay: 0, forceFallback: false, fallbackClass: 'sortable-fallback', fallbackOnBody: false, fallbackTolerance: 0, fallbackOffset: { x: 0, y: 0 }, supportPointer: Sortable.supportPointer !== false, } // Set default options for (var name in defaults) { !(name in options) && (options[name] = defaults[name]) } _prepareGroup(options) // Bind all private methods for (var fn in this) { if (fn.charAt(0) === '_' && typeof this[fn] === 'function') { this[fn] = this[fn].bind(this) } } // Setup drag mode this.nativeDraggable = options.forceFallback ? false : supportDraggable // Bind events _on(el, 'mousedown', this._onTapStart) _on(el, 'touchstart', this._onTapStart) options.supportPointer && _on(el, 'pointerdown', this._onTapStart) if (this.nativeDraggable) { _on(el, 'dragover', this) _on(el, 'dragenter', this) } touchDragOverListeners.push(this._onDragOver) // Restore sorting options.store && this.sort(options.store.get(this)) } Sortable.prototype = /** @lends Sortable.prototype */ { constructor: Sortable, _onTapStart: function(/** Event|TouchEvent */ evt) { var _this = this, el = this.el, options = this.options, preventOnFilter = options.preventOnFilter, type = evt.type, touch = evt.touches && evt.touches[0], target = (touch || evt).target, originalTarget = (evt.target.shadowRoot && (evt.path && evt.path[0])) || target, filter = options.filter, startIndex _saveInputCheckedState(el) // Don't trigger start event when an element is been dragged, otherwise the evt.oldindex always wrong when set option.group. if (dragEl) { return } if ( (/mousedown|pointerdown/.test(type) && evt.button !== 0) || options.disabled ) { return // only left button or enabled } // cancel dnd if original target is content editable if (originalTarget.isContentEditable) { return } target = _closest(target, options.draggable, el) if (!target) { return } if (lastDownEl === target) { // Ignoring duplicate `down` return } // Get the index of the dragged element within its parent startIndex = _index(target, options.draggable) // Check filter if (typeof filter === 'function') { if (filter.call(this, evt, target, this)) { _dispatchEvent( _this, originalTarget, 'filter', target, el, el, startIndex ) preventOnFilter && evt.preventDefault() return // cancel dnd } } else if (filter) { filter = filter.split(',').some(function(criteria) { criteria = _closest(originalTarget, criteria.trim(), el) if (criteria) { _dispatchEvent( _this, criteria, 'filter', target, el, el, startIndex ) return true } }) if (filter) { preventOnFilter && evt.preventDefault() return // cancel dnd } } if ( options.handle && !_closest(originalTarget, options.handle, el) ) { return } // Prepare `dragstart` this._prepareDragStart(evt, touch, target, startIndex) }, _prepareDragStart: function( /** Event */ evt, /** Touch */ touch, /** HTMLElement */ target, /** Number */ startIndex ) { var _this = this, el = _this.el, options = _this.options, ownerDocument = el.ownerDocument, dragStartFn if (target && !dragEl && target.parentNode === el) { tapEvt = evt rootEl = el dragEl = target parentEl = dragEl.parentNode nextEl = dragEl.nextSibling lastDownEl = target activeGroup = options.group oldIndex = startIndex this._lastX = (touch || evt).clientX this._lastY = (touch || evt).clientY dragEl.style['will-change'] = 'all' dragStartFn = function() { // Delayed drag has been triggered // we can re-enable the events: touchmove/mousemove _this._disableDelayedDrag() // Make the element draggable dragEl.draggable = _this.nativeDraggable // Chosen item _toggleClass(dragEl, options.chosenClass, true) // Bind the events: dragstart/dragend _this._triggerDragStart(evt, touch) // Drag start event _dispatchEvent( _this, rootEl, 'choose', dragEl, rootEl, rootEl, oldIndex ) } // Disable "draggable" options.ignore.split(',').forEach(function(criteria) { _find(dragEl, criteria.trim(), _disableDraggable) }) _on(ownerDocument, 'mouseup', _this._onDrop) _on(ownerDocument, 'touchend', _this._onDrop) _on(ownerDocument, 'touchcancel', _this._onDrop) _on(ownerDocument, 'selectstart', _this) options.supportPointer && _on(ownerDocument, 'pointercancel', _this._onDrop) if (options.delay) { // If the user moves the pointer or let go the click or touch // before the delay has been reached: // disable the delayed drag _on(ownerDocument, 'mouseup', _this._disableDelayedDrag) _on(ownerDocument, 'touchend', _this._disableDelayedDrag) _on(ownerDocument, 'touchcancel', _this._disableDelayedDrag) _on(ownerDocument, 'mousemove', _this._disableDelayedDrag) _on(ownerDocument, 'touchmove', _this._disableDelayedDrag) options.supportPointer && _on( ownerDocument, 'pointermove', _this._disableDelayedDrag ) _this._dragStartTimer = setTimeout( dragStartFn, options.delay ) } else { dragStartFn() } } }, _disableDelayedDrag: function() { var ownerDocument = this.el.ownerDocument clearTimeout(this._dragStartTimer) _off(ownerDocument, 'mouseup', this._disableDelayedDrag) _off(ownerDocument, 'touchend', this._disableDelayedDrag) _off(ownerDocument, 'touchcancel', this._disableDelayedDrag) _off(ownerDocument, 'mousemove', this._disableDelayedDrag) _off(ownerDocument, 'touchmove', this._disableDelayedDrag) _off(ownerDocument, 'pointermove', this._disableDelayedDrag) }, _triggerDragStart: function(/** Event */ evt, /** Touch */ touch) { touch = touch || (evt.pointerType == 'touch' ? evt : null) if (touch) { // Touch device support tapEvt = { target: dragEl, clientX: touch.clientX, clientY: touch.clientY, } this._onDragStart(tapEvt, 'touch') } else if (!this.nativeDraggable) { this._onDragStart(tapEvt, true) } else { _on(dragEl, 'dragend', this) _on(rootEl, 'dragstart', this._onDragStart) } try { if (document.selection) { // Timeout neccessary for IE9 _nextTick(function() { document.selection.empty() }) } else { window.getSelection().removeAllRanges() } } catch (err) {} }, _dragStarted: function() { if (rootEl && dragEl) { var options = this.options // Apply effect _toggleClass(dragEl, options.ghostClass, true) _toggleClass(dragEl, options.dragClass, false) Sortable.active = this // Drag start event _dispatchEvent( this, rootEl, 'start', dragEl, rootEl, rootEl, oldIndex ) } else { this._nulling() } }, _emulateDragOver: function() { if (touchEvt) { if ( this._lastX === touchEvt.clientX && this._lastY === touchEvt.clientY ) { return } this._lastX = touchEvt.clientX this._lastY = touchEvt.clientY if (!supportCssPointerEvents) { _css(ghostEl, 'display', 'none') } var target = document.elementFromPoint( touchEvt.clientX, touchEvt.clientY ) var parent = target var i = touchDragOverListeners.length if (target && target.shadowRoot) { target = target.shadowRoot.elementFromPoint( touchEvt.clientX, touchEvt.clientY ) parent = target } if (parent) { do { if (parent[expando]) { while (i--) { touchDragOverListeners[i]({ clientX: touchEvt.clientX, clientY: touchEvt.clientY, target: target, rootEl: parent, }) } break } target = parent // store last element } while ( /* jshint boss:true */ (parent = parent.parentNode) ) } if (!supportCssPointerEvents) { _css(ghostEl, 'display', '') } } }, _onTouchMove: function(/**TouchEvent*/ evt) { if (tapEvt) { var options = this.options, fallbackTolerance = options.fallbackTolerance, fallbackOffset = options.fallbackOffset, touch = evt.touches ? evt.touches[0] : evt, dx = touch.clientX - tapEvt.clientX + fallbackOffset.x, dy = touch.clientY - tapEvt.clientY + fallbackOffset.y, translate3d = evt.touches ? 'translate3d(' + dx + 'px,' + dy + 'px,0)' : 'translate(' + dx + 'px,' + dy + 'px)' // only set the status to dragging, when we are actually dragging if (!Sortable.active) { if ( fallbackTolerance && min( abs(touch.clientX - this._lastX), abs(touch.clientY - this._lastY) ) < fallbackTolerance ) { return } this._dragStarted() } // as well as creating the ghost element on the document body this._appendGhost() moved = true touchEvt = touch _css(ghostEl, 'webkitTransform', translate3d) _css(ghostEl, 'mozTransform', translate3d) _css(ghostEl, 'msTransform', translate3d) _css(ghostEl, 'transform', translate3d) evt.preventDefault() } }, _appendGhost: function() { if (!ghostEl) { var rect = dragEl.getBoundingClientRect(), css = _css(dragEl), options = this.options, ghostRect ghostEl = dragEl.cloneNode(true) _toggleClass(ghostEl, options.ghostClass, false) _toggleClass(ghostEl, options.fallbackClass, true) _toggleClass(ghostEl, options.dragClass, true) _css(ghostEl, 'top', rect.top - parseInt(css.marginTop, 10)) _css(ghostEl, 'left', rect.left - parseInt(css.marginLeft, 10)) _css(ghostEl, 'width', rect.width) _css(ghostEl, 'height', rect.height) _css(ghostEl, 'opacity', '0.8') _css(ghostEl, 'position', 'fixed') _css(ghostEl, 'zIndex', '100000') _css(ghostEl, 'pointerEvents', 'none') ;(options.fallbackOnBody && document.body.appendChild(ghostEl)) || rootEl.appendChild(ghostEl) // Fixing dimensions. ghostRect = ghostEl.getBoundingClientRect() _css(ghostEl, 'width', rect.width * 2 - ghostRect.width) _css(ghostEl, 'height', rect.height * 2 - ghostRect.height) } }, _onDragStart: function(/**Event*/ evt, /**boolean*/ useFallback) { var _this = this var dataTransfer = evt.dataTransfer var options = _this.options _this._offUpEvents() if (activeGroup.checkPull(_this, _this, dragEl, evt)) { cloneEl = _clone(dragEl) cloneEl.draggable = false cloneEl.style['will-change'] = '' _css(cloneEl, 'display', 'none') _toggleClass(cloneEl, _this.options.chosenClass, false) // #1143: IFrame support workaround _this._cloneId = _nextTick(function() { rootEl.insertBefore(cloneEl, dragEl) _dispatchEvent(_this, rootEl, 'clone', dragEl) }) } _toggleClass(dragEl, options.dragClass, true) if (useFallback) { if (useFallback === 'touch') { // Bind touch events _on(document, 'touchmove', _this._onTouchMove) _on(document, 'touchend', _this._onDrop) _on(document, 'touchcancel', _this._onDrop) if (options.supportPointer) { _on(document, 'pointermove', _this._onTouchMove) _on(document, 'pointerup', _this._onDrop) } } else { // Old brwoser _on(document, 'mousemove', _this._onTouchMove) _on(document, 'mouseup', _this._onDrop) } _this._loopId = setInterval(_this._emulateDragOver, 50) } else { if (dataTransfer) { dataTransfer.effectAllowed = 'move' options.setData && options.setData.call(_this, dataTransfer, dragEl) } _on(document, 'drop', _this) // #1143: Бывает элемент с IFrame внутри блокирует `drop`, // поэтому если вызвался `mouseover`, значит надо отменять весь d'n'd. // Breaking Chrome 62+ // _on(document, 'mouseover', _this); _this._dragStartId = _nextTick(_this._dragStarted) } }, _onDragOver: function(/**Event*/ evt) { var el = this.el, target, dragRect, targetRect, revert, options = this.options, group = options.group, activeSortable = Sortable.active, isOwner = activeGroup === group, isMovingBetweenSortable = false, canSort = options.sort if (evt.preventDefault !== void 0) { evt.preventDefault() !options.dragoverBubble && evt.stopPropagation() } if (dragEl.animated) { return } moved = true if ( activeSortable && !options.disabled && (isOwner ? canSort || (revert = !rootEl.contains(dragEl)) // Reverting item into the original list : putSortable === this || ((activeSortable.lastPullMode = activeGroup.checkPull( this, activeSortable, dragEl, evt )) && group.checkPut(this, activeSortable, dragEl, evt))) && (evt.rootEl === void 0 || evt.rootEl === this.el) // touch fallback ) { // Smart auto-scrolling _autoScroll(evt, options, this.el) if (_silent) { return } target = _closest(evt.target, options.draggable, el) dragRect = dragEl.getBoundingClientRect() if (putSortable !== this) { putSortable = this isMovingBetweenSortable = true } if (revert) { _cloneHide(activeSortable, true) parentEl = rootEl // actualization if (cloneEl || nextEl) { rootEl.insertBefore(dragEl, cloneEl || nextEl) } else if (!canSort) { rootEl.appendChild(dragEl) } return } if ( el.children.length === 0 || el.children[0] === ghostEl || (el === evt.target && _ghostIsLast(el, evt)) ) { //assign target only if condition is true if ( el.children.length !== 0 && el.children[0] !== ghostEl && el === evt.target ) { target = el.lastElementChild } if (target) { if (target.animated) { return } targetRect = target.getBoundingClientRect() } _cloneHide(activeSortable, isOwner) if ( _onMove( rootEl, el, dragEl, dragRect, target, targetRect, evt ) !== false ) { if (!dragEl.contains(el)) { el.appendChild(dragEl) parentEl = el // actualization } this._animate(dragRect, dragEl) target && this._animate(targetRect, target) } } else if ( target && !target.animated && target !== dragEl && target.parentNode[expando] !== void 0 ) { if (lastEl !== target) { lastEl = target lastCSS = _css(target) lastParentCSS = _css(target.parentNode) } targetRect = target.getBoundingClientRect() var width = targetRect.right - targetRect.left, height = targetRect.bottom - targetRect.top, floating = R_FLOAT.test(lastCSS.cssFloat + lastCSS.display) || (lastParentCSS.display == 'flex' && lastParentCSS['flex-direction'].indexOf( 'row' ) === 0), isWide = target.offsetWidth > dragEl.offsetWidth, isLong = target.offsetHeight > dragEl.offsetHeight, halfway = (floating ? (evt.clientX - targetRect.left) / width : (evt.clientY - targetRect.top) / height) > 0.5, nextSibling = target.nextElementSibling, after = false if (floating) { var elTop = dragEl.offsetTop, tgTop = target.offsetTop if (elTop === tgTop) { after = (target.previousElementSibling === dragEl && !isWide) || (halfway && isWide) } else if ( target.previousElementSibling === dragEl || dragEl.previousElementSibling === target ) { after = (evt.clientY - targetRect.top) / height > 0.5 } else { after = tgTop > elTop } } else if (!isMovingBetweenSortable) { after = (nextSibling !== dragEl && !isLong) || (halfway && isLong) } var moveVector = _onMove( rootEl, el, dragEl, dragRect, target, targetRect, evt, after ) if (moveVector !== false) { if (moveVector === 1 || moveVector === -1) { after = moveVector === 1 } _silent = true setTimeout(_unsilent, 30) _cloneHide(activeSortable, isOwner) if (!dragEl.contains(el)) { if (after && !nextSibling) { el.appendChild(dragEl) } else { target.parentNode.insertBefore( dragEl, after ? nextSibling : target ) } } parentEl = dragEl.parentNode // actualization this._animate(dragRect, dragEl) this._animate(targetRect, target) } } } }, _animate: function(prevRect, target) { var ms = this.options.animation if (ms) { var currentRect = target.getBoundingClientRect() if (prevRect.nodeType === 1) { prevRect = prevRect.getBoundingClientRect() } _css(target, 'transition', 'none') _css( target, 'transform', 'translate3d(' + (prevRect.left - currentRect.left) + 'px,' + (prevRect.top - currentRect.top) + 'px,0)' ) target.offsetWidth // repaint _css(target, 'transition', 'all ' + ms + 'ms') _css(target, 'transform', 'translate3d(0,0,0)') clearTimeout(target.animated) target.animated = setTimeout(function() { _css(target, 'transition', '') _css(target, 'transform', '') target.animated = false }, ms) } }, _offUpEvents: function() { var ownerDocument = this.el.ownerDocument _off(document, 'touchmove', this._onTouchMove) _off(document, 'pointermove', this._onTouchMove) _off(ownerDocument, 'mouseup', this._onDrop) _off(ownerDocument, 'touchend', this._onDrop) _off(ownerDocument, 'pointerup', this._onDrop) _off(ownerDocument, 'touchcancel', this._onDrop) _off(ownerDocument, 'pointercancel', this._onDrop) _off(ownerDocument, 'selectstart', this) }, _onDrop: function(/**Event*/ evt) { var el = this.el, options = this.options clearInterval(this._loopId) clearInterval(autoScroll.pid) clearTimeout(this._dragStartTimer) _cancelNextTick(this._cloneId) _cancelNextTick(this._dragStartId) // Unbind events _off(document, 'mouseover', this) _off(document, 'mousemove', this._onTouchMove) if (this.nativeDraggable) { _off(document, 'drop', this) _off(el, 'dragstart', this._onDragStart) } this._offUpEvents() if (evt) { if (moved) { evt.preventDefault() !options.dropBubble && evt.stopPropagation() } ghostEl && ghostEl.parentNode && ghostEl.parentNode.removeChild(ghostEl) if ( rootEl === parentEl || Sortable.active.lastPullMode !== 'clone' ) { // Remove clone cloneEl && cloneEl.parentNode && cloneEl.parentNode.removeChild(cloneEl) } if (dragEl) { if (this.nativeDraggable) { _off(dragEl, 'dragend', this) } _disableDraggable(dragEl) dragEl.style['will-change'] = '' // Remove class's _toggleClass(dragEl, this.options.ghostClass, false) _toggleClass(dragEl, this.options.chosenClass, false) // Drag stop event _dispatchEvent( this, rootEl, 'unchoose', dragEl, parentEl, rootEl, oldIndex ) if (rootEl !== parentEl) { newIndex = _index(dragEl, options.draggable) if (newIndex >= 0) { // Add event _dispatchEvent( null, parentEl, 'add', dragEl, parentEl, rootEl, oldIndex, newIndex ) // Remove event _dispatchEvent( this, rootEl, 'remove', dragEl, parentEl, rootEl, oldIndex, newIndex ) // drag from one list and drop into another _dispatchEvent( null, parentEl, 'sort', dragEl, parentEl, rootEl, oldIndex, newIndex ) _dispatchEvent( this, rootEl, 'sort', dragEl, parentEl, rootEl, oldIndex, newIndex ) } } else { if (dragEl.nextSibling !== nextEl) { // Get the index of the dragged element within its parent newIndex = _index(dragEl, options.draggable) if (newIndex >= 0) { // drag & drop within the same list _dispatchEvent( this, rootEl, 'update', dragEl, parentEl, rootEl, oldIndex, newIndex ) _dispatchEvent( this, rootEl, 'sort', dragEl, parentEl, rootEl, oldIndex, newIndex ) } } } if (Sortable.active) { /* jshint eqnull:true */ if (newIndex == null || newIndex === -1) { newIndex = oldIndex } _dispatchEvent( this, rootEl, 'end', dragEl, parentEl, rootEl, oldIndex, newIndex ) // Save sorting this.save() } } } this._nulling() }, _nulling: function() { rootEl = dragEl = parentEl = ghostEl = nextEl = cloneEl = lastDownEl = scrollEl = scrollParentEl = tapEvt = touchEvt = moved = newIndex = lastEl = lastCSS = putSortable = activeGroup = Sortable.active = null savedInputChecked.forEach(function(el) { el.checked = true }) savedInputChecked.length = 0 }, handleEvent: function(/**Event*/ evt) { switch (evt.type) { case 'drop': case 'dragend': this._onDrop(evt) break case 'dragover': case 'dragenter': if (dragEl) { this._onDragOver(evt) _globalDragOver(evt) } break case 'mouseover': this._onDrop(evt) break case 'selectstart': evt.preventDefault() break } }, /** * Serializes the item into an array of string. * @returns {String[]} */ toArray: function() { var order = [], el, children = this.el.children, i = 0, n = children.length, options = this.options for (; i < n; i++) { el = children[i] if (_closest(el, options.draggable, this.el)) { order.push( el.getAttribute(options.dataIdAttr) || _generateId(el) ) } } return order }, /** * Sorts the elements according to the array. * @param {String[]} order order of the items */ sort: function(order) { var items = {}, rootEl = this.el this.toArray().forEach(function(id, i) { var el = rootEl.children[i] if (_closest(el, this.options.draggable, rootEl)) { items[id] = el } }, this) order.forEach(function(id) { if (items[id]) { rootEl.removeChild(items[id]) rootEl.appendChild(items[id]) } }) }, /** * Save the current sorting */ save: function() { var store = this.options.store store && store.set(this) }, /** * 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. * @param {HTMLElement} el * @param {String} [selector] default: `options.draggable` * @returns {HTMLElement|null} */ closest: function(el, selector) { return _closest(el, selector || this.options.draggable, this.el) }, /** * Set/get option * @param {string} name * @param {*} [value] * @returns {*} */ option: function(name, value) { var options = this.options if (value === void 0) { return options[name] } else { options[name] = value if (name === 'group') { _prepareGroup(options) } } }, /** * Destroy */ destroy: function() { var el = this.el el[expando] = null _off(el, 'mousedown', this._onTapStart) _off(el, 'touchstart', this._onTapStart) _off(el, 'pointerdown', this._onTapStart) if (this.nativeDraggable) { _off(el, 'dragover', this) _off(el, 'dragenter', this) } // Remove draggable attributes Array.prototype.forEach.call( el.querySelectorAll('[draggable]'), function(el) { el.removeAttribute('draggable') } ) touchDragOverListeners.splice( touchDragOverListeners.indexOf(this._onDragOver), 1 ) this._onDrop() this.el = el = null }, } function _cloneHide(sortable, state) { if (sortable.lastPullMode !== 'clone') { state = true } if (cloneEl && cloneEl.state !== state) { _css(cloneEl, 'display', state ? 'none' : '') if (!state) { if (cloneEl.state) { if (sortable.options.group.revertClone) { rootEl.insertBefore(cloneEl, nextEl) sortable._animate(dragEl, cloneEl) } else { rootEl.insertBefore(cloneEl, dragEl) } } } cloneEl.state = state } } function _closest( /**HTMLElement*/ el, /**String*/ selector, /**HTMLElement*/ ctx ) { if (el) { ctx = ctx || document do { if ( (selector === '>*' && el.parentNode === ctx) || _matches(el, selector) ) { return el } /* jshint boss:true */ } while ((el = _getParentOrHost(el))) } return null } function _getParentOrHost(el) { var parent = el.host return parent && parent.nodeType ? parent : el.parentNode } function _globalDragOver(/**Event*/ evt) { if (evt.dataTransfer) { evt.dataTransfer.dropEffect = 'move' } evt.preventDefault() } function _on(el, event, fn) { el.addEventListener(event, fn, captureMode) } function _off(el, event, fn) { el.removeEventListener(event, fn, captureMode) } function _toggleClass(el, name, state) { if (el) { if (el.classList) { el.classList[state ? 'add' : 'remove'](name) } else { var className = (' ' + el.className + ' ') .replace(R_SPACE, ' ') .replace(' ' + name + ' ', ' ') el.className = (className + (state ? ' ' + name : '')).replace( R_SPACE, ' ' ) } } } function _css(el, prop, val) { var style = el && el.style if (style) { if (val === void 0) { if ( document.defaultView && document.defaultView.getComputedStyle ) { val = document.defaultView.getComputedStyle(el, '') } else if (el.currentStyle) { val = el.currentStyle } return prop === void 0 ? val : val[prop] } else { if (!(prop in style)) { prop = '-webkit-' + prop } style[prop] = val + (typeof val === 'string' ? '' : 'px') } } } function _find(ctx, tagName, iterator) { if (ctx) { var list = ctx.getElementsByTagName(tagName), i = 0, n = list.length if (iterator) { for (; i < n; i++) { iterator(list[i], i) } } return list } return [] } function _dispatchEvent( sortable, rootEl, name, targetEl, toEl, fromEl, startIndex, newIndex ) { sortable = sortable || rootEl[expando] var evt = document.createEvent('Event'), options = sortable.options, onName = 'on' + name.charAt(0).toUpperCase() + name.substr(1) evt.initEvent(name, true, true) evt.to = toEl || rootEl evt.from = fromEl || rootEl evt.item = targetEl || rootEl evt.clone = cloneEl evt.oldIndex = startIndex evt.newIndex = newIndex rootEl.dispatchEvent(evt) if (options[onName]) { options[onName].call(sortable, evt) } } function _onMove( fromEl, toEl, dragEl, dragRect, targetEl, targetRect, originalEvt, willInsertAfter ) { var evt, sortable = fromEl[expando], onMoveFn = sortable.options.onMove, retVal evt = document.createEvent('Event') evt.initEvent('move', true, true) evt.to = toEl evt.from = fromEl evt.dragged = dragEl evt.draggedRect = dragRect evt.related = targetEl || toEl evt.relatedRect = targetRect || toEl.getBoundingClientRect() evt.willInsertAfter = willInsertAfter fromEl.dispatchEvent(evt) if (onMoveFn) { retVal = onMoveFn.call(sortable, evt, originalEvt) } return retVal } function _disableDraggable(el) { el.draggable = false } function _unsilent() { _silent = false } /** @returns {HTMLElement|false} */ function _ghostIsLast(el, evt) { var lastEl = el.lastElementChild, rect = lastEl.getBoundingClientRect() // 5 — min delta // abs — нельзя добавлять, а то глюки при наведении сверху return ( evt.clientY - (rect.top + rect.height) > 5 || evt.clientX - (rect.left + rect.width) > 5 ) } /** * Generate id * @param {HTMLElement} el * @returns {String} * @private */ function _generateId(el) { var str = el.tagName + el.className + el.src + el.href + el.textContent, i = str.length, sum = 0 while (i--) { sum += str.charCodeAt(i) } return sum.toString(36) } /** * Returns the index of an element within its parent for a selected set of * elements * @param {HTMLElement} el * @param {selector} selector * @return {number} */ function _index(el, selector) { var index = 0 if (!el || !el.parentNode) { return -1 } while (el && (el = el.previousElementSibling)) { if ( el.nodeName.toUpperCase() !== 'TEMPLATE' && (selector === '>*' || _matches(el, selector)) ) { index++ } } return index } function _matches(/**HTMLElement*/ el, /**String*/ selector) { if (el) { selector = selector.split('.') var tag = selector.shift().toUpperCase(), re = new RegExp('\\s(' + selector.join('|') + ')(?=\\s)', 'g') return ( (tag === '' || el.nodeName.toUpperCase() == tag) && (!selector.length || ((' ' + el.className + ' ').match(re) || []).length == selector.length) ) } return false } function _throttle(callback, ms) { var args, _this return function() { if (args === void 0) { args = arguments _this = this setTimeout(function() { if (args.length === 1) { callback.call(_this, args[0]) } else { callback.apply(_this, args) } args = void 0 }, ms) } } } function _extend(dst, src) { if (dst && src) { for (var key in src) { if (src.hasOwnProperty(key)) { dst[key] = src[key] } } } return dst } function _clone(el) { if (Polymer && Polymer.dom) { return Polymer.dom(el).cloneNode(true) } else if ($) { return $(el).clone(true)[0] } else { return el.cloneNode(true) } } function _saveInputCheckedState(root) { var inputs = root.getElementsByTagName('input') var idx = inputs.length while (idx--) { var el = inputs[idx] el.checked && savedInputChecked.push(el) } } function _nextTick(fn) { return setTimeout(fn, 0) } function _cancelNextTick(id) { return clearTimeout(id) } // Fixed #973: _on(document, 'touchmove', function(evt) { if (Sortable.active) { evt.preventDefault() } }) // Export utils Sortable.utils = { on: _on, off: _off, css: _css, find: _find, is: function(el, selector) { return !!_closest(el, selector, el) }, extend: _extend, throttle: _throttle, closest: _closest, toggleClass: _toggleClass, clone: _clone, index: _index, nextTick: _nextTick, cancelNextTick: _cancelNextTick, } /** * Create sortable instance * @param {HTMLElement} el * @param {Object} [options] */ Sortable.create = function(el, options) { return new Sortable(el, options) } // Export Sortable.version = '1.7.0' return Sortable })