| 1 |
/**! |
| 2 |
* Sortable |
| 3 |
* @author RubaXa <trash@rubaxa.org> |
| 4 |
* @author owenm <owen23355@gmail.com> |
| 5 |
* @license MIT |
| 6 |
*/ |
| 7 |
|
| 8 |
(function sortableModule(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 { |
| 18 |
/* jshint sub:true */ |
| 19 |
window["Sortable"] = factory(); |
| 20 |
} |
| 21 |
})(function sortableFactory() { |
| 22 |
"use strict"; |
| 23 |
|
| 24 |
if (typeof window === "undefined" || !window.document) { |
| 25 |
return function sortableError() { |
| 26 |
throw new Error("Sortable.js requires a window with a document"); |
| 27 |
}; |
| 28 |
} |
| 29 |
|
| 30 |
var dragEl, |
| 31 |
parentEl, |
| 32 |
ghostEl, |
| 33 |
cloneEl, |
| 34 |
rootEl, |
| 35 |
nextEl, |
| 36 |
lastDownEl, |
| 37 |
|
| 38 |
scrollEl, |
| 39 |
scrollParentEl, |
| 40 |
scrollCustomFn, |
| 41 |
|
| 42 |
oldIndex, |
| 43 |
newIndex, |
| 44 |
|
| 45 |
activeGroup, |
| 46 |
putSortable, |
| 47 |
|
| 48 |
autoScrolls = [], |
| 49 |
scrolling = false, |
| 50 |
|
| 51 |
awaitingDragStarted = false, |
| 52 |
ignoreNextClick = false, |
| 53 |
sortables = [], |
| 54 |
|
| 55 |
pointerElemChangedInterval, |
| 56 |
lastPointerElemX, |
| 57 |
lastPointerElemY, |
| 58 |
|
| 59 |
tapEvt, |
| 60 |
touchEvt, |
| 61 |
|
| 62 |
moved, |
| 63 |
|
| 64 |
|
| 65 |
lastTarget, |
| 66 |
lastDirection, |
| 67 |
pastFirstInvertThresh = false, |
| 68 |
isCircumstantialInvert = false, |
| 69 |
lastMode, // 'swap' or 'insert' |
| 70 |
|
| 71 |
targetMoveDistance, |
| 72 |
|
| 73 |
// For positioning ghost absolutely |
| 74 |
ghostRelativeParent, |
| 75 |
ghostRelativeParentInitialScroll = [], // (left, top) |
| 76 |
|
| 77 |
|
| 78 |
forRepaintDummy, |
| 79 |
realDragElRect, // dragEl rect after current animation |
| 80 |
|
| 81 |
/** @const */ |
| 82 |
R_SPACE = /\s+/g, |
| 83 |
|
| 84 |
expando = 'Sortable' + (new Date).getTime(), |
| 85 |
|
| 86 |
win = window, |
| 87 |
document = win.document, |
| 88 |
parseInt = win.parseInt, |
| 89 |
setTimeout = win.setTimeout, |
| 90 |
|
| 91 |
$ = win.jQuery || win.Zepto, |
| 92 |
Polymer = win.Polymer, |
| 93 |
|
| 94 |
captureMode = { |
| 95 |
capture: false, |
| 96 |
passive: false |
| 97 |
}, |
| 98 |
|
| 99 |
IE11OrLess = !!navigator.userAgent.match(/(?:Trident.*rv[ :]?11\.|msie|iemobile)/i), |
| 100 |
Edge = !!navigator.userAgent.match(/Edge/i), |
| 101 |
FireFox = !!navigator.userAgent.match(/firefox/i), |
| 102 |
Safari = !!(navigator.userAgent.match(/safari/i) && !navigator.userAgent.match(/chrome/i) && !navigator.userAgent.match(/android/i)), |
| 103 |
IOS = !!(navigator.userAgent.match(/iP(ad|od|hone)/i)), |
| 104 |
|
| 105 |
PositionGhostAbsolutely = IOS, |
| 106 |
|
| 107 |
CSSFloatProperty = Edge || IE11OrLess ? 'cssFloat' : 'float', |
| 108 |
|
| 109 |
// This will not pass for IE9, because IE9 DnD only works on anchors |
| 110 |
supportDraggable = ('draggable' in document.createElement('div')), |
| 111 |
|
| 112 |
supportCssPointerEvents = (function() { |
| 113 |
// false when <= IE11 |
| 114 |
if (IE11OrLess) { |
| 115 |
return false; |
| 116 |
} |
| 117 |
var el = document.createElement('x'); |
| 118 |
el.style.cssText = 'pointer-events:auto'; |
| 119 |
return el.style.pointerEvents === 'auto'; |
| 120 |
})(), |
| 121 |
|
| 122 |
_silent = false, |
| 123 |
_alignedSilent = false, |
| 124 |
|
| 125 |
abs = Math.abs, |
| 126 |
min = Math.min, |
| 127 |
max = Math.max, |
| 128 |
|
| 129 |
savedInputChecked = [], |
| 130 |
|
| 131 |
_detectDirection = function(el, options) { |
| 132 |
var elCSS = _css(el), |
| 133 |
elWidth = parseInt(elCSS.width) |
| 134 |
- parseInt(elCSS.paddingLeft) |
| 135 |
- parseInt(elCSS.paddingRight) |
| 136 |
- parseInt(elCSS.borderLeftWidth) |
| 137 |
- parseInt(elCSS.borderRightWidth), |
| 138 |
child1 = _getChild(el, 0, options), |
| 139 |
child2 = _getChild(el, 1, options), |
| 140 |
firstChildCSS = child1 && _css(child1), |
| 141 |
secondChildCSS = child2 && _css(child2), |
| 142 |
firstChildWidth = firstChildCSS && parseInt(firstChildCSS.marginLeft) + parseInt(firstChildCSS.marginRight) + _getRect(child1).width, |
| 143 |
secondChildWidth = secondChildCSS && parseInt(secondChildCSS.marginLeft) + parseInt(secondChildCSS.marginRight) + _getRect(child2).width; |
| 144 |
|
| 145 |
if (elCSS.display === 'flex') { |
| 146 |
return elCSS.flexDirection === 'column' || elCSS.flexDirection === 'column-reverse' |
| 147 |
? 'vertical' : 'horizontal'; |
| 148 |
} |
| 149 |
|
| 150 |
if (elCSS.display === 'grid') { |
| 151 |
return elCSS.gridTemplateColumns.split(' ').length <= 1 ? 'vertical' : 'horizontal'; |
| 152 |
} |
| 153 |
|
| 154 |
if (child1 && firstChildCSS.float !== 'none') { |
| 155 |
var touchingSideChild2 = firstChildCSS.float === 'left' ? 'left' : 'right'; |
| 156 |
|
| 157 |
return child2 && (secondChildCSS.clear === 'both' || secondChildCSS.clear === touchingSideChild2) ? |
| 158 |
'vertical' : 'horizontal'; |
| 159 |
} |
| 160 |
|
| 161 |
return (child1 && |
| 162 |
( |
| 163 |
firstChildCSS.display === 'block' || |
| 164 |
firstChildCSS.display === 'flex' || |
| 165 |
firstChildCSS.display === 'table' || |
| 166 |
firstChildCSS.display === 'grid' || |
| 167 |
firstChildWidth >= elWidth && |
| 168 |
elCSS[CSSFloatProperty] === 'none' || |
| 169 |
child2 && |
| 170 |
elCSS[CSSFloatProperty] === 'none' && |
| 171 |
firstChildWidth + secondChildWidth > elWidth |
| 172 |
) ? |
| 173 |
'vertical' : 'horizontal' |
| 174 |
); |
| 175 |
}, |
| 176 |
|
| 177 |
/** |
| 178 |
* Detects first nearest empty sortable to X and Y position using emptyInsertThreshold. |
| 179 |
* @param {Number} x X position |
| 180 |
* @param {Number} y Y position |
| 181 |
* @return {HTMLElement} Element of the first found nearest Sortable |
| 182 |
*/ |
| 183 |
_detectNearestEmptySortable = function(x, y) { |
| 184 |
for (var i = 0; i < sortables.length; i++) { |
| 185 |
if (_lastChild(sortables[i])) continue; |
| 186 |
|
| 187 |
var rect = _getRect(sortables[i]), |
| 188 |
threshold = sortables[i][expando].options.emptyInsertThreshold, |
| 189 |
insideHorizontally = x >= (rect.left - threshold) && x <= (rect.right + threshold), |
| 190 |
insideVertically = y >= (rect.top - threshold) && y <= (rect.bottom + threshold); |
| 191 |
|
| 192 |
if (insideHorizontally && insideVertically) { |
| 193 |
return sortables[i]; |
| 194 |
} |
| 195 |
} |
| 196 |
}, |
| 197 |
|
| 198 |
_isClientInRowColumn = function(x, y, el, axis, options) { |
| 199 |
var targetRect = _getRect(el), |
| 200 |
targetS1Opp = axis === 'vertical' ? targetRect.left : targetRect.top, |
| 201 |
targetS2Opp = axis === 'vertical' ? targetRect.right : targetRect.bottom, |
| 202 |
mouseOnOppAxis = axis === 'vertical' ? x : y; |
| 203 |
|
| 204 |
return targetS1Opp < mouseOnOppAxis && mouseOnOppAxis < targetS2Opp; |
| 205 |
}, |
| 206 |
|
| 207 |
_isElInRowColumn = function(el1, el2, axis) { |
| 208 |
var el1Rect = el1 === dragEl && realDragElRect || _getRect(el1), |
| 209 |
el2Rect = el2 === dragEl && realDragElRect || _getRect(el2), |
| 210 |
el1S1Opp = axis === 'vertical' ? el1Rect.left : el1Rect.top, |
| 211 |
el1S2Opp = axis === 'vertical' ? el1Rect.right : el1Rect.bottom, |
| 212 |
el1OppLength = axis === 'vertical' ? el1Rect.width : el1Rect.height, |
| 213 |
el2S1Opp = axis === 'vertical' ? el2Rect.left : el2Rect.top, |
| 214 |
el2S2Opp = axis === 'vertical' ? el2Rect.right : el2Rect.bottom, |
| 215 |
el2OppLength = axis === 'vertical' ? el2Rect.width : el2Rect.height; |
| 216 |
|
| 217 |
return ( |
| 218 |
el1S1Opp === el2S1Opp || |
| 219 |
el1S2Opp === el2S2Opp || |
| 220 |
(el1S1Opp + el1OppLength / 2) === (el2S1Opp + el2OppLength / 2) |
| 221 |
); |
| 222 |
}, |
| 223 |
|
| 224 |
_getParentAutoScrollElement = function(el, includeSelf) { |
| 225 |
// skip to window |
| 226 |
if (!el || !el.getBoundingClientRect) return _getWindowScrollingElement(); |
| 227 |
|
| 228 |
var elem = el; |
| 229 |
var gotSelf = false; |
| 230 |
do { |
| 231 |
// we don't need to get elem css if it isn't even overflowing in the first place (performance) |
| 232 |
if (elem.clientWidth < elem.scrollWidth || elem.clientHeight < elem.scrollHeight) { |
| 233 |
var elemCSS = _css(elem); |
| 234 |
if ( |
| 235 |
elem.clientWidth < elem.scrollWidth && (elemCSS.overflowX == 'auto' || elemCSS.overflowX == 'scroll') || |
| 236 |
elem.clientHeight < elem.scrollHeight && (elemCSS.overflowY == 'auto' || elemCSS.overflowY == 'scroll') |
| 237 |
) { |
| 238 |
if (!elem || !elem.getBoundingClientRect || elem === document.body) return _getWindowScrollingElement(); |
| 239 |
|
| 240 |
if (gotSelf || includeSelf) return elem; |
| 241 |
gotSelf = true; |
| 242 |
} |
| 243 |
} |
| 244 |
/* jshint boss:true */ |
| 245 |
} while (elem = elem.parentNode); |
| 246 |
|
| 247 |
return _getWindowScrollingElement(); |
| 248 |
}, |
| 249 |
|
| 250 |
_getWindowScrollingElement = function() { |
| 251 |
if (IE11OrLess) { |
| 252 |
return document.documentElement; |
| 253 |
} else { |
| 254 |
return document.scrollingElement; |
| 255 |
} |
| 256 |
}, |
| 257 |
|
| 258 |
_scrollBy = function(el, x, y) { |
| 259 |
el.scrollLeft += x; |
| 260 |
el.scrollTop += y; |
| 261 |
}, |
| 262 |
|
| 263 |
_autoScroll = _throttle(function (/**Event*/evt, /**Object*/options, /**HTMLElement*/rootEl, /**Boolean*/isFallback) { |
| 264 |
// Bug: https://bugzilla.mozilla.org/show_bug.cgi?id=505521 |
| 265 |
if (options.scroll) { |
| 266 |
var _this = rootEl ? rootEl[expando] : window, |
| 267 |
sens = options.scrollSensitivity, |
| 268 |
speed = options.scrollSpeed, |
| 269 |
|
| 270 |
x = evt.clientX, |
| 271 |
y = evt.clientY, |
| 272 |
|
| 273 |
winScroller = _getWindowScrollingElement(), |
| 274 |
|
| 275 |
scrollThisInstance = false; |
| 276 |
|
| 277 |
// Detect scrollEl |
| 278 |
if (scrollParentEl !== rootEl) { |
| 279 |
_clearAutoScrolls(); |
| 280 |
|
| 281 |
scrollEl = options.scroll; |
| 282 |
scrollCustomFn = options.scrollFn; |
| 283 |
|
| 284 |
if (scrollEl === true) { |
| 285 |
scrollEl = _getParentAutoScrollElement(rootEl, true); |
| 286 |
scrollParentEl = scrollEl; |
| 287 |
} |
| 288 |
} |
| 289 |
|
| 290 |
|
| 291 |
var layersOut = 0; |
| 292 |
var currentParent = scrollEl; |
| 293 |
do { |
| 294 |
var el = currentParent, |
| 295 |
rect = _getRect(el), |
| 296 |
|
| 297 |
top = rect.top, |
| 298 |
bottom = rect.bottom, |
| 299 |
left = rect.left, |
| 300 |
right = rect.right, |
| 301 |
|
| 302 |
width = rect.width, |
| 303 |
height = rect.height, |
| 304 |
|
| 305 |
scrollWidth, |
| 306 |
scrollHeight, |
| 307 |
|
| 308 |
css, |
| 309 |
|
| 310 |
vx, |
| 311 |
vy, |
| 312 |
|
| 313 |
canScrollX, |
| 314 |
canScrollY, |
| 315 |
|
| 316 |
scrollPosX, |
| 317 |
scrollPosY; |
| 318 |
|
| 319 |
|
| 320 |
scrollWidth = el.scrollWidth; |
| 321 |
scrollHeight = el.scrollHeight; |
| 322 |
|
| 323 |
css = _css(el); |
| 324 |
|
| 325 |
scrollPosX = el.scrollLeft; |
| 326 |
scrollPosY = el.scrollTop; |
| 327 |
|
| 328 |
if (el === winScroller) { |
| 329 |
canScrollX = width < scrollWidth && (css.overflowX === 'auto' || css.overflowX === 'scroll' || css.overflowX === 'visible'); |
| 330 |
canScrollY = height < scrollHeight && (css.overflowY === 'auto' || css.overflowY === 'scroll' || css.overflowY === 'visible'); |
| 331 |
} else { |
| 332 |
canScrollX = width < scrollWidth && (css.overflowX === 'auto' || css.overflowX === 'scroll'); |
| 333 |
canScrollY = height < scrollHeight && (css.overflowY === 'auto' || css.overflowY === 'scroll'); |
| 334 |
} |
| 335 |
|
| 336 |
vx = canScrollX && (abs(right - x) <= sens && (scrollPosX + width) < scrollWidth) - (abs(left - x) <= sens && !!scrollPosX); |
| 337 |
|
| 338 |
vy = canScrollY && (abs(bottom - y) <= sens && (scrollPosY + height) < scrollHeight) - (abs(top - y) <= sens && !!scrollPosY); |
| 339 |
|
| 340 |
|
| 341 |
if (!autoScrolls[layersOut]) { |
| 342 |
for (var i = 0; i <= layersOut; i++) { |
| 343 |
if (!autoScrolls[i]) { |
| 344 |
autoScrolls[i] = {}; |
| 345 |
} |
| 346 |
} |
| 347 |
} |
| 348 |
|
| 349 |
if (autoScrolls[layersOut].vx != vx || autoScrolls[layersOut].vy != vy || autoScrolls[layersOut].el !== el) { |
| 350 |
autoScrolls[layersOut].el = el; |
| 351 |
autoScrolls[layersOut].vx = vx; |
| 352 |
autoScrolls[layersOut].vy = vy; |
| 353 |
|
| 354 |
clearInterval(autoScrolls[layersOut].pid); |
| 355 |
|
| 356 |
if (el && (vx != 0 || vy != 0)) { |
| 357 |
scrollThisInstance = true; |
| 358 |
/* jshint loopfunc:true */ |
| 359 |
autoScrolls[layersOut].pid = setInterval((function () { |
| 360 |
// emulate drag over during autoscroll (fallback), emulating native DnD behaviour |
| 361 |
if (isFallback && this.layer === 0) { |
| 362 |
Sortable.active._emulateDragOver(true); |
| 363 |
Sortable.active._onTouchMove(touchEvt, true); |
| 364 |
} |
| 365 |
var scrollOffsetY = autoScrolls[this.layer].vy ? autoScrolls[this.layer].vy * speed : 0; |
| 366 |
var scrollOffsetX = autoScrolls[this.layer].vx ? autoScrolls[this.layer].vx * speed : 0; |
| 367 |
|
| 368 |
if ('function' === typeof(scrollCustomFn)) { |
| 369 |
if (scrollCustomFn.call(_this, scrollOffsetX, scrollOffsetY, evt, touchEvt, autoScrolls[this.layer].el) !== 'continue') { |
| 370 |
return; |
| 371 |
} |
| 372 |
} |
| 373 |
|
| 374 |
_scrollBy(autoScrolls[this.layer].el, scrollOffsetX, scrollOffsetY); |
| 375 |
}).bind({layer: layersOut}), 24); |
| 376 |
} |
| 377 |
} |
| 378 |
layersOut++; |
| 379 |
} while (options.bubbleScroll && currentParent !== winScroller && (currentParent = _getParentAutoScrollElement(currentParent, false))); |
| 380 |
scrolling = scrollThisInstance; // in case another function catches scrolling as false in between when it is not |
| 381 |
} |
| 382 |
}, 30), |
| 383 |
|
| 384 |
_clearAutoScrolls = function () { |
| 385 |
autoScrolls.forEach(function(autoScroll) { |
| 386 |
clearInterval(autoScroll.pid); |
| 387 |
}); |
| 388 |
autoScrolls = []; |
| 389 |
}, |
| 390 |
|
| 391 |
_prepareGroup = function (options) { |
| 392 |
function toFn(value, pull) { |
| 393 |
return function(to, from, dragEl, evt) { |
| 394 |
var sameGroup = to.options.group.name && |
| 395 |
from.options.group.name && |
| 396 |
to.options.group.name === from.options.group.name; |
| 397 |
|
| 398 |
if (value == null && (pull || sameGroup)) { |
| 399 |
// Default pull value |
| 400 |
// Default pull and put value if same group |
| 401 |
return true; |
| 402 |
} else if (value == null || value === false) { |
| 403 |
return false; |
| 404 |
} else if (pull && value === 'clone') { |
| 405 |
return value; |
| 406 |
} else if (typeof value === 'function') { |
| 407 |
return toFn(value(to, from, dragEl, evt), pull)(to, from, dragEl, evt); |
| 408 |
} else { |
| 409 |
var otherGroup = (pull ? to : from).options.group.name; |
| 410 |
|
| 411 |
return (value === true || |
| 412 |
(typeof value === 'string' && value === otherGroup) || |
| 413 |
(value.join && value.indexOf(otherGroup) > -1)); |
| 414 |
} |
| 415 |
}; |
| 416 |
} |
| 417 |
|
| 418 |
var group = {}; |
| 419 |
var originalGroup = options.group; |
| 420 |
|
| 421 |
if (!originalGroup || typeof originalGroup != 'object') { |
| 422 |
originalGroup = {name: originalGroup}; |
| 423 |
} |
| 424 |
|
| 425 |
group.name = originalGroup.name; |
| 426 |
group.checkPull = toFn(originalGroup.pull, true); |
| 427 |
group.checkPut = toFn(originalGroup.put); |
| 428 |
group.revertClone = originalGroup.revertClone; |
| 429 |
|
| 430 |
options.group = group; |
| 431 |
}, |
| 432 |
|
| 433 |
_checkAlignment = function(evt) { |
| 434 |
if (!dragEl || !dragEl.parentNode) return; |
| 435 |
dragEl.parentNode[expando] && dragEl.parentNode[expando]._computeIsAligned(evt); |
| 436 |
}, |
| 437 |
|
| 438 |
_isTrueParentSortable = function(el, target) { |
| 439 |
var trueParent = target; |
| 440 |
while (!trueParent[expando]) { |
| 441 |
trueParent = trueParent.parentNode; |
| 442 |
} |
| 443 |
|
| 444 |
return el === trueParent; |
| 445 |
}, |
| 446 |
|
| 447 |
_artificalBubble = function(sortable, originalEvt, method) { |
| 448 |
// Artificial IE bubbling |
| 449 |
var nextParent = sortable.parentNode; |
| 450 |
while (nextParent && !nextParent[expando]) { |
| 451 |
nextParent = nextParent.parentNode; |
| 452 |
} |
| 453 |
|
| 454 |
if (nextParent) { |
| 455 |
nextParent[expando][method](_extend(originalEvt, { |
| 456 |
artificialBubble: true |
| 457 |
})); |
| 458 |
} |
| 459 |
}, |
| 460 |
|
| 461 |
_hideGhostForTarget = function() { |
| 462 |
if (!supportCssPointerEvents && ghostEl) { |
| 463 |
_css(ghostEl, 'display', 'none'); |
| 464 |
} |
| 465 |
}, |
| 466 |
|
| 467 |
_unhideGhostForTarget = function() { |
| 468 |
if (!supportCssPointerEvents && ghostEl) { |
| 469 |
_css(ghostEl, 'display', ''); |
| 470 |
} |
| 471 |
}; |
| 472 |
|
| 473 |
|
| 474 |
// #1184 fix - Prevent click event on fallback if dragged but item not changed position |
| 475 |
document.addEventListener('click', function(evt) { |
| 476 |
if (ignoreNextClick) { |
| 477 |
evt.preventDefault(); |
| 478 |
evt.stopPropagation && evt.stopPropagation(); |
| 479 |
evt.stopImmediatePropagation && evt.stopImmediatePropagation(); |
| 480 |
ignoreNextClick = false; |
| 481 |
return false; |
| 482 |
} |
| 483 |
}, true); |
| 484 |
|
| 485 |
var nearestEmptyInsertDetectEvent = function(evt) { |
| 486 |
evt = evt.touches ? evt.touches[0] : evt; |
| 487 |
if (dragEl) { |
| 488 |
var nearest = _detectNearestEmptySortable(evt.clientX, evt.clientY); |
| 489 |
|
| 490 |
if (nearest) { |
| 491 |
nearest[expando]._onDragOver({ |
| 492 |
clientX: evt.clientX, |
| 493 |
clientY: evt.clientY, |
| 494 |
target: nearest, |
| 495 |
rootEl: nearest |
| 496 |
}); |
| 497 |
} |
| 498 |
} |
| 499 |
}; |
| 500 |
// We do not want this to be triggered if completed (bubbling canceled), so only define it here |
| 501 |
_on(document, 'dragover', nearestEmptyInsertDetectEvent); |
| 502 |
_on(document, 'mousemove', nearestEmptyInsertDetectEvent); |
| 503 |
_on(document, 'touchmove', nearestEmptyInsertDetectEvent); |
| 504 |
|
| 505 |
/** |
| 506 |
* @class Sortable |
| 507 |
* @param {HTMLElement} el |
| 508 |
* @param {Object} [options] |
| 509 |
*/ |
| 510 |
function Sortable(el, options) { |
| 511 |
if (!(el && el.nodeType && el.nodeType === 1)) { |
| 512 |
throw 'Sortable: `el` must be HTMLElement, not ' + {}.toString.call(el); |
| 513 |
} |
| 514 |
|
| 515 |
this.el = el; // root element |
| 516 |
this.options = options = _extend({}, options); |
| 517 |
|
| 518 |
|
| 519 |
// Export instance |
| 520 |
el[expando] = this; |
| 521 |
|
| 522 |
// Default options |
| 523 |
var defaults = { |
| 524 |
group: null, |
| 525 |
sort: true, |
| 526 |
disabled: false, |
| 527 |
store: null, |
| 528 |
handle: null, |
| 529 |
scroll: true, |
| 530 |
scrollSensitivity: 30, |
| 531 |
scrollSpeed: 10, |
| 532 |
bubbleScroll: true, |
| 533 |
draggable: /[uo]l/i.test(el.nodeName) ? '>li' : '>*', |
| 534 |
swapThreshold: 1, // percentage; 0 <= x <= 1 |
| 535 |
invertSwap: false, // invert always |
| 536 |
invertedSwapThreshold: null, // will be set to same as swapThreshold if default |
| 537 |
removeCloneOnHide: true, |
| 538 |
direction: function() { |
| 539 |
return _detectDirection(el, this.options); |
| 540 |
}, |
| 541 |
ghostClass: 'sortable-ghost', |
| 542 |
chosenClass: 'sortable-chosen', |
| 543 |
dragClass: 'sortable-drag', |
| 544 |
ignore: 'a, img', |
| 545 |
filter: null, |
| 546 |
preventOnFilter: true, |
| 547 |
animation: 0, |
| 548 |
easing: null, |
| 549 |
setData: function (dataTransfer, dragEl) { |
| 550 |
dataTransfer.setData('Text', dragEl.textContent); |
| 551 |
}, |
| 552 |
dropBubble: false, |
| 553 |
dragoverBubble: false, |
| 554 |
dataIdAttr: 'data-id', |
| 555 |
delay: 0, |
| 556 |
touchStartThreshold: parseInt(window.devicePixelRatio, 10) || 1, |
| 557 |
forceFallback: false, |
| 558 |
fallbackClass: 'sortable-fallback', |
| 559 |
fallbackOnBody: false, |
| 560 |
fallbackTolerance: 0, |
| 561 |
fallbackOffset: {x: 0, y: 0}, |
| 562 |
supportPointer: Sortable.supportPointer !== false && ( |
| 563 |
('PointerEvent' in window) || |
| 564 |
window.navigator && ('msPointerEnabled' in window.navigator) // microsoft |
| 565 |
), |
| 566 |
emptyInsertThreshold: 5 |
| 567 |
}; |
| 568 |
|
| 569 |
|
| 570 |
// Set default options |
| 571 |
for (var name in defaults) { |
| 572 |
!(name in options) && (options[name] = defaults[name]); |
| 573 |
} |
| 574 |
|
| 575 |
_prepareGroup(options); |
| 576 |
|
| 577 |
// Bind all private methods |
| 578 |
for (var fn in this) { |
| 579 |
if (fn.charAt(0) === '_' && typeof this[fn] === 'function') { |
| 580 |
this[fn] = this[fn].bind(this); |
| 581 |
} |
| 582 |
} |
| 583 |
|
| 584 |
// Setup drag mode |
| 585 |
this.nativeDraggable = options.forceFallback ? false : supportDraggable; |
| 586 |
|
| 587 |
if (this.nativeDraggable) { |
| 588 |
// Touch start threshold cannot be greater than the native dragstart threshold |
| 589 |
this.options.touchStartThreshold = 1; |
| 590 |
} |
| 591 |
|
| 592 |
// Bind events |
| 593 |
if (options.supportPointer) { |
| 594 |
_on(el, 'pointerdown', this._onTapStart); |
| 595 |
} else { |
| 596 |
_on(el, 'mousedown', this._onTapStart); |
| 597 |
_on(el, 'touchstart', this._onTapStart); |
| 598 |
} |
| 599 |
|
| 600 |
if (this.nativeDraggable) { |
| 601 |
_on(el, 'dragover', this); |
| 602 |
_on(el, 'dragenter', this); |
| 603 |
} |
| 604 |
|
| 605 |
sortables.push(this.el); |
| 606 |
|
| 607 |
// Restore sorting |
| 608 |
options.store && options.store.get && this.sort(options.store.get(this) || []); |
| 609 |
} |
| 610 |
|
| 611 |
Sortable.prototype = /** @lends Sortable.prototype */ { |
| 612 |
constructor: Sortable, |
| 613 |
|
| 614 |
_computeIsAligned: function(evt) { |
| 615 |
var target; |
| 616 |
|
| 617 |
if (ghostEl && !supportCssPointerEvents) { |
| 618 |
_hideGhostForTarget(); |
| 619 |
target = document.elementFromPoint(evt.clientX, evt.clientY); |
| 620 |
_unhideGhostForTarget(); |
| 621 |
} else { |
| 622 |
target = evt.target; |
| 623 |
} |
| 624 |
|
| 625 |
target = _closest(target, this.options.draggable, this.el, false); |
| 626 |
if (_alignedSilent) return; |
| 627 |
if (!dragEl || dragEl.parentNode !== this.el) return; |
| 628 |
|
| 629 |
var children = this.el.children; |
| 630 |
for (var i = 0; i < children.length; i++) { |
| 631 |
// Don't change for target in case it is changed to aligned before onDragOver is fired |
| 632 |
if (_closest(children[i], this.options.draggable, this.el, false) && children[i] !== target) { |
| 633 |
children[i].sortableMouseAligned = _isClientInRowColumn(evt.clientX, evt.clientY, children[i], this._getDirection(evt, null), this.options); |
| 634 |
} |
| 635 |
} |
| 636 |
// Used for nulling last target when not in element, nothing to do with checking if aligned |
| 637 |
if (!_closest(target, this.options.draggable, this.el, true)) { |
| 638 |
lastTarget = null; |
| 639 |
} |
| 640 |
|
| 641 |
_alignedSilent = true; |
| 642 |
setTimeout(function() { |
| 643 |
_alignedSilent = false; |
| 644 |
}, 30); |
| 645 |
|
| 646 |
}, |
| 647 |
|
| 648 |
_getDirection: function(evt, target) { |
| 649 |
return (typeof this.options.direction === 'function') ? this.options.direction.call(this, evt, target, dragEl) : this.options.direction; |
| 650 |
}, |
| 651 |
|
| 652 |
_onTapStart: function (/** Event|TouchEvent */evt) { |
| 653 |
if (!evt.cancelable) return; |
| 654 |
var _this = this, |
| 655 |
el = this.el, |
| 656 |
options = this.options, |
| 657 |
preventOnFilter = options.preventOnFilter, |
| 658 |
type = evt.type, |
| 659 |
touch = evt.touches && evt.touches[0], |
| 660 |
target = (touch || evt).target, |
| 661 |
originalTarget = evt.target.shadowRoot && ((evt.path && evt.path[0]) || (evt.composedPath && evt.composedPath()[0])) || target, |
| 662 |
filter = options.filter, |
| 663 |
startIndex; |
| 664 |
|
| 665 |
_saveInputCheckedState(el); |
| 666 |
|
| 667 |
|
| 668 |
// IE: Calls events in capture mode if event element is nested. This ensures only correct element's _onTapStart goes through. |
| 669 |
// This process is also done in _onDragOver |
| 670 |
if (IE11OrLess && !evt.artificialBubble && !_isTrueParentSortable(el, target)) { |
| 671 |
return; |
| 672 |
} |
| 673 |
|
| 674 |
// Don't trigger start event when an element is been dragged, otherwise the evt.oldindex always wrong when set option.group. |
| 675 |
if (dragEl) { |
| 676 |
return; |
| 677 |
} |
| 678 |
|
| 679 |
if (/mousedown|pointerdown/.test(type) && evt.button !== 0 || options.disabled) { |
| 680 |
return; // only left button and enabled |
| 681 |
} |
| 682 |
|
| 683 |
// cancel dnd if original target is content editable |
| 684 |
if (originalTarget.isContentEditable) { |
| 685 |
return; |
| 686 |
} |
| 687 |
|
| 688 |
target = _closest(target, options.draggable, el, false); |
| 689 |
|
| 690 |
if (!target) { |
| 691 |
if (IE11OrLess) { |
| 692 |
_artificalBubble(el, evt, '_onTapStart'); |
| 693 |
} |
| 694 |
return; |
| 695 |
} |
| 696 |
|
| 697 |
if (lastDownEl === target) { |
| 698 |
// Ignoring duplicate `down` |
| 699 |
return; |
| 700 |
} |
| 701 |
|
| 702 |
// Get the index of the dragged element within its parent |
| 703 |
startIndex = _index(target, options.draggable); |
| 704 |
|
| 705 |
// Check filter |
| 706 |
if (typeof filter === 'function') { |
| 707 |
if (filter.call(this, evt, target, this)) { |
| 708 |
_dispatchEvent(_this, originalTarget, 'filter', target, el, el, startIndex); |
| 709 |
preventOnFilter && evt.cancelable && evt.preventDefault(); |
| 710 |
return; // cancel dnd |
| 711 |
} |
| 712 |
} |
| 713 |
else if (filter) { |
| 714 |
filter = filter.split(',').some(function (criteria) { |
| 715 |
criteria = _closest(originalTarget, criteria.trim(), el, false); |
| 716 |
|
| 717 |
if (criteria) { |
| 718 |
_dispatchEvent(_this, criteria, 'filter', target, el, el, startIndex); |
| 719 |
return true; |
| 720 |
} |
| 721 |
}); |
| 722 |
|
| 723 |
if (filter) { |
| 724 |
preventOnFilter && evt.cancelable && evt.preventDefault(); |
| 725 |
return; // cancel dnd |
| 726 |
} |
| 727 |
} |
| 728 |
|
| 729 |
if (options.handle && !_closest(originalTarget, options.handle, el, false)) { |
| 730 |
return; |
| 731 |
} |
| 732 |
|
| 733 |
// Prepare `dragstart` |
| 734 |
this._prepareDragStart(evt, touch, target, startIndex); |
| 735 |
}, |
| 736 |
|
| 737 |
|
| 738 |
_handleAutoScroll: function(evt, fallback) { |
| 739 |
if (!dragEl || !this.options.scroll) return; |
| 740 |
var x = evt.clientX, |
| 741 |
y = evt.clientY, |
| 742 |
|
| 743 |
elem = document.elementFromPoint(x, y), |
| 744 |
_this = this; |
| 745 |
|
| 746 |
// IE does not seem to have native autoscroll, |
| 747 |
// Edge's autoscroll seems too conditional, |
| 748 |
// MACOS Safari does not have autoscroll, |
| 749 |
// Firefox and Chrome are good |
| 750 |
if (fallback || Edge || IE11OrLess || Safari) { |
| 751 |
_autoScroll(evt, _this.options, elem, fallback); |
| 752 |
|
| 753 |
// Listener for pointer element change |
| 754 |
var ogElemScroller = _getParentAutoScrollElement(elem, true); |
| 755 |
if ( |
| 756 |
scrolling && |
| 757 |
( |
| 758 |
!pointerElemChangedInterval || |
| 759 |
x !== lastPointerElemX || |
| 760 |
y !== lastPointerElemY |
| 761 |
) |
| 762 |
) { |
| 763 |
|
| 764 |
pointerElemChangedInterval && clearInterval(pointerElemChangedInterval); |
| 765 |
// Detect for pointer elem change, emulating native DnD behaviour |
| 766 |
pointerElemChangedInterval = setInterval(function() { |
| 767 |
if (!dragEl) return; |
| 768 |
// could also check if scroll direction on newElem changes due to parent autoscrolling |
| 769 |
var newElem = _getParentAutoScrollElement(document.elementFromPoint(x, y), true); |
| 770 |
if (newElem !== ogElemScroller) { |
| 771 |
ogElemScroller = newElem; |
| 772 |
_clearAutoScrolls(); |
| 773 |
_autoScroll(evt, _this.options, ogElemScroller, fallback); |
| 774 |
} |
| 775 |
}, 10); |
| 776 |
lastPointerElemX = x; |
| 777 |
lastPointerElemY = y; |
| 778 |
} |
| 779 |
|
| 780 |
} else { |
| 781 |
// if DnD is enabled (and browser has good autoscrolling), first autoscroll will already scroll, so get parent autoscroll of first autoscroll |
| 782 |
if (!_this.options.bubbleScroll || _getParentAutoScrollElement(elem, true) === _getWindowScrollingElement()) { |
| 783 |
_clearAutoScrolls(); |
| 784 |
//return; |
| 785 |
} |
| 786 |
_autoScroll(evt, _this.options, _getParentAutoScrollElement(elem, false), false); |
| 787 |
} |
| 788 |
}, |
| 789 |
|
| 790 |
_prepareDragStart: function (/** Event */evt, /** Touch */touch, /** HTMLElement */target, /** Number */startIndex) { |
| 791 |
var _this = this, |
| 792 |
el = _this.el, |
| 793 |
options = _this.options, |
| 794 |
ownerDocument = el.ownerDocument, |
| 795 |
dragStartFn; |
| 796 |
|
| 797 |
if (target && !dragEl && (target.parentNode === el)) { |
| 798 |
rootEl = el; |
| 799 |
dragEl = target; |
| 800 |
parentEl = dragEl.parentNode; |
| 801 |
nextEl = dragEl.nextSibling; |
| 802 |
lastDownEl = target; |
| 803 |
activeGroup = options.group; |
| 804 |
oldIndex = startIndex; |
| 805 |
|
| 806 |
tapEvt = { |
| 807 |
target: dragEl, |
| 808 |
clientX: (touch || evt).clientX, |
| 809 |
clientY: (touch || evt).clientY |
| 810 |
}; |
| 811 |
|
| 812 |
this._lastX = (touch || evt).clientX; |
| 813 |
this._lastY = (touch || evt).clientY; |
| 814 |
|
| 815 |
dragEl.style['will-change'] = 'all'; |
| 816 |
// undo animation if needed |
| 817 |
dragEl.style.transition = ''; |
| 818 |
dragEl.style.transform = ''; |
| 819 |
|
| 820 |
dragStartFn = function () { |
| 821 |
// Delayed drag has been triggered |
| 822 |
// we can re-enable the events: touchmove/mousemove |
| 823 |
_this._disableDelayedDragEvents(); |
| 824 |
|
| 825 |
if (!FireFox && _this.nativeDraggable) { |
| 826 |
dragEl.draggable = true; |
| 827 |
} |
| 828 |
|
| 829 |
// Bind the events: dragstart/dragend |
| 830 |
_this._triggerDragStart(evt, touch); |
| 831 |
|
| 832 |
// Drag start event |
| 833 |
_dispatchEvent(_this, rootEl, 'choose', dragEl, rootEl, rootEl, oldIndex); |
| 834 |
|
| 835 |
// Chosen item |
| 836 |
_toggleClass(dragEl, options.chosenClass, true); |
| 837 |
}; |
| 838 |
|
| 839 |
// Disable "draggable" |
| 840 |
options.ignore.split(',').forEach(function (criteria) { |
| 841 |
_find(dragEl, criteria.trim(), _disableDraggable); |
| 842 |
}); |
| 843 |
|
| 844 |
if (options.supportPointer) { |
| 845 |
_on(ownerDocument, 'pointerup', _this._onDrop); |
| 846 |
} else { |
| 847 |
_on(ownerDocument, 'mouseup', _this._onDrop); |
| 848 |
_on(ownerDocument, 'touchend', _this._onDrop); |
| 849 |
_on(ownerDocument, 'touchcancel', _this._onDrop); |
| 850 |
} |
| 851 |
|
| 852 |
// Make dragEl draggable (must be before delay for FireFox) |
| 853 |
if (FireFox && this.nativeDraggable) { |
| 854 |
this.options.touchStartThreshold = 4; |
| 855 |
dragEl.draggable = true; |
| 856 |
} |
| 857 |
|
| 858 |
// Delay is impossible for native DnD in Edge or IE |
| 859 |
if (options.delay && (!this.nativeDraggable || !(Edge || IE11OrLess))) { |
| 860 |
// If the user moves the pointer or let go the click or touch |
| 861 |
// before the delay has been reached: |
| 862 |
// disable the delayed drag |
| 863 |
_on(ownerDocument, 'mouseup', _this._disableDelayedDrag); |
| 864 |
_on(ownerDocument, 'touchend', _this._disableDelayedDrag); |
| 865 |
_on(ownerDocument, 'touchcancel', _this._disableDelayedDrag); |
| 866 |
_on(ownerDocument, 'mousemove', _this._delayedDragTouchMoveHandler); |
| 867 |
_on(ownerDocument, 'touchmove', _this._delayedDragTouchMoveHandler); |
| 868 |
options.supportPointer && _on(ownerDocument, 'pointermove', _this._delayedDragTouchMoveHandler); |
| 869 |
|
| 870 |
_this._dragStartTimer = setTimeout(dragStartFn, options.delay); |
| 871 |
} else { |
| 872 |
dragStartFn(); |
| 873 |
} |
| 874 |
} |
| 875 |
}, |
| 876 |
|
| 877 |
_delayedDragTouchMoveHandler: function (/** TouchEvent|PointerEvent **/e) { |
| 878 |
var touch = e.touches ? e.touches[0] : e; |
| 879 |
if (max(abs(touch.clientX - this._lastX), abs(touch.clientY - this._lastY)) |
| 880 |
>= Math.floor(this.options.touchStartThreshold / (this.nativeDraggable && window.devicePixelRatio || 1)) |
| 881 |
) { |
| 882 |
this._disableDelayedDrag(); |
| 883 |
} |
| 884 |
}, |
| 885 |
|
| 886 |
_disableDelayedDrag: function () { |
| 887 |
dragEl && _disableDraggable(dragEl); |
| 888 |
clearTimeout(this._dragStartTimer); |
| 889 |
|
| 890 |
this._disableDelayedDragEvents(); |
| 891 |
}, |
| 892 |
|
| 893 |
_disableDelayedDragEvents: function () { |
| 894 |
var ownerDocument = this.el.ownerDocument; |
| 895 |
_off(ownerDocument, 'mouseup', this._disableDelayedDrag); |
| 896 |
_off(ownerDocument, 'touchend', this._disableDelayedDrag); |
| 897 |
_off(ownerDocument, 'touchcancel', this._disableDelayedDrag); |
| 898 |
_off(ownerDocument, 'mousemove', this._delayedDragTouchMoveHandler); |
| 899 |
_off(ownerDocument, 'touchmove', this._delayedDragTouchMoveHandler); |
| 900 |
_off(ownerDocument, 'pointermove', this._delayedDragTouchMoveHandler); |
| 901 |
}, |
| 902 |
|
| 903 |
_triggerDragStart: function (/** Event */evt, /** Touch */touch) { |
| 904 |
touch = touch || (evt.pointerType == 'touch' ? evt : null); |
| 905 |
|
| 906 |
if (!this.nativeDraggable || touch) { |
| 907 |
if (this.options.supportPointer) { |
| 908 |
_on(document, 'pointermove', this._onTouchMove); |
| 909 |
} else if (touch) { |
| 910 |
_on(document, 'touchmove', this._onTouchMove); |
| 911 |
} else { |
| 912 |
_on(document, 'mousemove', this._onTouchMove); |
| 913 |
} |
| 914 |
} else { |
| 915 |
_on(dragEl, 'dragend', this); |
| 916 |
_on(rootEl, 'dragstart', this._onDragStart); |
| 917 |
} |
| 918 |
|
| 919 |
try { |
| 920 |
if (document.selection) { |
| 921 |
// Timeout neccessary for IE9 |
| 922 |
_nextTick(function () { |
| 923 |
document.selection.empty(); |
| 924 |
}); |
| 925 |
} else { |
| 926 |
window.getSelection().removeAllRanges(); |
| 927 |
} |
| 928 |
} catch (err) { |
| 929 |
} |
| 930 |
}, |
| 931 |
|
| 932 |
_dragStarted: function (fallback, evt) { |
| 933 |
awaitingDragStarted = false; |
| 934 |
if (rootEl && dragEl) { |
| 935 |
if (this.nativeDraggable) { |
| 936 |
_on(document, 'dragover', this._handleAutoScroll); |
| 937 |
_on(document, 'dragover', _checkAlignment); |
| 938 |
} |
| 939 |
var options = this.options; |
| 940 |
|
| 941 |
// Apply effect |
| 942 |
!fallback && _toggleClass(dragEl, options.dragClass, false); |
| 943 |
_toggleClass(dragEl, options.ghostClass, true); |
| 944 |
|
| 945 |
// In case dragging an animated element |
| 946 |
_css(dragEl, 'transform', ''); |
| 947 |
|
| 948 |
Sortable.active = this; |
| 949 |
|
| 950 |
fallback && this._appendGhost(); |
| 951 |
|
| 952 |
// Drag start event |
| 953 |
_dispatchEvent(this, rootEl, 'start', dragEl, rootEl, rootEl, oldIndex, undefined, evt); |
| 954 |
} else { |
| 955 |
this._nulling(); |
| 956 |
} |
| 957 |
}, |
| 958 |
|
| 959 |
_emulateDragOver: function (forAutoScroll) { |
| 960 |
if (touchEvt) { |
| 961 |
if (this._lastX === touchEvt.clientX && this._lastY === touchEvt.clientY && !forAutoScroll) { |
| 962 |
return; |
| 963 |
} |
| 964 |
this._lastX = touchEvt.clientX; |
| 965 |
this._lastY = touchEvt.clientY; |
| 966 |
|
| 967 |
_hideGhostForTarget(); |
| 968 |
|
| 969 |
var target = document.elementFromPoint(touchEvt.clientX, touchEvt.clientY); |
| 970 |
var parent = target; |
| 971 |
|
| 972 |
while (target && target.shadowRoot) { |
| 973 |
target = target.shadowRoot.elementFromPoint(touchEvt.clientX, touchEvt.clientY); |
| 974 |
parent = target; |
| 975 |
} |
| 976 |
|
| 977 |
if (parent) { |
| 978 |
do { |
| 979 |
if (parent[expando]) { |
| 980 |
var inserted; |
| 981 |
|
| 982 |
inserted = parent[expando]._onDragOver({ |
| 983 |
clientX: touchEvt.clientX, |
| 984 |
clientY: touchEvt.clientY, |
| 985 |
target: target, |
| 986 |
rootEl: parent |
| 987 |
}); |
| 988 |
|
| 989 |
if (inserted && !this.options.dragoverBubble) { |
| 990 |
break; |
| 991 |
} |
| 992 |
} |
| 993 |
|
| 994 |
target = parent; // store last element |
| 995 |
} |
| 996 |
/* jshint boss:true */ |
| 997 |
while (parent = parent.parentNode); |
| 998 |
} |
| 999 |
dragEl.parentNode[expando]._computeIsAligned(touchEvt); |
| 1000 |
|
| 1001 |
_unhideGhostForTarget(); |
| 1002 |
} |
| 1003 |
}, |
| 1004 |
|
| 1005 |
|
| 1006 |
_onTouchMove: function (/**TouchEvent*/evt, forAutoScroll) { |
| 1007 |
if (tapEvt) { |
| 1008 |
var options = this.options, |
| 1009 |
fallbackTolerance = options.fallbackTolerance, |
| 1010 |
fallbackOffset = options.fallbackOffset, |
| 1011 |
touch = evt.touches ? evt.touches[0] : evt, |
| 1012 |
matrix = ghostEl && _matrix(ghostEl), |
| 1013 |
scaleX = ghostEl && matrix && matrix.a, |
| 1014 |
scaleY = ghostEl && matrix && matrix.d, |
| 1015 |
relativeScrollOffset = PositionGhostAbsolutely && ghostRelativeParent && _getRelativeScrollOffset(ghostRelativeParent), |
| 1016 |
dx = ((touch.clientX - tapEvt.clientX) |
| 1017 |
+ fallbackOffset.x) / (scaleX || 1) |
| 1018 |
+ (relativeScrollOffset ? (relativeScrollOffset[0] - ghostRelativeParentInitialScroll[0]) : 0) / (scaleX || 1), |
| 1019 |
dy = ((touch.clientY - tapEvt.clientY) |
| 1020 |
+ fallbackOffset.y) / (scaleY || 1) |
| 1021 |
+ (relativeScrollOffset ? (relativeScrollOffset[1] - ghostRelativeParentInitialScroll[1]) : 0) / (scaleY || 1), |
| 1022 |
translate3d = evt.touches ? 'translate3d(' + dx + 'px,' + dy + 'px,0)' : 'translate(' + dx + 'px,' + dy + 'px)'; |
| 1023 |
|
| 1024 |
// only set the status to dragging, when we are actually dragging |
| 1025 |
if (!Sortable.active && !awaitingDragStarted) { |
| 1026 |
if (fallbackTolerance && |
| 1027 |
min(abs(touch.clientX - this._lastX), abs(touch.clientY - this._lastY)) < fallbackTolerance |
| 1028 |
) { |
| 1029 |
return; |
| 1030 |
} |
| 1031 |
this._onDragStart(evt, true); |
| 1032 |
} |
| 1033 |
|
| 1034 |
!forAutoScroll && this._handleAutoScroll(touch, true); |
| 1035 |
|
| 1036 |
moved = true; |
| 1037 |
touchEvt = touch; |
| 1038 |
|
| 1039 |
_css(ghostEl, 'webkitTransform', translate3d); |
| 1040 |
_css(ghostEl, 'mozTransform', translate3d); |
| 1041 |
_css(ghostEl, 'msTransform', translate3d); |
| 1042 |
_css(ghostEl, 'transform', translate3d); |
| 1043 |
|
| 1044 |
evt.cancelable && evt.preventDefault(); |
| 1045 |
} |
| 1046 |
}, |
| 1047 |
|
| 1048 |
_appendGhost: function () { |
| 1049 |
// Bug if using scale(): https://stackoverflow.com/questions/2637058 |
| 1050 |
// Not being adjusted for |
| 1051 |
if (!ghostEl) { |
| 1052 |
var container = this.options.fallbackOnBody ? document.body : rootEl, |
| 1053 |
rect = _getRect(dragEl, true, container, !PositionGhostAbsolutely), |
| 1054 |
css = _css(dragEl), |
| 1055 |
options = this.options; |
| 1056 |
|
| 1057 |
// Position absolutely |
| 1058 |
if (PositionGhostAbsolutely) { |
| 1059 |
// Get relatively positioned parent |
| 1060 |
ghostRelativeParent = container; |
| 1061 |
|
| 1062 |
while ( |
| 1063 |
_css(ghostRelativeParent, 'position') === 'static' && |
| 1064 |
_css(ghostRelativeParent, 'transform') === 'none' && |
| 1065 |
ghostRelativeParent !== document |
| 1066 |
) { |
| 1067 |
ghostRelativeParent = ghostRelativeParent.parentNode; |
| 1068 |
} |
| 1069 |
|
| 1070 |
if (ghostRelativeParent !== document) { |
| 1071 |
var ghostRelativeParentRect = _getRect(ghostRelativeParent, true); |
| 1072 |
|
| 1073 |
rect.top -= ghostRelativeParentRect.top; |
| 1074 |
rect.left -= ghostRelativeParentRect.left; |
| 1075 |
} |
| 1076 |
|
| 1077 |
if (ghostRelativeParent !== document.body && ghostRelativeParent !== document.documentElement) { |
| 1078 |
if (ghostRelativeParent === document) ghostRelativeParent = _getWindowScrollingElement(); |
| 1079 |
|
| 1080 |
rect.top += ghostRelativeParent.scrollTop; |
| 1081 |
rect.left += ghostRelativeParent.scrollLeft; |
| 1082 |
} else { |
| 1083 |
ghostRelativeParent = _getWindowScrollingElement(); |
| 1084 |
} |
| 1085 |
ghostRelativeParentInitialScroll = _getRelativeScrollOffset(ghostRelativeParent); |
| 1086 |
} |
| 1087 |
|
| 1088 |
|
| 1089 |
ghostEl = dragEl.cloneNode(true); |
| 1090 |
|
| 1091 |
_toggleClass(ghostEl, options.ghostClass, false); |
| 1092 |
_toggleClass(ghostEl, options.fallbackClass, true); |
| 1093 |
_toggleClass(ghostEl, options.dragClass, true); |
| 1094 |
|
| 1095 |
_css(ghostEl, 'box-sizing', 'border-box'); |
| 1096 |
_css(ghostEl, 'margin', 0); |
| 1097 |
_css(ghostEl, 'top', rect.top); |
| 1098 |
_css(ghostEl, 'left', rect.left); |
| 1099 |
_css(ghostEl, 'width', rect.width); |
| 1100 |
_css(ghostEl, 'height', rect.height); |
| 1101 |
_css(ghostEl, 'opacity', '0.8'); |
| 1102 |
_css(ghostEl, 'position', (PositionGhostAbsolutely ? 'absolute' : 'fixed')); |
| 1103 |
_css(ghostEl, 'zIndex', '100000'); |
| 1104 |
_css(ghostEl, 'pointerEvents', 'none'); |
| 1105 |
|
| 1106 |
container.appendChild(ghostEl); |
| 1107 |
} |
| 1108 |
}, |
| 1109 |
|
| 1110 |
_onDragStart: function (/**Event*/evt, /**boolean*/fallback) { |
| 1111 |
var _this = this; |
| 1112 |
var dataTransfer = evt.dataTransfer; |
| 1113 |
var options = _this.options; |
| 1114 |
|
| 1115 |
// Setup clone |
| 1116 |
cloneEl = _clone(dragEl); |
| 1117 |
|
| 1118 |
cloneEl.draggable = false; |
| 1119 |
cloneEl.style['will-change'] = ''; |
| 1120 |
|
| 1121 |
this._hideClone(); |
| 1122 |
|
| 1123 |
_toggleClass(cloneEl, _this.options.chosenClass, false); |
| 1124 |
|
| 1125 |
|
| 1126 |
// #1143: IFrame support workaround |
| 1127 |
_this._cloneId = _nextTick(function () { |
| 1128 |
if (!_this.options.removeCloneOnHide) { |
| 1129 |
rootEl.insertBefore(cloneEl, dragEl); |
| 1130 |
} |
| 1131 |
_dispatchEvent(_this, rootEl, 'clone', dragEl); |
| 1132 |
}); |
| 1133 |
|
| 1134 |
|
| 1135 |
!fallback && _toggleClass(dragEl, options.dragClass, true); |
| 1136 |
|
| 1137 |
// Set proper drop events |
| 1138 |
if (fallback) { |
| 1139 |
ignoreNextClick = true; |
| 1140 |
_this._loopId = setInterval(_this._emulateDragOver, 50); |
| 1141 |
} else { |
| 1142 |
// Undo what was set in _prepareDragStart before drag started |
| 1143 |
_off(document, 'mouseup', _this._onDrop); |
| 1144 |
_off(document, 'touchend', _this._onDrop); |
| 1145 |
_off(document, 'touchcancel', _this._onDrop); |
| 1146 |
|
| 1147 |
if (dataTransfer) { |
| 1148 |
dataTransfer.effectAllowed = 'move'; |
| 1149 |
options.setData && options.setData.call(_this, dataTransfer, dragEl); |
| 1150 |
} |
| 1151 |
|
| 1152 |
_on(document, 'drop', _this); |
| 1153 |
|
| 1154 |
// #1276 fix: |
| 1155 |
_css(dragEl, 'transform', 'translateZ(0)'); |
| 1156 |
} |
| 1157 |
|
| 1158 |
awaitingDragStarted = true; |
| 1159 |
|
| 1160 |
_this._dragStartId = _nextTick(_this._dragStarted.bind(_this, fallback, evt)); |
| 1161 |
_on(document, 'selectstart', _this); |
| 1162 |
if (Safari) { |
| 1163 |
_css(document.body, 'user-select', 'none'); |
| 1164 |
} |
| 1165 |
}, |
| 1166 |
|
| 1167 |
|
| 1168 |
// Returns true - if no further action is needed (either inserted or another condition) |
| 1169 |
_onDragOver: function (/**Event*/evt) { |
| 1170 |
var el = this.el, |
| 1171 |
target = evt.target, |
| 1172 |
dragRect, |
| 1173 |
targetRect, |
| 1174 |
revert, |
| 1175 |
options = this.options, |
| 1176 |
group = options.group, |
| 1177 |
activeSortable = Sortable.active, |
| 1178 |
isOwner = (activeGroup === group), |
| 1179 |
canSort = options.sort, |
| 1180 |
_this = this; |
| 1181 |
|
| 1182 |
if (_silent) return; |
| 1183 |
|
| 1184 |
// IE event order fix |
| 1185 |
if (IE11OrLess && !evt.rootEl && !evt.artificialBubble && !_isTrueParentSortable(el, target)) { |
| 1186 |
return; |
| 1187 |
} |
| 1188 |
|
| 1189 |
// Return invocation when dragEl is inserted (or completed) |
| 1190 |
function completed(insertion) { |
| 1191 |
if (insertion) { |
| 1192 |
if (isOwner) { |
| 1193 |
activeSortable._hideClone(); |
| 1194 |
} else { |
| 1195 |
activeSortable._showClone(_this); |
| 1196 |
} |
| 1197 |
|
| 1198 |
if (activeSortable) { |
| 1199 |
// Set ghost class to new sortable's ghost class |
| 1200 |
_toggleClass(dragEl, putSortable ? putSortable.options.ghostClass : activeSortable.options.ghostClass, false); |
| 1201 |
_toggleClass(dragEl, options.ghostClass, true); |
| 1202 |
} |
| 1203 |
|
| 1204 |
if (putSortable !== _this && _this !== Sortable.active) { |
| 1205 |
putSortable = _this; |
| 1206 |
} else if (_this === Sortable.active) { |
| 1207 |
putSortable = null; |
| 1208 |
} |
| 1209 |
|
| 1210 |
// Animation |
| 1211 |
dragRect && _this._animate(dragRect, dragEl); |
| 1212 |
target && targetRect && _this._animate(targetRect, target); |
| 1213 |
} |
| 1214 |
|
| 1215 |
|
| 1216 |
// Null lastTarget if it is not inside a previously swapped element |
| 1217 |
if ((target === dragEl && !dragEl.animated) || (target === el && !target.animated)) { |
| 1218 |
lastTarget = null; |
| 1219 |
} |
| 1220 |
// no bubbling and not fallback |
| 1221 |
if (!options.dragoverBubble && !evt.rootEl && target !== document) { |
| 1222 |
_this._handleAutoScroll(evt); |
| 1223 |
dragEl.parentNode[expando]._computeIsAligned(evt); |
| 1224 |
} |
| 1225 |
|
| 1226 |
!options.dragoverBubble && evt.stopPropagation && evt.stopPropagation(); |
| 1227 |
|
| 1228 |
return true; |
| 1229 |
} |
| 1230 |
|
| 1231 |
// Call when dragEl has been inserted |
| 1232 |
function changed() { |
| 1233 |
_dispatchEvent(_this, rootEl, 'change', target, el, rootEl, oldIndex, _index(dragEl, options.draggable), evt); |
| 1234 |
} |
| 1235 |
|
| 1236 |
|
| 1237 |
if (evt.preventDefault !== void 0) { |
| 1238 |
evt.cancelable && evt.preventDefault(); |
| 1239 |
} |
| 1240 |
|
| 1241 |
|
| 1242 |
moved = true; |
| 1243 |
|
| 1244 |
target = _closest(target, options.draggable, el, true); |
| 1245 |
|
| 1246 |
// target is dragEl or target is animated |
| 1247 |
if (!!_closest(evt.target, null, dragEl, true) || target.animated) { |
| 1248 |
return completed(false); |
| 1249 |
} |
| 1250 |
|
| 1251 |
if (target !== dragEl) { |
| 1252 |
ignoreNextClick = false; |
| 1253 |
} |
| 1254 |
|
| 1255 |
if (activeSortable && !options.disabled && |
| 1256 |
(isOwner |
| 1257 |
? canSort || (revert = !rootEl.contains(dragEl)) // Reverting item into the original list |
| 1258 |
: ( |
| 1259 |
putSortable === this || |
| 1260 |
( |
| 1261 |
(this.lastPutMode = activeGroup.checkPull(this, activeSortable, dragEl, evt)) && |
| 1262 |
group.checkPut(this, activeSortable, dragEl, evt) |
| 1263 |
) |
| 1264 |
) |
| 1265 |
) |
| 1266 |
) { |
| 1267 |
var axis = this._getDirection(evt, target); |
| 1268 |
|
| 1269 |
dragRect = _getRect(dragEl); |
| 1270 |
|
| 1271 |
if (revert) { |
| 1272 |
this._hideClone(); |
| 1273 |
parentEl = rootEl; // actualization |
| 1274 |
|
| 1275 |
if (nextEl) { |
| 1276 |
rootEl.insertBefore(dragEl, nextEl); |
| 1277 |
} else { |
| 1278 |
rootEl.appendChild(dragEl); |
| 1279 |
} |
| 1280 |
|
| 1281 |
return completed(true); |
| 1282 |
} |
| 1283 |
|
| 1284 |
var elLastChild = _lastChild(el); |
| 1285 |
|
| 1286 |
if (!elLastChild || _ghostIsLast(evt, axis, el) && !elLastChild.animated) { |
| 1287 |
// assign target only if condition is true |
| 1288 |
if (elLastChild && el === evt.target) { |
| 1289 |
target = elLastChild; |
| 1290 |
} |
| 1291 |
|
| 1292 |
if (target) { |
| 1293 |
targetRect = _getRect(target); |
| 1294 |
} |
| 1295 |
|
| 1296 |
if (isOwner) { |
| 1297 |
activeSortable._hideClone(); |
| 1298 |
} else { |
| 1299 |
activeSortable._showClone(this); |
| 1300 |
} |
| 1301 |
|
| 1302 |
if (_onMove(rootEl, el, dragEl, dragRect, target, targetRect, evt, !!target) !== false) { |
| 1303 |
el.appendChild(dragEl); |
| 1304 |
parentEl = el; // actualization |
| 1305 |
realDragElRect = null; |
| 1306 |
|
| 1307 |
changed(); |
| 1308 |
return completed(true); |
| 1309 |
} |
| 1310 |
} |
| 1311 |
else if (target && target !== dragEl && target.parentNode === el) { |
| 1312 |
var direction = 0, |
| 1313 |
targetBeforeFirstSwap, |
| 1314 |
aligned = target.sortableMouseAligned, |
| 1315 |
differentLevel = dragEl.parentNode !== el, |
| 1316 |
side1 = axis === 'vertical' ? 'top' : 'left', |
| 1317 |
scrolledPastTop = _isScrolledPast(target, 'top') || _isScrolledPast(dragEl, 'top'), |
| 1318 |
scrollBefore = scrolledPastTop ? scrolledPastTop.scrollTop : void 0; |
| 1319 |
|
| 1320 |
|
| 1321 |
if (lastTarget !== target) { |
| 1322 |
lastMode = null; |
| 1323 |
targetBeforeFirstSwap = _getRect(target)[side1]; |
| 1324 |
pastFirstInvertThresh = false; |
| 1325 |
} |
| 1326 |
|
| 1327 |
// Reference: https://www.lucidchart.com/documents/view/10fa0e93-e362-4126-aca2-b709ee56bd8b/0 |
| 1328 |
if ( |
| 1329 |
_isElInRowColumn(dragEl, target, axis) && aligned || |
| 1330 |
differentLevel || |
| 1331 |
scrolledPastTop || |
| 1332 |
options.invertSwap || |
| 1333 |
lastMode === 'insert' || |
| 1334 |
// Needed, in the case that we are inside target and inserted because not aligned... aligned will stay false while inside |
| 1335 |
// and lastMode will change to 'insert', but we must swap |
| 1336 |
lastMode === 'swap' |
| 1337 |
) { |
| 1338 |
// New target that we will be inside |
| 1339 |
if (lastMode !== 'swap') { |
| 1340 |
isCircumstantialInvert = options.invertSwap || differentLevel; |
| 1341 |
} |
| 1342 |
|
| 1343 |
direction = _getSwapDirection(evt, target, axis, |
| 1344 |
options.swapThreshold, options.invertedSwapThreshold == null ? options.swapThreshold : options.invertedSwapThreshold, |
| 1345 |
isCircumstantialInvert, |
| 1346 |
lastTarget === target); |
| 1347 |
lastMode = 'swap'; |
| 1348 |
} else { |
| 1349 |
// Insert at position |
| 1350 |
direction = _getInsertDirection(target); |
| 1351 |
lastMode = 'insert'; |
| 1352 |
} |
| 1353 |
if (direction === 0) return completed(false); |
| 1354 |
|
| 1355 |
realDragElRect = null; |
| 1356 |
lastTarget = target; |
| 1357 |
|
| 1358 |
lastDirection = direction; |
| 1359 |
|
| 1360 |
targetRect = _getRect(target); |
| 1361 |
|
| 1362 |
var nextSibling = target.nextElementSibling, |
| 1363 |
after = false; |
| 1364 |
|
| 1365 |
after = direction === 1; |
| 1366 |
|
| 1367 |
var moveVector = _onMove(rootEl, el, dragEl, dragRect, target, targetRect, evt, after); |
| 1368 |
|
| 1369 |
if (moveVector !== false) { |
| 1370 |
if (moveVector === 1 || moveVector === -1) { |
| 1371 |
after = (moveVector === 1); |
| 1372 |
} |
| 1373 |
|
| 1374 |
_silent = true; |
| 1375 |
setTimeout(_unsilent, 30); |
| 1376 |
|
| 1377 |
if (isOwner) { |
| 1378 |
activeSortable._hideClone(); |
| 1379 |
} else { |
| 1380 |
activeSortable._showClone(this); |
| 1381 |
} |
| 1382 |
|
| 1383 |
if (after && !nextSibling) { |
| 1384 |
el.appendChild(dragEl); |
| 1385 |
} else { |
| 1386 |
target.parentNode.insertBefore(dragEl, after ? nextSibling : target); |
| 1387 |
} |
| 1388 |
|
| 1389 |
// Undo chrome's scroll adjustment |
| 1390 |
if (scrolledPastTop) { |
| 1391 |
_scrollBy(scrolledPastTop, 0, scrollBefore - scrolledPastTop.scrollTop); |
| 1392 |
} |
| 1393 |
|
| 1394 |
parentEl = dragEl.parentNode; // actualization |
| 1395 |
|
| 1396 |
// must be done before animation |
| 1397 |
if (targetBeforeFirstSwap !== undefined && !isCircumstantialInvert) { |
| 1398 |
targetMoveDistance = abs(targetBeforeFirstSwap - _getRect(target)[side1]); |
| 1399 |
} |
| 1400 |
changed(); |
| 1401 |
|
| 1402 |
return completed(true); |
| 1403 |
} |
| 1404 |
} |
| 1405 |
|
| 1406 |
if (el.contains(dragEl)) { |
| 1407 |
return completed(false); |
| 1408 |
} |
| 1409 |
} |
| 1410 |
|
| 1411 |
if (IE11OrLess && !evt.rootEl) { |
| 1412 |
_artificalBubble(el, evt, '_onDragOver'); |
| 1413 |
} |
| 1414 |
|
| 1415 |
return false; |
| 1416 |
}, |
| 1417 |
|
| 1418 |
_animate: function (prevRect, target) { |
| 1419 |
var ms = this.options.animation; |
| 1420 |
|
| 1421 |
if (ms) { |
| 1422 |
var currentRect = _getRect(target); |
| 1423 |
|
| 1424 |
if (target === dragEl) { |
| 1425 |
realDragElRect = currentRect; |
| 1426 |
} |
| 1427 |
|
| 1428 |
if (prevRect.nodeType === 1) { |
| 1429 |
prevRect = _getRect(prevRect); |
| 1430 |
} |
| 1431 |
|
| 1432 |
// Check if actually moving position |
| 1433 |
if ((prevRect.left + prevRect.width / 2) !== (currentRect.left + currentRect.width / 2) |
| 1434 |
|| (prevRect.top + prevRect.height / 2) !== (currentRect.top + currentRect.height / 2) |
| 1435 |
) { |
| 1436 |
var matrix = _matrix(this.el), |
| 1437 |
scaleX = matrix && matrix.a, |
| 1438 |
scaleY = matrix && matrix.d; |
| 1439 |
|
| 1440 |
_css(target, 'transition', 'none'); |
| 1441 |
_css(target, 'transform', 'translate3d(' |
| 1442 |
+ (prevRect.left - currentRect.left) / (scaleX ? scaleX : 1) + 'px,' |
| 1443 |
+ (prevRect.top - currentRect.top) / (scaleY ? scaleY : 1) + 'px,0)' |
| 1444 |
); |
| 1445 |
|
| 1446 |
forRepaintDummy = target.offsetWidth; // repaint |
| 1447 |
_css(target, 'transition', 'transform ' + ms + 'ms' + (this.options.easing ? ' ' + this.options.easing : '')); |
| 1448 |
_css(target, 'transform', 'translate3d(0,0,0)'); |
| 1449 |
} |
| 1450 |
|
| 1451 |
(typeof target.animated === 'number') && clearTimeout(target.animated); |
| 1452 |
target.animated = setTimeout(function () { |
| 1453 |
_css(target, 'transition', ''); |
| 1454 |
_css(target, 'transform', ''); |
| 1455 |
target.animated = false; |
| 1456 |
}, ms); |
| 1457 |
} |
| 1458 |
}, |
| 1459 |
|
| 1460 |
_offUpEvents: function () { |
| 1461 |
var ownerDocument = this.el.ownerDocument; |
| 1462 |
|
| 1463 |
_off(document, 'touchmove', this._onTouchMove); |
| 1464 |
_off(document, 'pointermove', this._onTouchMove); |
| 1465 |
_off(ownerDocument, 'mouseup', this._onDrop); |
| 1466 |
_off(ownerDocument, 'touchend', this._onDrop); |
| 1467 |
_off(ownerDocument, 'pointerup', this._onDrop); |
| 1468 |
_off(ownerDocument, 'touchcancel', this._onDrop); |
| 1469 |
_off(document, 'selectstart', this); |
| 1470 |
}, |
| 1471 |
|
| 1472 |
_onDrop: function (/**Event*/evt) { |
| 1473 |
var el = this.el, |
| 1474 |
options = this.options; |
| 1475 |
awaitingDragStarted = false; |
| 1476 |
scrolling = false; |
| 1477 |
isCircumstantialInvert = false; |
| 1478 |
pastFirstInvertThresh = false; |
| 1479 |
|
| 1480 |
clearInterval(this._loopId); |
| 1481 |
|
| 1482 |
clearInterval(pointerElemChangedInterval); |
| 1483 |
_clearAutoScrolls(); |
| 1484 |
_cancelThrottle(); |
| 1485 |
|
| 1486 |
clearTimeout(this._dragStartTimer); |
| 1487 |
|
| 1488 |
_cancelNextTick(this._cloneId); |
| 1489 |
_cancelNextTick(this._dragStartId); |
| 1490 |
|
| 1491 |
// Unbind events |
| 1492 |
_off(document, 'mousemove', this._onTouchMove); |
| 1493 |
|
| 1494 |
|
| 1495 |
if (this.nativeDraggable) { |
| 1496 |
_off(document, 'drop', this); |
| 1497 |
_off(el, 'dragstart', this._onDragStart); |
| 1498 |
_off(document, 'dragover', this._handleAutoScroll); |
| 1499 |
_off(document, 'dragover', _checkAlignment); |
| 1500 |
} |
| 1501 |
|
| 1502 |
if (Safari) { |
| 1503 |
_css(document.body, 'user-select', ''); |
| 1504 |
} |
| 1505 |
|
| 1506 |
this._offUpEvents(); |
| 1507 |
|
| 1508 |
if (evt) { |
| 1509 |
if (moved) { |
| 1510 |
evt.cancelable && evt.preventDefault(); |
| 1511 |
!options.dropBubble && evt.stopPropagation(); |
| 1512 |
} |
| 1513 |
|
| 1514 |
ghostEl && ghostEl.parentNode && ghostEl.parentNode.removeChild(ghostEl); |
| 1515 |
|
| 1516 |
if (rootEl === parentEl || (putSortable && putSortable.lastPutMode !== 'clone')) { |
| 1517 |
// Remove clone |
| 1518 |
cloneEl && cloneEl.parentNode && cloneEl.parentNode.removeChild(cloneEl); |
| 1519 |
} |
| 1520 |
|
| 1521 |
if (dragEl) { |
| 1522 |
if (this.nativeDraggable) { |
| 1523 |
_off(dragEl, 'dragend', this); |
| 1524 |
} |
| 1525 |
|
| 1526 |
_disableDraggable(dragEl); |
| 1527 |
dragEl.style['will-change'] = ''; |
| 1528 |
|
| 1529 |
// Remove class's |
| 1530 |
_toggleClass(dragEl, putSortable ? putSortable.options.ghostClass : this.options.ghostClass, false); |
| 1531 |
_toggleClass(dragEl, this.options.chosenClass, false); |
| 1532 |
|
| 1533 |
// Drag stop event |
| 1534 |
_dispatchEvent(this, rootEl, 'unchoose', dragEl, parentEl, rootEl, oldIndex, null, evt); |
| 1535 |
|
| 1536 |
if (rootEl !== parentEl) { |
| 1537 |
newIndex = _index(dragEl, options.draggable); |
| 1538 |
|
| 1539 |
if (newIndex >= 0) { |
| 1540 |
// Add event |
| 1541 |
_dispatchEvent(null, parentEl, 'add', dragEl, parentEl, rootEl, oldIndex, newIndex, evt); |
| 1542 |
|
| 1543 |
// Remove event |
| 1544 |
_dispatchEvent(this, rootEl, 'remove', dragEl, parentEl, rootEl, oldIndex, newIndex, evt); |
| 1545 |
|
| 1546 |
// drag from one list and drop into another |
| 1547 |
_dispatchEvent(null, parentEl, 'sort', dragEl, parentEl, rootEl, oldIndex, newIndex, evt); |
| 1548 |
_dispatchEvent(this, rootEl, 'sort', dragEl, parentEl, rootEl, oldIndex, newIndex, evt); |
| 1549 |
} |
| 1550 |
|
| 1551 |
putSortable && putSortable.save(); |
| 1552 |
} |
| 1553 |
else { |
| 1554 |
if (dragEl.nextSibling !== nextEl) { |
| 1555 |
// Get the index of the dragged element within its parent |
| 1556 |
newIndex = _index(dragEl, options.draggable); |
| 1557 |
|
| 1558 |
if (newIndex >= 0) { |
| 1559 |
// drag & drop within the same list |
| 1560 |
_dispatchEvent(this, rootEl, 'update', dragEl, parentEl, rootEl, oldIndex, newIndex, evt); |
| 1561 |
_dispatchEvent(this, rootEl, 'sort', dragEl, parentEl, rootEl, oldIndex, newIndex, evt); |
| 1562 |
} |
| 1563 |
} |
| 1564 |
} |
| 1565 |
|
| 1566 |
if (Sortable.active) { |
| 1567 |
/* jshint eqnull:true */ |
| 1568 |
if (newIndex == null || newIndex === -1) { |
| 1569 |
newIndex = oldIndex; |
| 1570 |
} |
| 1571 |
_dispatchEvent(this, rootEl, 'end', dragEl, parentEl, rootEl, oldIndex, newIndex, evt); |
| 1572 |
|
| 1573 |
// Save sorting |
| 1574 |
this.save(); |
| 1575 |
} |
| 1576 |
} |
| 1577 |
|
| 1578 |
} |
| 1579 |
this._nulling(); |
| 1580 |
}, |
| 1581 |
|
| 1582 |
_nulling: function() { |
| 1583 |
rootEl = |
| 1584 |
dragEl = |
| 1585 |
parentEl = |
| 1586 |
ghostEl = |
| 1587 |
nextEl = |
| 1588 |
cloneEl = |
| 1589 |
lastDownEl = |
| 1590 |
|
| 1591 |
scrollEl = |
| 1592 |
scrollParentEl = |
| 1593 |
autoScrolls.length = |
| 1594 |
|
| 1595 |
pointerElemChangedInterval = |
| 1596 |
lastPointerElemX = |
| 1597 |
lastPointerElemY = |
| 1598 |
|
| 1599 |
tapEvt = |
| 1600 |
touchEvt = |
| 1601 |
|
| 1602 |
moved = |
| 1603 |
newIndex = |
| 1604 |
oldIndex = |
| 1605 |
|
| 1606 |
lastTarget = |
| 1607 |
lastDirection = |
| 1608 |
|
| 1609 |
forRepaintDummy = |
| 1610 |
realDragElRect = |
| 1611 |
|
| 1612 |
putSortable = |
| 1613 |
activeGroup = |
| 1614 |
Sortable.active = null; |
| 1615 |
|
| 1616 |
savedInputChecked.forEach(function (el) { |
| 1617 |
el.checked = true; |
| 1618 |
}); |
| 1619 |
|
| 1620 |
savedInputChecked.length = 0; |
| 1621 |
}, |
| 1622 |
|
| 1623 |
handleEvent: function (/**Event*/evt) { |
| 1624 |
switch (evt.type) { |
| 1625 |
case 'drop': |
| 1626 |
case 'dragend': |
| 1627 |
this._onDrop(evt); |
| 1628 |
break; |
| 1629 |
|
| 1630 |
case 'dragenter': |
| 1631 |
case 'dragover': |
| 1632 |
if (dragEl) { |
| 1633 |
this._onDragOver(evt); |
| 1634 |
_globalDragOver(evt); |
| 1635 |
} |
| 1636 |
break; |
| 1637 |
|
| 1638 |
case 'selectstart': |
| 1639 |
evt.preventDefault(); |
| 1640 |
break; |
| 1641 |
} |
| 1642 |
}, |
| 1643 |
|
| 1644 |
|
| 1645 |
/** |
| 1646 |
* Serializes the item into an array of string. |
| 1647 |
* @returns {String[]} |
| 1648 |
*/ |
| 1649 |
toArray: function () { |
| 1650 |
var order = [], |
| 1651 |
el, |
| 1652 |
children = this.el.children, |
| 1653 |
i = 0, |
| 1654 |
n = children.length, |
| 1655 |
options = this.options; |
| 1656 |
|
| 1657 |
for (; i < n; i++) { |
| 1658 |
el = children[i]; |
| 1659 |
if (_closest(el, options.draggable, this.el, false)) { |
| 1660 |
order.push(el.getAttribute(options.dataIdAttr) || _generateId(el)); |
| 1661 |
} |
| 1662 |
} |
| 1663 |
|
| 1664 |
return order; |
| 1665 |
}, |
| 1666 |
|
| 1667 |
|
| 1668 |
/** |
| 1669 |
* Sorts the elements according to the array. |
| 1670 |
* @param {String[]} order order of the items |
| 1671 |
*/ |
| 1672 |
sort: function (order) { |
| 1673 |
var items = {}, rootEl = this.el; |
| 1674 |
|
| 1675 |
this.toArray().forEach(function (id, i) { |
| 1676 |
var el = rootEl.children[i]; |
| 1677 |
|
| 1678 |
if (_closest(el, this.options.draggable, rootEl, false)) { |
| 1679 |
items[id] = el; |
| 1680 |
} |
| 1681 |
}, this); |
| 1682 |
|
| 1683 |
order.forEach(function (id) { |
| 1684 |
if (items[id]) { |
| 1685 |
rootEl.removeChild(items[id]); |
| 1686 |
rootEl.appendChild(items[id]); |
| 1687 |
} |
| 1688 |
}); |
| 1689 |
}, |
| 1690 |
|
| 1691 |
|
| 1692 |
/** |
| 1693 |
* Save the current sorting |
| 1694 |
*/ |
| 1695 |
save: function () { |
| 1696 |
var store = this.options.store; |
| 1697 |
store && store.set && store.set(this); |
| 1698 |
}, |
| 1699 |
|
| 1700 |
|
| 1701 |
/** |
| 1702 |
* 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. |
| 1703 |
* @param {HTMLElement} el |
| 1704 |
* @param {String} [selector] default: `options.draggable` |
| 1705 |
* @returns {HTMLElement|null} |
| 1706 |
*/ |
| 1707 |
closest: function (el, selector) { |
| 1708 |
return _closest(el, selector || this.options.draggable, this.el, false); |
| 1709 |
}, |
| 1710 |
|
| 1711 |
|
| 1712 |
/** |
| 1713 |
* Set/get option |
| 1714 |
* @param {string} name |
| 1715 |
* @param {*} [value] |
| 1716 |
* @returns {*} |
| 1717 |
*/ |
| 1718 |
option: function (name, value) { |
| 1719 |
var options = this.options; |
| 1720 |
|
| 1721 |
if (value === void 0) { |
| 1722 |
return options[name]; |
| 1723 |
} else { |
| 1724 |
options[name] = value; |
| 1725 |
|
| 1726 |
if (name === 'group') { |
| 1727 |
_prepareGroup(options); |
| 1728 |
} |
| 1729 |
} |
| 1730 |
}, |
| 1731 |
|
| 1732 |
|
| 1733 |
/** |
| 1734 |
* Destroy |
| 1735 |
*/ |
| 1736 |
destroy: function () { |
| 1737 |
var el = this.el; |
| 1738 |
|
| 1739 |
el[expando] = null; |
| 1740 |
|
| 1741 |
_off(el, 'mousedown', this._onTapStart); |
| 1742 |
_off(el, 'touchstart', this._onTapStart); |
| 1743 |
_off(el, 'pointerdown', this._onTapStart); |
| 1744 |
|
| 1745 |
if (this.nativeDraggable) { |
| 1746 |
_off(el, 'dragover', this); |
| 1747 |
_off(el, 'dragenter', this); |
| 1748 |
} |
| 1749 |
// Remove draggable attributes |
| 1750 |
Array.prototype.forEach.call(el.querySelectorAll('[draggable]'), function (el) { |
| 1751 |
el.removeAttribute('draggable'); |
| 1752 |
}); |
| 1753 |
|
| 1754 |
this._onDrop(); |
| 1755 |
|
| 1756 |
sortables.splice(sortables.indexOf(this.el), 1); |
| 1757 |
|
| 1758 |
this.el = el = null; |
| 1759 |
}, |
| 1760 |
|
| 1761 |
_hideClone: function() { |
| 1762 |
if (!cloneEl.cloneHidden) { |
| 1763 |
_css(cloneEl, 'display', 'none'); |
| 1764 |
cloneEl.cloneHidden = true; |
| 1765 |
if (cloneEl.parentNode && this.options.removeCloneOnHide) { |
| 1766 |
cloneEl.parentNode.removeChild(cloneEl); |
| 1767 |
} |
| 1768 |
} |
| 1769 |
}, |
| 1770 |
|
| 1771 |
_showClone: function(putSortable) { |
| 1772 |
if (putSortable.lastPutMode !== 'clone') { |
| 1773 |
this._hideClone(); |
| 1774 |
return; |
| 1775 |
} |
| 1776 |
|
| 1777 |
if (cloneEl.cloneHidden) { |
| 1778 |
// show clone at dragEl or original position |
| 1779 |
if (rootEl.contains(dragEl) && !this.options.group.revertClone) { |
| 1780 |
rootEl.insertBefore(cloneEl, dragEl); |
| 1781 |
} else if (nextEl) { |
| 1782 |
rootEl.insertBefore(cloneEl, nextEl); |
| 1783 |
} else { |
| 1784 |
rootEl.appendChild(cloneEl); |
| 1785 |
} |
| 1786 |
|
| 1787 |
if (this.options.group.revertClone) { |
| 1788 |
this._animate(dragEl, cloneEl); |
| 1789 |
} |
| 1790 |
_css(cloneEl, 'display', ''); |
| 1791 |
cloneEl.cloneHidden = false; |
| 1792 |
} |
| 1793 |
} |
| 1794 |
}; |
| 1795 |
|
| 1796 |
function _closest(/**HTMLElement*/el, /**String*/selector, /**HTMLElement*/ctx, includeCTX) { |
| 1797 |
if (el) { |
| 1798 |
ctx = ctx || document; |
| 1799 |
|
| 1800 |
do { |
| 1801 |
if ( |
| 1802 |
selector != null && |
| 1803 |
( |
| 1804 |
selector[0] === '>' && el.parentNode === ctx && _matches(el, selector.substring(1)) || |
| 1805 |
_matches(el, selector) |
| 1806 |
) || |
| 1807 |
includeCTX && el === ctx |
| 1808 |
) { |
| 1809 |
return el; |
| 1810 |
} |
| 1811 |
|
| 1812 |
if (el === ctx) break; |
| 1813 |
/* jshint boss:true */ |
| 1814 |
} while (el = _getParentOrHost(el)); |
| 1815 |
} |
| 1816 |
|
| 1817 |
return null; |
| 1818 |
} |
| 1819 |
|
| 1820 |
|
| 1821 |
function _getParentOrHost(el) { |
| 1822 |
return (el.host && el !== document && el.host.nodeType) |
| 1823 |
? el.host |
| 1824 |
: el.parentNode; |
| 1825 |
} |
| 1826 |
|
| 1827 |
|
| 1828 |
function _globalDragOver(/**Event*/evt) { |
| 1829 |
if (evt.dataTransfer) { |
| 1830 |
evt.dataTransfer.dropEffect = 'move'; |
| 1831 |
} |
| 1832 |
evt.cancelable && evt.preventDefault(); |
| 1833 |
} |
| 1834 |
|
| 1835 |
|
| 1836 |
function _on(el, event, fn) { |
| 1837 |
el.addEventListener(event, fn, captureMode); |
| 1838 |
} |
| 1839 |
|
| 1840 |
|
| 1841 |
function _off(el, event, fn) { |
| 1842 |
el.removeEventListener(event, fn, captureMode); |
| 1843 |
} |
| 1844 |
|
| 1845 |
|
| 1846 |
function _toggleClass(el, name, state) { |
| 1847 |
if (el && name) { |
| 1848 |
if (el.classList) { |
| 1849 |
el.classList[state ? 'add' : 'remove'](name); |
| 1850 |
} |
| 1851 |
else { |
| 1852 |
var className = (' ' + el.className + ' ').replace(R_SPACE, ' ').replace(' ' + name + ' ', ' '); |
| 1853 |
el.className = (className + (state ? ' ' + name : '')).replace(R_SPACE, ' '); |
| 1854 |
} |
| 1855 |
} |
| 1856 |
} |
| 1857 |
|
| 1858 |
|
| 1859 |
function _css(el, prop, val) { |
| 1860 |
var style = el && el.style; |
| 1861 |
|
| 1862 |
if (style) { |
| 1863 |
if (val === void 0) { |
| 1864 |
if (document.defaultView && document.defaultView.getComputedStyle) { |
| 1865 |
val = document.defaultView.getComputedStyle(el, ''); |
| 1866 |
} |
| 1867 |
else if (el.currentStyle) { |
| 1868 |
val = el.currentStyle; |
| 1869 |
} |
| 1870 |
|
| 1871 |
return prop === void 0 ? val : val[prop]; |
| 1872 |
} |
| 1873 |
else { |
| 1874 |
if (!(prop in style) && prop.indexOf('webkit') === -1) { |
| 1875 |
prop = '-webkit-' + prop; |
| 1876 |
} |
| 1877 |
|
| 1878 |
style[prop] = val + (typeof val === 'string' ? '' : 'px'); |
| 1879 |
} |
| 1880 |
} |
| 1881 |
} |
| 1882 |
|
| 1883 |
function _matrix(el) { |
| 1884 |
var appliedTransforms = ''; |
| 1885 |
do { |
| 1886 |
var transform = _css(el, 'transform'); |
| 1887 |
|
| 1888 |
if (transform && transform !== 'none') { |
| 1889 |
appliedTransforms = transform + ' ' + appliedTransforms; |
| 1890 |
} |
| 1891 |
/* jshint boss:true */ |
| 1892 |
} while (el = el.parentNode); |
| 1893 |
|
| 1894 |
if (window.DOMMatrix) { |
| 1895 |
return new DOMMatrix(appliedTransforms); |
| 1896 |
} else if (window.WebKitCSSMatrix) { |
| 1897 |
return new WebKitCSSMatrix(appliedTransforms); |
| 1898 |
} else if (window.CSSMatrix) { |
| 1899 |
return new CSSMatrix(appliedTransforms); |
| 1900 |
} |
| 1901 |
} |
| 1902 |
|
| 1903 |
|
| 1904 |
function _find(ctx, tagName, iterator) { |
| 1905 |
if (ctx) { |
| 1906 |
var list = ctx.getElementsByTagName(tagName), i = 0, n = list.length; |
| 1907 |
|
| 1908 |
if (iterator) { |
| 1909 |
for (; i < n; i++) { |
| 1910 |
iterator(list[i], i); |
| 1911 |
} |
| 1912 |
} |
| 1913 |
|
| 1914 |
return list; |
| 1915 |
} |
| 1916 |
|
| 1917 |
return []; |
| 1918 |
} |
| 1919 |
|
| 1920 |
|
| 1921 |
|
| 1922 |
function _dispatchEvent(sortable, rootEl, name, targetEl, toEl, fromEl, startIndex, newIndex, originalEvt) { |
| 1923 |
sortable = (sortable || rootEl[expando]); |
| 1924 |
var evt, |
| 1925 |
options = sortable.options, |
| 1926 |
onName = 'on' + name.charAt(0).toUpperCase() + name.substr(1); |
| 1927 |
// Support for new CustomEvent feature |
| 1928 |
if (window.CustomEvent && !IE11OrLess && !Edge) { |
| 1929 |
evt = new CustomEvent(name, { |
| 1930 |
bubbles: true, |
| 1931 |
cancelable: true |
| 1932 |
}); |
| 1933 |
} else { |
| 1934 |
evt = document.createEvent('Event'); |
| 1935 |
evt.initEvent(name, true, true); |
| 1936 |
} |
| 1937 |
|
| 1938 |
evt.to = toEl || rootEl; |
| 1939 |
evt.from = fromEl || rootEl; |
| 1940 |
evt.item = targetEl || rootEl; |
| 1941 |
evt.clone = cloneEl; |
| 1942 |
|
| 1943 |
evt.oldIndex = startIndex; |
| 1944 |
evt.newIndex = newIndex; |
| 1945 |
|
| 1946 |
evt.originalEvent = originalEvt; |
| 1947 |
evt.pullMode = putSortable ? putSortable.lastPutMode : undefined; |
| 1948 |
|
| 1949 |
if (rootEl) { |
| 1950 |
rootEl.dispatchEvent(evt); |
| 1951 |
} |
| 1952 |
|
| 1953 |
if (options[onName]) { |
| 1954 |
options[onName].call(sortable, evt); |
| 1955 |
} |
| 1956 |
} |
| 1957 |
|
| 1958 |
|
| 1959 |
function _onMove(fromEl, toEl, dragEl, dragRect, targetEl, targetRect, originalEvt, willInsertAfter) { |
| 1960 |
var evt, |
| 1961 |
sortable = fromEl[expando], |
| 1962 |
onMoveFn = sortable.options.onMove, |
| 1963 |
retVal; |
| 1964 |
// Support for new CustomEvent feature |
| 1965 |
if (window.CustomEvent && !IE11OrLess && !Edge) { |
| 1966 |
evt = new CustomEvent('move', { |
| 1967 |
bubbles: true, |
| 1968 |
cancelable: true |
| 1969 |
}); |
| 1970 |
} else { |
| 1971 |
evt = document.createEvent('Event'); |
| 1972 |
evt.initEvent('move', true, true); |
| 1973 |
} |
| 1974 |
|
| 1975 |
evt.to = toEl; |
| 1976 |
evt.from = fromEl; |
| 1977 |
evt.dragged = dragEl; |
| 1978 |
evt.draggedRect = dragRect; |
| 1979 |
evt.related = targetEl || toEl; |
| 1980 |
evt.relatedRect = targetRect || _getRect(toEl); |
| 1981 |
evt.willInsertAfter = willInsertAfter; |
| 1982 |
|
| 1983 |
evt.originalEvent = originalEvt; |
| 1984 |
|
| 1985 |
fromEl.dispatchEvent(evt); |
| 1986 |
|
| 1987 |
if (onMoveFn) { |
| 1988 |
retVal = onMoveFn.call(sortable, evt, originalEvt); |
| 1989 |
} |
| 1990 |
|
| 1991 |
return retVal; |
| 1992 |
} |
| 1993 |
|
| 1994 |
function _disableDraggable(el) { |
| 1995 |
el.draggable = false; |
| 1996 |
} |
| 1997 |
|
| 1998 |
function _unsilent() { |
| 1999 |
_silent = false; |
| 2000 |
} |
| 2001 |
|
| 2002 |
/** |
| 2003 |
* Gets nth child of el, ignoring hidden children, sortable's elements (does not ignore clone if it's visible) |
| 2004 |
* and non-draggable elements |
| 2005 |
* @param {HTMLElement} el The parent element |
| 2006 |
* @param {Number} childNum The index of the child |
| 2007 |
* @param {Object} options Parent Sortable's options |
| 2008 |
* @return {HTMLElement} The child at index childNum, or null if not found |
| 2009 |
*/ |
| 2010 |
function _getChild(el, childNum, options) { |
| 2011 |
var currentChild = 0, |
| 2012 |
i = 0, |
| 2013 |
children = el.children; |
| 2014 |
|
| 2015 |
while (i < children.length) { |
| 2016 |
if ( |
| 2017 |
children[i].style.display !== 'none' && |
| 2018 |
children[i] !== ghostEl && |
| 2019 |
children[i] !== dragEl && |
| 2020 |
_closest(children[i], options.draggable, el, false) |
| 2021 |
) { |
| 2022 |
if (currentChild === childNum) { |
| 2023 |
return children[i]; |
| 2024 |
} |
| 2025 |
currentChild++; |
| 2026 |
} |
| 2027 |
|
| 2028 |
i++; |
| 2029 |
} |
| 2030 |
return null; |
| 2031 |
} |
| 2032 |
|
| 2033 |
/** |
| 2034 |
* Gets the last child in the el, ignoring ghostEl or invisible elements (clones) |
| 2035 |
* @param {HTMLElement} el Parent element |
| 2036 |
* @return {HTMLElement} The last child, ignoring ghostEl |
| 2037 |
*/ |
| 2038 |
function _lastChild(el) { |
| 2039 |
var last = el.lastElementChild; |
| 2040 |
|
| 2041 |
while (last && (last === ghostEl || last.style.display === 'none')) { |
| 2042 |
last = last.previousElementSibling; |
| 2043 |
} |
| 2044 |
|
| 2045 |
return last || null; |
| 2046 |
} |
| 2047 |
|
| 2048 |
function _ghostIsLast(evt, axis, el) { |
| 2049 |
var elRect = _getRect(_lastChild(el)), |
| 2050 |
mouseOnAxis = axis === 'vertical' ? evt.clientY : evt.clientX, |
| 2051 |
mouseOnOppAxis = axis === 'vertical' ? evt.clientX : evt.clientY, |
| 2052 |
targetS2 = axis === 'vertical' ? elRect.bottom : elRect.right, |
| 2053 |
targetS1Opp = axis === 'vertical' ? elRect.left : elRect.top, |
| 2054 |
targetS2Opp = axis === 'vertical' ? elRect.right : elRect.bottom, |
| 2055 |
spacer = 10; |
| 2056 |
|
| 2057 |
return ( |
| 2058 |
axis === 'vertical' ? |
| 2059 |
(mouseOnOppAxis > targetS2Opp + spacer || mouseOnOppAxis <= targetS2Opp && mouseOnAxis > targetS2 && mouseOnOppAxis >= targetS1Opp) : |
| 2060 |
(mouseOnAxis > targetS2 && mouseOnOppAxis > targetS1Opp || mouseOnAxis <= targetS2 && mouseOnOppAxis > targetS2Opp + spacer) |
| 2061 |
); |
| 2062 |
} |
| 2063 |
|
| 2064 |
function _getSwapDirection(evt, target, axis, swapThreshold, invertedSwapThreshold, invertSwap, isLastTarget) { |
| 2065 |
var targetRect = _getRect(target), |
| 2066 |
mouseOnAxis = axis === 'vertical' ? evt.clientY : evt.clientX, |
| 2067 |
targetLength = axis === 'vertical' ? targetRect.height : targetRect.width, |
| 2068 |
targetS1 = axis === 'vertical' ? targetRect.top : targetRect.left, |
| 2069 |
targetS2 = axis === 'vertical' ? targetRect.bottom : targetRect.right, |
| 2070 |
dragRect = _getRect(dragEl), |
| 2071 |
invert = false; |
| 2072 |
|
| 2073 |
|
| 2074 |
if (!invertSwap) { |
| 2075 |
// Never invert or create dragEl shadow when target movemenet causes mouse to move past the end of regular swapThreshold |
| 2076 |
if (isLastTarget && targetMoveDistance < targetLength * swapThreshold) { // multiplied only by swapThreshold because mouse will already be inside target by (1 - threshold) * targetLength / 2 |
| 2077 |
// check if past first invert threshold on side opposite of lastDirection |
| 2078 |
if (!pastFirstInvertThresh && |
| 2079 |
(lastDirection === 1 ? |
| 2080 |
( |
| 2081 |
mouseOnAxis > targetS1 + targetLength * invertedSwapThreshold / 2 |
| 2082 |
) : |
| 2083 |
( |
| 2084 |
mouseOnAxis < targetS2 - targetLength * invertedSwapThreshold / 2 |
| 2085 |
) |
| 2086 |
) |
| 2087 |
) |
| 2088 |
{ |
| 2089 |
// past first invert threshold, do not restrict inverted threshold to dragEl shadow |
| 2090 |
pastFirstInvertThresh = true; |
| 2091 |
} |
| 2092 |
|
| 2093 |
if (!pastFirstInvertThresh) { |
| 2094 |
var dragS1 = axis === 'vertical' ? dragRect.top : dragRect.left, |
| 2095 |
dragS2 = axis === 'vertical' ? dragRect.bottom : dragRect.right; |
| 2096 |
// dragEl shadow (target move distance shadow) |
| 2097 |
if ( |
| 2098 |
lastDirection === 1 ? |
| 2099 |
( |
| 2100 |
mouseOnAxis < targetS1 + targetMoveDistance // over dragEl shadow |
| 2101 |
) : |
| 2102 |
( |
| 2103 |
mouseOnAxis > targetS2 - targetMoveDistance |
| 2104 |
) |
| 2105 |
) |
| 2106 |
{ |
| 2107 |
return lastDirection * -1; |
| 2108 |
} |
| 2109 |
} else { |
| 2110 |
invert = true; |
| 2111 |
} |
| 2112 |
} else { |
| 2113 |
// Regular |
| 2114 |
if ( |
| 2115 |
mouseOnAxis > targetS1 + (targetLength * (1 - swapThreshold) / 2) && |
| 2116 |
mouseOnAxis < targetS2 - (targetLength * (1 - swapThreshold) / 2) |
| 2117 |
) { |
| 2118 |
return _getInsertDirection(target); |
| 2119 |
} |
| 2120 |
} |
| 2121 |
} |
| 2122 |
|
| 2123 |
invert = invert || invertSwap; |
| 2124 |
|
| 2125 |
if (invert) { |
| 2126 |
// Invert of regular |
| 2127 |
if ( |
| 2128 |
mouseOnAxis < targetS1 + (targetLength * invertedSwapThreshold / 2) || |
| 2129 |
mouseOnAxis > targetS2 - (targetLength * invertedSwapThreshold / 2) |
| 2130 |
) |
| 2131 |
{ |
| 2132 |
return ((mouseOnAxis > targetS1 + targetLength / 2) ? 1 : -1); |
| 2133 |
} |
| 2134 |
} |
| 2135 |
|
| 2136 |
return 0; |
| 2137 |
} |
| 2138 |
|
| 2139 |
/** |
| 2140 |
* Gets the direction dragEl must be swapped relative to target in order to make it |
| 2141 |
* seem that dragEl has been "inserted" into that element's position |
| 2142 |
* @param {HTMLElement} target The target whose position dragEl is being inserted at |
| 2143 |
* @return {Number} Direction dragEl must be swapped |
| 2144 |
*/ |
| 2145 |
function _getInsertDirection(target) { |
| 2146 |
var dragElIndex = _index(dragEl), |
| 2147 |
targetIndex = _index(target); |
| 2148 |
|
| 2149 |
if (dragElIndex < targetIndex) { |
| 2150 |
return 1; |
| 2151 |
} else { |
| 2152 |
return -1; |
| 2153 |
} |
| 2154 |
} |
| 2155 |
|
| 2156 |
|
| 2157 |
/** |
| 2158 |
* Generate id |
| 2159 |
* @param {HTMLElement} el |
| 2160 |
* @returns {String} |
| 2161 |
* @private |
| 2162 |
*/ |
| 2163 |
function _generateId(el) { |
| 2164 |
var str = el.tagName + el.className + el.src + el.href + el.textContent, |
| 2165 |
i = str.length, |
| 2166 |
sum = 0; |
| 2167 |
|
| 2168 |
while (i--) { |
| 2169 |
sum += str.charCodeAt(i); |
| 2170 |
} |
| 2171 |
|
| 2172 |
return sum.toString(36); |
| 2173 |
} |
| 2174 |
|
| 2175 |
/** |
| 2176 |
* Returns the index of an element within its parent for a selected set of |
| 2177 |
* elements |
| 2178 |
* @param {HTMLElement} el |
| 2179 |
* @param {selector} selector |
| 2180 |
* @return {number} |
| 2181 |
*/ |
| 2182 |
function _index(el, selector) { |
| 2183 |
var index = 0; |
| 2184 |
|
| 2185 |
if (!el || !el.parentNode) { |
| 2186 |
return -1; |
| 2187 |
} |
| 2188 |
|
| 2189 |
while (el && (el = el.previousElementSibling)) { |
| 2190 |
if ((el.nodeName.toUpperCase() !== 'TEMPLATE') && el !== cloneEl) { |
| 2191 |
index++; |
| 2192 |
} |
| 2193 |
} |
| 2194 |
|
| 2195 |
return index; |
| 2196 |
} |
| 2197 |
|
| 2198 |
function _matches(/**HTMLElement*/el, /**String*/selector) { |
| 2199 |
if (el) { |
| 2200 |
try { |
| 2201 |
if (el.matches) { |
| 2202 |
return el.matches(selector); |
| 2203 |
} else if (el.msMatchesSelector) { |
| 2204 |
return el.msMatchesSelector(selector); |
| 2205 |
} else if (el.webkitMatchesSelector) { |
| 2206 |
return el.webkitMatchesSelector(selector); |
| 2207 |
} |
| 2208 |
} catch(_) { |
| 2209 |
return false; |
| 2210 |
} |
| 2211 |
} |
| 2212 |
|
| 2213 |
return false; |
| 2214 |
} |
| 2215 |
|
| 2216 |
var _throttleTimeout; |
| 2217 |
function _throttle(callback, ms) { |
| 2218 |
return function () { |
| 2219 |
if (!_throttleTimeout) { |
| 2220 |
var args = arguments, |
| 2221 |
_this = this; |
| 2222 |
|
| 2223 |
_throttleTimeout = setTimeout(function () { |
| 2224 |
if (args.length === 1) { |
| 2225 |
callback.call(_this, args[0]); |
| 2226 |
} else { |
| 2227 |
callback.apply(_this, args); |
| 2228 |
} |
| 2229 |
|
| 2230 |
_throttleTimeout = void 0; |
| 2231 |
}, ms); |
| 2232 |
} |
| 2233 |
}; |
| 2234 |
} |
| 2235 |
|
| 2236 |
function _cancelThrottle() { |
| 2237 |
clearTimeout(_throttleTimeout); |
| 2238 |
_throttleTimeout = void 0; |
| 2239 |
} |
| 2240 |
|
| 2241 |
function _extend(dst, src) { |
| 2242 |
if (dst && src) { |
| 2243 |
for (var key in src) { |
| 2244 |
if (src.hasOwnProperty(key)) { |
| 2245 |
dst[key] = src[key]; |
| 2246 |
} |
| 2247 |
} |
| 2248 |
} |
| 2249 |
|
| 2250 |
return dst; |
| 2251 |
} |
| 2252 |
|
| 2253 |
function _clone(el) { |
| 2254 |
if (Polymer && Polymer.dom) { |
| 2255 |
return Polymer.dom(el).cloneNode(true); |
| 2256 |
} |
| 2257 |
else if ($) { |
| 2258 |
return $(el).clone(true)[0]; |
| 2259 |
} |
| 2260 |
else { |
| 2261 |
return el.cloneNode(true); |
| 2262 |
} |
| 2263 |
} |
| 2264 |
|
| 2265 |
function _saveInputCheckedState(root) { |
| 2266 |
savedInputChecked.length = 0; |
| 2267 |
|
| 2268 |
var inputs = root.getElementsByTagName('input'); |
| 2269 |
var idx = inputs.length; |
| 2270 |
|
| 2271 |
while (idx--) { |
| 2272 |
var el = inputs[idx]; |
| 2273 |
el.checked && savedInputChecked.push(el); |
| 2274 |
} |
| 2275 |
} |
| 2276 |
|
| 2277 |
function _nextTick(fn) { |
| 2278 |
return setTimeout(fn, 0); |
| 2279 |
} |
| 2280 |
|
| 2281 |
function _cancelNextTick(id) { |
| 2282 |
return clearTimeout(id); |
| 2283 |
} |
| 2284 |
|
| 2285 |
|
| 2286 |
/** |
| 2287 |
* Returns the "bounding client rect" of given element |
| 2288 |
* @param {HTMLElement} el The element whose boundingClientRect is wanted |
| 2289 |
* @param {[HTMLElement]} container the parent the element will be placed in |
| 2290 |
* @param {[Boolean]} adjustForTransform Whether the rect should compensate for parent's transform |
| 2291 |
* @return {Object} The boundingClientRect of el |
| 2292 |
*/ |
| 2293 |
function _getRect(el, adjustForTransform, container, adjustForFixed) { |
| 2294 |
if (!el.getBoundingClientRect && el !== win) return; |
| 2295 |
|
| 2296 |
var elRect, |
| 2297 |
top, |
| 2298 |
left, |
| 2299 |
bottom, |
| 2300 |
right, |
| 2301 |
height, |
| 2302 |
width; |
| 2303 |
|
| 2304 |
if (el !== win && el !== _getWindowScrollingElement()) { |
| 2305 |
elRect = el.getBoundingClientRect(); |
| 2306 |
top = elRect.top; |
| 2307 |
left = elRect.left; |
| 2308 |
bottom = elRect.bottom; |
| 2309 |
right = elRect.right; |
| 2310 |
height = elRect.height; |
| 2311 |
width = elRect.width; |
| 2312 |
} else { |
| 2313 |
top = 0; |
| 2314 |
left = 0; |
| 2315 |
bottom = window.innerHeight; |
| 2316 |
right = window.innerWidth; |
| 2317 |
height = window.innerHeight; |
| 2318 |
width = window.innerWidth; |
| 2319 |
} |
| 2320 |
|
| 2321 |
if (adjustForFixed && el !== win) { |
| 2322 |
// Adjust for translate() |
| 2323 |
container = container || el.parentNode; |
| 2324 |
|
| 2325 |
// solves #1123 (see: https://stackoverflow.com/a/37953806/6088312) |
| 2326 |
// Not needed on <= IE11 |
| 2327 |
if (!IE11OrLess) { |
| 2328 |
do { |
| 2329 |
if (container && container.getBoundingClientRect && _css(container, 'transform') !== 'none') { |
| 2330 |
var containerRect = container.getBoundingClientRect(); |
| 2331 |
|
| 2332 |
// Set relative to edges of padding box of container |
| 2333 |
top -= containerRect.top + parseInt(_css(container, 'border-top-width')); |
| 2334 |
left -= containerRect.left + parseInt(_css(container, 'border-left-width')); |
| 2335 |
bottom = top + elRect.height; |
| 2336 |
right = left + elRect.width; |
| 2337 |
|
| 2338 |
break; |
| 2339 |
} |
| 2340 |
/* jshint boss:true */ |
| 2341 |
} while (container = container.parentNode); |
| 2342 |
} |
| 2343 |
} |
| 2344 |
|
| 2345 |
if (adjustForTransform && el !== win) { |
| 2346 |
// Adjust for scale() |
| 2347 |
var matrix = _matrix(container || el), |
| 2348 |
scaleX = matrix && matrix.a, |
| 2349 |
scaleY = matrix && matrix.d; |
| 2350 |
|
| 2351 |
if (matrix) { |
| 2352 |
top /= scaleY; |
| 2353 |
left /= scaleX; |
| 2354 |
|
| 2355 |
width /= scaleX; |
| 2356 |
height /= scaleY; |
| 2357 |
|
| 2358 |
bottom = top + height; |
| 2359 |
right = left + width; |
| 2360 |
} |
| 2361 |
} |
| 2362 |
|
| 2363 |
return { |
| 2364 |
top: top, |
| 2365 |
left: left, |
| 2366 |
bottom: bottom, |
| 2367 |
right: right, |
| 2368 |
width: width, |
| 2369 |
height: height |
| 2370 |
}; |
| 2371 |
} |
| 2372 |
|
| 2373 |
|
| 2374 |
/** |
| 2375 |
* Checks if a side of an element is scrolled past a side of it's parents |
| 2376 |
* @param {HTMLElement} el The element who's side being scrolled out of view is in question |
| 2377 |
* @param {String} side Side of the element in question ('top', 'left', 'right', 'bottom') |
| 2378 |
* @return {HTMLElement} The parent scroll element that the el's side is scrolled past, or null if there is no such element |
| 2379 |
*/ |
| 2380 |
function _isScrolledPast(el, side) { |
| 2381 |
var parent = _getParentAutoScrollElement(el, true), |
| 2382 |
elSide = _getRect(el)[side]; |
| 2383 |
|
| 2384 |
/* jshint boss:true */ |
| 2385 |
while (parent) { |
| 2386 |
var parentSide = _getRect(parent)[side], |
| 2387 |
visible; |
| 2388 |
|
| 2389 |
if (side === 'top' || side === 'left') { |
| 2390 |
visible = elSide >= parentSide; |
| 2391 |
} else { |
| 2392 |
visible = elSide <= parentSide; |
| 2393 |
} |
| 2394 |
|
| 2395 |
if (!visible) return parent; |
| 2396 |
|
| 2397 |
if (parent === _getWindowScrollingElement()) break; |
| 2398 |
|
| 2399 |
parent = _getParentAutoScrollElement(parent, false); |
| 2400 |
} |
| 2401 |
|
| 2402 |
return false; |
| 2403 |
} |
| 2404 |
|
| 2405 |
/** |
| 2406 |
* Returns the scroll offset of the given element, added with all the scroll offsets of parent elements. |
| 2407 |
* The value is returned in real pixels. |
| 2408 |
* @param {HTMLElement} el |
| 2409 |
* @return {Array} Offsets in the format of [left, top] |
| 2410 |
*/ |
| 2411 |
function _getRelativeScrollOffset(el) { |
| 2412 |
var offsetLeft = 0, |
| 2413 |
offsetTop = 0, |
| 2414 |
winScroller = _getWindowScrollingElement(); |
| 2415 |
|
| 2416 |
if (el) { |
| 2417 |
do { |
| 2418 |
var matrix = _matrix(el), |
| 2419 |
scaleX = matrix.a, |
| 2420 |
scaleY = matrix.d; |
| 2421 |
|
| 2422 |
offsetLeft += el.scrollLeft * scaleX; |
| 2423 |
offsetTop += el.scrollTop * scaleY; |
| 2424 |
} while (el !== winScroller && (el = el.parentNode)); |
| 2425 |
} |
| 2426 |
|
| 2427 |
return [offsetLeft, offsetTop]; |
| 2428 |
} |
| 2429 |
|
| 2430 |
// Fixed #973: |
| 2431 |
_on(document, 'touchmove', function(evt) { |
| 2432 |
if ((Sortable.active || awaitingDragStarted) && evt.cancelable) { |
| 2433 |
evt.preventDefault(); |
| 2434 |
} |
| 2435 |
}); |
| 2436 |
|
| 2437 |
|
| 2438 |
// Export utils |
| 2439 |
Sortable.utils = { |
| 2440 |
on: _on, |
| 2441 |
off: _off, |
| 2442 |
css: _css, |
| 2443 |
find: _find, |
| 2444 |
is: function (el, selector) { |
| 2445 |
return !!_closest(el, selector, el, false); |
| 2446 |
}, |
| 2447 |
extend: _extend, |
| 2448 |
throttle: _throttle, |
| 2449 |
closest: _closest, |
| 2450 |
toggleClass: _toggleClass, |
| 2451 |
clone: _clone, |
| 2452 |
index: _index, |
| 2453 |
nextTick: _nextTick, |
| 2454 |
cancelNextTick: _cancelNextTick, |
| 2455 |
detectDirection: _detectDirection, |
| 2456 |
getChild: _getChild |
| 2457 |
}; |
| 2458 |
|
| 2459 |
|
| 2460 |
/** |
| 2461 |
* Create sortable instance |
| 2462 |
* @param {HTMLElement} el |
| 2463 |
* @param {Object} [options] |
| 2464 |
*/ |
| 2465 |
Sortable.create = function (el, options) { |
| 2466 |
return new Sortable(el, options); |
| 2467 |
}; |
| 2468 |
|
| 2469 |
|
| 2470 |
// Export |
| 2471 |
Sortable.version = '1.8.4'; |
| 2472 |
return Sortable; |
| 2473 |
}); |
| 2474 |
|