Sortable.js 48 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966
  1. /**!
  2. * Sortable
  3. * @author RubaXa <trash@rubaxa.org>
  4. * @author owenm <owen23355@gmail.com>
  5. * @license MIT
  6. */
  7. import { version } from '../package.json';
  8. import { IE11OrLess, Edge, FireFox, Safari, IOS, ChromeForAndroid } from './BrowserInfo.js';
  9. import AnimationStateManager from './Animation.js';
  10. import PluginManager from './PluginManager.js';
  11. import dispatchEvent from './EventDispatcher.js';
  12. import {
  13. on,
  14. off,
  15. closest,
  16. toggleClass,
  17. css,
  18. matrix,
  19. find,
  20. getWindowScrollingElement,
  21. getRect,
  22. isScrolledPast,
  23. getChild,
  24. lastChild,
  25. index,
  26. getRelativeScrollOffset,
  27. extend,
  28. throttle,
  29. scrollBy,
  30. clone,
  31. expando
  32. } from './utils.js';
  33. let pluginEvent = function(eventName, sortable, { evt: originalEvent, ...data } = {}) {
  34. PluginManager.pluginEvent.bind(Sortable)(eventName, sortable, {
  35. dragEl,
  36. parentEl,
  37. ghostEl,
  38. rootEl,
  39. nextEl,
  40. lastDownEl,
  41. cloneEl,
  42. cloneHidden,
  43. dragStarted: moved,
  44. putSortable,
  45. activeSortable: Sortable.active,
  46. originalEvent,
  47. oldIndex,
  48. oldDraggableIndex,
  49. newIndex,
  50. newDraggableIndex,
  51. hideGhostForTarget: _hideGhostForTarget,
  52. unhideGhostForTarget: _unhideGhostForTarget,
  53. cloneNowHidden() {
  54. cloneHidden = true;
  55. },
  56. cloneNowShown() {
  57. cloneHidden = false;
  58. },
  59. dispatchSortableEvent(name) {
  60. _dispatchEvent({ sortable, name, originalEvent });
  61. },
  62. ...data
  63. });
  64. };
  65. function _dispatchEvent(info) {
  66. dispatchEvent({
  67. putSortable,
  68. cloneEl,
  69. targetEl: dragEl,
  70. rootEl,
  71. oldIndex,
  72. oldDraggableIndex,
  73. newIndex,
  74. newDraggableIndex,
  75. ...info
  76. });
  77. }
  78. let dragEl,
  79. parentEl,
  80. ghostEl,
  81. rootEl,
  82. nextEl,
  83. lastDownEl,
  84. cloneEl,
  85. cloneHidden,
  86. oldIndex,
  87. newIndex,
  88. oldDraggableIndex,
  89. newDraggableIndex,
  90. activeGroup,
  91. putSortable,
  92. awaitingDragStarted = false,
  93. ignoreNextClick = false,
  94. sortables = [],
  95. tapEvt,
  96. touchEvt,
  97. lastDx,
  98. lastDy,
  99. tapDistanceLeft,
  100. tapDistanceTop,
  101. moved,
  102. lastTarget,
  103. lastDirection,
  104. pastFirstInvertThresh = false,
  105. isCircumstantialInvert = false,
  106. targetMoveDistance,
  107. // For positioning ghost absolutely
  108. ghostRelativeParent,
  109. ghostRelativeParentInitialScroll = [], // (left, top)
  110. _silent = false,
  111. savedInputChecked = [];
  112. /** @const */
  113. const documentExists = typeof document !== 'undefined',
  114. PositionGhostAbsolutely = IOS,
  115. CSSFloatProperty = Edge || IE11OrLess ? 'cssFloat' : 'float',
  116. // This will not pass for IE9, because IE9 DnD only works on anchors
  117. supportDraggable = documentExists && !ChromeForAndroid && !IOS && ('draggable' in document.createElement('div')),
  118. supportCssPointerEvents = (function() {
  119. if (!documentExists) return;
  120. // false when <= IE11
  121. if (IE11OrLess) {
  122. return false;
  123. }
  124. let el = document.createElement('x');
  125. el.style.cssText = 'pointer-events:auto';
  126. return el.style.pointerEvents === 'auto';
  127. })(),
  128. _detectDirection = function(el, options) {
  129. let elCSS = css(el),
  130. elWidth = parseInt(elCSS.width)
  131. - parseInt(elCSS.paddingLeft)
  132. - parseInt(elCSS.paddingRight)
  133. - parseInt(elCSS.borderLeftWidth)
  134. - parseInt(elCSS.borderRightWidth),
  135. child1 = getChild(el, 0, options),
  136. child2 = getChild(el, 1, options),
  137. firstChildCSS = child1 && css(child1),
  138. secondChildCSS = child2 && css(child2),
  139. firstChildWidth = firstChildCSS && parseInt(firstChildCSS.marginLeft) + parseInt(firstChildCSS.marginRight) + getRect(child1).width,
  140. secondChildWidth = secondChildCSS && parseInt(secondChildCSS.marginLeft) + parseInt(secondChildCSS.marginRight) + getRect(child2).width;
  141. if (elCSS.display === 'flex') {
  142. return elCSS.flexDirection === 'column' || elCSS.flexDirection === 'column-reverse'
  143. ? 'vertical' : 'horizontal';
  144. }
  145. if (elCSS.display === 'grid') {
  146. return elCSS.gridTemplateColumns.split(' ').length <= 1 ? 'vertical' : 'horizontal';
  147. }
  148. if (child1 && firstChildCSS.float && firstChildCSS.float !== 'none') {
  149. let touchingSideChild2 = firstChildCSS.float === 'left' ? 'left' : 'right';
  150. return child2 && (secondChildCSS.clear === 'both' || secondChildCSS.clear === touchingSideChild2) ?
  151. 'vertical' : 'horizontal';
  152. }
  153. return (child1 &&
  154. (
  155. firstChildCSS.display === 'block' ||
  156. firstChildCSS.display === 'flex' ||
  157. firstChildCSS.display === 'table' ||
  158. firstChildCSS.display === 'grid' ||
  159. firstChildWidth >= elWidth &&
  160. elCSS[CSSFloatProperty] === 'none' ||
  161. child2 &&
  162. elCSS[CSSFloatProperty] === 'none' &&
  163. firstChildWidth + secondChildWidth > elWidth
  164. ) ?
  165. 'vertical' : 'horizontal'
  166. );
  167. },
  168. _dragElInRowColumn = function(dragRect, targetRect, vertical) {
  169. let dragElS1Opp = vertical ? dragRect.left : dragRect.top,
  170. dragElS2Opp = vertical ? dragRect.right : dragRect.bottom,
  171. dragElOppLength = vertical ? dragRect.width : dragRect.height,
  172. targetS1Opp = vertical ? targetRect.left : targetRect.top,
  173. targetS2Opp = vertical ? targetRect.right : targetRect.bottom,
  174. targetOppLength = vertical ? targetRect.width : targetRect.height;
  175. return (
  176. dragElS1Opp === targetS1Opp ||
  177. dragElS2Opp === targetS2Opp ||
  178. (dragElS1Opp + dragElOppLength / 2) === (targetS1Opp + targetOppLength / 2)
  179. );
  180. },
  181. /**
  182. * Detects first nearest empty sortable to X and Y position using emptyInsertThreshold.
  183. * @param {Number} x X position
  184. * @param {Number} y Y position
  185. * @return {HTMLElement} Element of the first found nearest Sortable
  186. */
  187. _detectNearestEmptySortable = function(x, y) {
  188. let ret;
  189. sortables.some((sortable) => {
  190. if (lastChild(sortable)) return;
  191. let rect = getRect(sortable),
  192. threshold = sortable[expando].options.emptyInsertThreshold,
  193. insideHorizontally = x >= (rect.left - threshold) && x <= (rect.right + threshold),
  194. insideVertically = y >= (rect.top - threshold) && y <= (rect.bottom + threshold);
  195. if (threshold && insideHorizontally && insideVertically) {
  196. return (ret = sortable);
  197. }
  198. });
  199. return ret;
  200. },
  201. _prepareGroup = function (options) {
  202. function toFn(value, pull) {
  203. return function(to, from, dragEl, evt) {
  204. let sameGroup = to.options.group.name &&
  205. from.options.group.name &&
  206. to.options.group.name === from.options.group.name;
  207. if (value == null && (pull || sameGroup)) {
  208. // Default pull value
  209. // Default pull and put value if same group
  210. return true;
  211. } else if (value == null || value === false) {
  212. return false;
  213. } else if (pull && value === 'clone') {
  214. return value;
  215. } else if (typeof value === 'function') {
  216. return toFn(value(to, from, dragEl, evt), pull)(to, from, dragEl, evt);
  217. } else {
  218. let otherGroup = (pull ? to : from).options.group.name;
  219. return (value === true ||
  220. (typeof value === 'string' && value === otherGroup) ||
  221. (value.join && value.indexOf(otherGroup) > -1));
  222. }
  223. };
  224. }
  225. let group = {};
  226. let originalGroup = options.group;
  227. if (!originalGroup || typeof originalGroup != 'object') {
  228. originalGroup = {name: originalGroup};
  229. }
  230. group.name = originalGroup.name;
  231. group.checkPull = toFn(originalGroup.pull, true);
  232. group.checkPut = toFn(originalGroup.put);
  233. group.revertClone = originalGroup.revertClone;
  234. options.group = group;
  235. },
  236. _hideGhostForTarget = function() {
  237. if (!supportCssPointerEvents && ghostEl) {
  238. css(ghostEl, 'display', 'none');
  239. }
  240. },
  241. _unhideGhostForTarget = function() {
  242. if (!supportCssPointerEvents && ghostEl) {
  243. css(ghostEl, 'display', '');
  244. }
  245. };
  246. // #1184 fix - Prevent click event on fallback if dragged but item not changed position
  247. if (documentExists) {
  248. document.addEventListener('click', function(evt) {
  249. if (ignoreNextClick) {
  250. evt.preventDefault();
  251. evt.stopPropagation && evt.stopPropagation();
  252. evt.stopImmediatePropagation && evt.stopImmediatePropagation();
  253. ignoreNextClick = false;
  254. return false;
  255. }
  256. }, true);
  257. }
  258. let nearestEmptyInsertDetectEvent = function(evt) {
  259. if (dragEl) {
  260. evt = evt.touches ? evt.touches[0] : evt;
  261. let nearest = _detectNearestEmptySortable(evt.clientX, evt.clientY);
  262. if (nearest) {
  263. // Create imitation event
  264. let event = {};
  265. for (let i in evt) {
  266. if (evt.hasOwnProperty(i)) {
  267. event[i] = evt[i];
  268. }
  269. }
  270. event.target = event.rootEl = nearest;
  271. event.preventDefault = void 0;
  272. event.stopPropagation = void 0;
  273. nearest[expando]._onDragOver(event);
  274. }
  275. }
  276. };
  277. let _checkOutsideTargetEl = function(evt) {
  278. if (dragEl) {
  279. dragEl.parentNode[expando]._isOutsideThisEl(evt.target);
  280. }
  281. };
  282. /**
  283. * @class Sortable
  284. * @param {HTMLElement} el
  285. * @param {Object} [options]
  286. */
  287. function Sortable(el, options) {
  288. if (!(el && el.nodeType && el.nodeType === 1)) {
  289. throw `Sortable: \`el\` must be an HTMLElement, not ${ {}.toString.call(el) }`;
  290. }
  291. this.el = el; // root element
  292. this.options = options = Object.assign({}, options);
  293. // Export instance
  294. el[expando] = this;
  295. let defaults = {
  296. group: null,
  297. sort: true,
  298. disabled: false,
  299. store: null,
  300. handle: null,
  301. draggable: /^[uo]l$/i.test(el.nodeName) ? '>li' : '>*',
  302. swapThreshold: 1, // percentage; 0 <= x <= 1
  303. invertSwap: false, // invert always
  304. invertedSwapThreshold: null, // will be set to same as swapThreshold if default
  305. removeCloneOnHide: true,
  306. direction: function() {
  307. return _detectDirection(el, this.options);
  308. },
  309. ghostClass: 'sortable-ghost',
  310. chosenClass: 'sortable-chosen',
  311. dragClass: 'sortable-drag',
  312. ignore: 'a, img',
  313. filter: null,
  314. preventOnFilter: true,
  315. animation: 0,
  316. easing: null,
  317. setData: function (dataTransfer, dragEl) {
  318. dataTransfer.setData('Text', dragEl.textContent);
  319. },
  320. dropBubble: false,
  321. dragoverBubble: false,
  322. dataIdAttr: 'data-id',
  323. delay: 0,
  324. delayOnTouchOnly: false,
  325. touchStartThreshold: (Number.parseInt ? Number : window).parseInt(window.devicePixelRatio, 10) || 1,
  326. forceFallback: false,
  327. fallbackClass: 'sortable-fallback',
  328. fallbackOnBody: false,
  329. fallbackTolerance: 0,
  330. fallbackOffset: {x: 0, y: 0},
  331. supportPointer: Sortable.supportPointer !== false && ('PointerEvent' in window),
  332. emptyInsertThreshold: 5
  333. };
  334. PluginManager.initializePlugins(this, el, defaults);
  335. // Set default options
  336. for (let name in defaults) {
  337. !(name in options) && (options[name] = defaults[name]);
  338. }
  339. _prepareGroup(options);
  340. // Bind all private methods
  341. for (let fn in this) {
  342. if (fn.charAt(0) === '_' && typeof this[fn] === 'function') {
  343. this[fn] = this[fn].bind(this);
  344. }
  345. }
  346. // Setup drag mode
  347. this.nativeDraggable = options.forceFallback ? false : supportDraggable;
  348. if (this.nativeDraggable) {
  349. // Touch start threshold cannot be greater than the native dragstart threshold
  350. this.options.touchStartThreshold = 1;
  351. }
  352. // Bind events
  353. if (options.supportPointer) {
  354. on(el, 'pointerdown', this._onTapStart);
  355. } else {
  356. on(el, 'mousedown', this._onTapStart);
  357. on(el, 'touchstart', this._onTapStart);
  358. }
  359. if (this.nativeDraggable) {
  360. on(el, 'dragover', this);
  361. on(el, 'dragenter', this);
  362. }
  363. sortables.push(this.el);
  364. // Restore sorting
  365. options.store && options.store.get && this.sort(options.store.get(this) || []);
  366. // Add animation state manager
  367. Object.assign(this, AnimationStateManager());
  368. }
  369. Sortable.prototype = /** @lends Sortable.prototype */ {
  370. constructor: Sortable,
  371. _isOutsideThisEl: function(target) {
  372. if (!this.el.contains(target) && target !== this.el) {
  373. lastTarget = null;
  374. }
  375. },
  376. _getDirection: function(evt, target) {
  377. return (typeof this.options.direction === 'function') ? this.options.direction.call(this, evt, target, dragEl) : this.options.direction;
  378. },
  379. _onTapStart: function (/** Event|TouchEvent */evt) {
  380. if (!evt.cancelable) return;
  381. let _this = this,
  382. el = this.el,
  383. options = this.options,
  384. preventOnFilter = options.preventOnFilter,
  385. type = evt.type,
  386. touch = (evt.touches && evt.touches[0]) || (evt.pointerType && evt.pointerType === 'touch' && evt),
  387. target = (touch || evt).target,
  388. originalTarget = evt.target.shadowRoot && ((evt.path && evt.path[0]) || (evt.composedPath && evt.composedPath()[0])) || target,
  389. filter = options.filter;
  390. _saveInputCheckedState(el);
  391. // Don't trigger start event when an element is been dragged, otherwise the evt.oldindex always wrong when set option.group.
  392. if (dragEl) {
  393. return;
  394. }
  395. if (/mousedown|pointerdown/.test(type) && evt.button !== 0 || options.disabled) {
  396. return; // only left button and enabled
  397. }
  398. // cancel dnd if original target is content editable
  399. if (originalTarget.isContentEditable) {
  400. return;
  401. }
  402. target = closest(target, options.draggable, el, false);
  403. if (target && target.animated) {
  404. return;
  405. }
  406. if (lastDownEl === target) {
  407. // Ignoring duplicate `down`
  408. return;
  409. }
  410. // Get the index of the dragged element within its parent
  411. oldIndex = index(target);
  412. oldDraggableIndex = index(target, options.draggable);
  413. // Check filter
  414. if (typeof filter === 'function') {
  415. if (filter.call(this, evt, target, this)) {
  416. _dispatchEvent({
  417. sortable: _this,
  418. rootEl: originalTarget,
  419. name: 'filter',
  420. targetEl: target,
  421. toEl: el,
  422. fromEl: el
  423. });
  424. pluginEvent('filter', _this, { evt });
  425. preventOnFilter && evt.cancelable && evt.preventDefault();
  426. return; // cancel dnd
  427. }
  428. }
  429. else if (filter) {
  430. filter = filter.split(',').some(function (criteria) {
  431. criteria = closest(originalTarget, criteria.trim(), el, false);
  432. if (criteria) {
  433. _dispatchEvent({
  434. sortable: _this,
  435. rootEl: criteria,
  436. name: 'filter',
  437. targetEl: target,
  438. fromEl: el,
  439. toEl: el
  440. });
  441. pluginEvent('filter', _this, { evt });
  442. return true;
  443. }
  444. });
  445. if (filter) {
  446. preventOnFilter && evt.cancelable && evt.preventDefault();
  447. return; // cancel dnd
  448. }
  449. }
  450. if (options.handle && !closest(originalTarget, options.handle, el, false)) {
  451. return;
  452. }
  453. // Prepare `dragstart`
  454. this._prepareDragStart(evt, touch, target);
  455. },
  456. _prepareDragStart: function (/** Event */evt, /** Touch */touch, /** HTMLElement */target) {
  457. let _this = this,
  458. el = _this.el,
  459. options = _this.options,
  460. ownerDocument = el.ownerDocument,
  461. dragStartFn;
  462. if (target && !dragEl && (target.parentNode === el)) {
  463. let dragRect = getRect(target);
  464. rootEl = el;
  465. dragEl = target;
  466. parentEl = dragEl.parentNode;
  467. nextEl = dragEl.nextSibling;
  468. lastDownEl = target;
  469. activeGroup = options.group;
  470. Sortable.dragged = dragEl;
  471. tapEvt = {
  472. target: dragEl,
  473. clientX: (touch || evt).clientX,
  474. clientY: (touch || evt).clientY
  475. };
  476. tapDistanceLeft = tapEvt.clientX - dragRect.left;
  477. tapDistanceTop = tapEvt.clientY - dragRect.top;
  478. this._lastX = (touch || evt).clientX;
  479. this._lastY = (touch || evt).clientY;
  480. dragEl.style['will-change'] = 'all';
  481. dragStartFn = function () {
  482. pluginEvent('delayEnded', _this, { evt });
  483. if (Sortable.eventCanceled) {
  484. _this._onDrop();
  485. return;
  486. }
  487. // Delayed drag has been triggered
  488. // we can re-enable the events: touchmove/mousemove
  489. _this._disableDelayedDragEvents();
  490. if (!FireFox && _this.nativeDraggable) {
  491. dragEl.draggable = true;
  492. }
  493. // Bind the events: dragstart/dragend
  494. _this._triggerDragStart(evt, touch);
  495. // Drag start event
  496. _dispatchEvent({
  497. sortable: _this,
  498. name: 'choose',
  499. originalEvent: evt
  500. });
  501. // Chosen item
  502. toggleClass(dragEl, options.chosenClass, true);
  503. };
  504. // Disable "draggable"
  505. options.ignore.split(',').forEach(function (criteria) {
  506. find(dragEl, criteria.trim(), _disableDraggable);
  507. });
  508. on(ownerDocument, 'dragover', nearestEmptyInsertDetectEvent);
  509. on(ownerDocument, 'mousemove', nearestEmptyInsertDetectEvent);
  510. on(ownerDocument, 'touchmove', nearestEmptyInsertDetectEvent);
  511. on(ownerDocument, 'mouseup', _this._onDrop);
  512. on(ownerDocument, 'touchend', _this._onDrop);
  513. on(ownerDocument, 'touchcancel', _this._onDrop);
  514. // Make dragEl draggable (must be before delay for FireFox)
  515. if (FireFox && this.nativeDraggable) {
  516. this.options.touchStartThreshold = 4;
  517. dragEl.draggable = true;
  518. }
  519. pluginEvent('delayStart', this, { evt });
  520. // Delay is impossible for native DnD in Edge or IE
  521. if (options.delay && (!options.delayOnTouchOnly || touch) && (!this.nativeDraggable || !(Edge || IE11OrLess))) {
  522. if (Sortable.eventCanceled) {
  523. this._onDrop();
  524. return;
  525. }
  526. // If the user moves the pointer or let go the click or touch
  527. // before the delay has been reached:
  528. // disable the delayed drag
  529. on(ownerDocument, 'mouseup', _this._disableDelayedDrag);
  530. on(ownerDocument, 'touchend', _this._disableDelayedDrag);
  531. on(ownerDocument, 'touchcancel', _this._disableDelayedDrag);
  532. on(ownerDocument, 'mousemove', _this._delayedDragTouchMoveHandler);
  533. on(ownerDocument, 'touchmove', _this._delayedDragTouchMoveHandler);
  534. options.supportPointer && on(ownerDocument, 'pointermove', _this._delayedDragTouchMoveHandler);
  535. _this._dragStartTimer = setTimeout(dragStartFn, options.delay);
  536. } else {
  537. dragStartFn();
  538. }
  539. }
  540. },
  541. _delayedDragTouchMoveHandler: function (/** TouchEvent|PointerEvent **/e) {
  542. let touch = e.touches ? e.touches[0] : e;
  543. if (Math.max(Math.abs(touch.clientX - this._lastX), Math.abs(touch.clientY - this._lastY))
  544. >= Math.floor(this.options.touchStartThreshold / (this.nativeDraggable && window.devicePixelRatio || 1))
  545. ) {
  546. this._disableDelayedDrag();
  547. }
  548. },
  549. _disableDelayedDrag: function () {
  550. dragEl && _disableDraggable(dragEl);
  551. clearTimeout(this._dragStartTimer);
  552. this._disableDelayedDragEvents();
  553. },
  554. _disableDelayedDragEvents: function () {
  555. let ownerDocument = this.el.ownerDocument;
  556. off(ownerDocument, 'mouseup', this._disableDelayedDrag);
  557. off(ownerDocument, 'touchend', this._disableDelayedDrag);
  558. off(ownerDocument, 'touchcancel', this._disableDelayedDrag);
  559. off(ownerDocument, 'mousemove', this._delayedDragTouchMoveHandler);
  560. off(ownerDocument, 'touchmove', this._delayedDragTouchMoveHandler);
  561. off(ownerDocument, 'pointermove', this._delayedDragTouchMoveHandler);
  562. },
  563. _triggerDragStart: function (/** Event */evt, /** Touch */touch) {
  564. touch = touch || (evt.pointerType == 'touch' && evt);
  565. if (!this.nativeDraggable || touch) {
  566. if (this.options.supportPointer) {
  567. on(document, 'pointermove', this._onTouchMove);
  568. } else if (touch) {
  569. on(document, 'touchmove', this._onTouchMove);
  570. } else {
  571. on(document, 'mousemove', this._onTouchMove);
  572. }
  573. } else {
  574. on(dragEl, 'dragend', this);
  575. on(rootEl, 'dragstart', this._onDragStart);
  576. }
  577. try {
  578. if (document.selection) {
  579. // Timeout neccessary for IE9
  580. _nextTick(function () {
  581. document.selection.empty();
  582. });
  583. } else {
  584. window.getSelection().removeAllRanges();
  585. }
  586. } catch (err) {
  587. }
  588. },
  589. _dragStarted: function (fallback, evt) {
  590. let _this = this;
  591. awaitingDragStarted = false;
  592. if (rootEl && dragEl) {
  593. pluginEvent('dragStarted', this, { evt });
  594. if (this.nativeDraggable) {
  595. on(document, 'dragover', _checkOutsideTargetEl);
  596. }
  597. let options = this.options;
  598. // Apply effect
  599. !fallback && toggleClass(dragEl, options.dragClass, false);
  600. toggleClass(dragEl, options.ghostClass, true);
  601. Sortable.active = this;
  602. fallback && this._appendGhost();
  603. // Drag start event
  604. _dispatchEvent({
  605. sortable: this,
  606. name: 'start',
  607. originalEvent: evt
  608. });
  609. } else {
  610. this._nulling();
  611. }
  612. },
  613. _emulateDragOver: function () {
  614. if (touchEvt) {
  615. this._lastX = touchEvt.clientX;
  616. this._lastY = touchEvt.clientY;
  617. _hideGhostForTarget();
  618. let target = document.elementFromPoint(touchEvt.clientX, touchEvt.clientY);
  619. let parent = target;
  620. while (target && target.shadowRoot) {
  621. target = target.shadowRoot.elementFromPoint(touchEvt.clientX, touchEvt.clientY);
  622. if (target === parent) break;
  623. parent = target;
  624. }
  625. dragEl.parentNode[expando]._isOutsideThisEl(target);
  626. if (parent) {
  627. do {
  628. if (parent[expando]) {
  629. let inserted;
  630. inserted = parent[expando]._onDragOver({
  631. clientX: touchEvt.clientX,
  632. clientY: touchEvt.clientY,
  633. target: target,
  634. rootEl: parent
  635. });
  636. if (inserted && !this.options.dragoverBubble) {
  637. break;
  638. }
  639. }
  640. target = parent; // store last element
  641. }
  642. /* jshint boss:true */
  643. while (parent = parent.parentNode);
  644. }
  645. _unhideGhostForTarget();
  646. }
  647. },
  648. _onTouchMove: function (/**TouchEvent*/evt) {
  649. if (tapEvt) {
  650. let options = this.options,
  651. fallbackTolerance = options.fallbackTolerance,
  652. fallbackOffset = options.fallbackOffset,
  653. touch = evt.touches ? evt.touches[0] : evt,
  654. ghostMatrix = ghostEl && matrix(ghostEl, true),
  655. scaleX = ghostEl && ghostMatrix && ghostMatrix.a,
  656. scaleY = ghostEl && ghostMatrix && ghostMatrix.d,
  657. relativeScrollOffset = PositionGhostAbsolutely && ghostRelativeParent && getRelativeScrollOffset(ghostRelativeParent),
  658. dx = ((touch.clientX - tapEvt.clientX)
  659. + fallbackOffset.x) / (scaleX || 1)
  660. + (relativeScrollOffset ? (relativeScrollOffset[0] - ghostRelativeParentInitialScroll[0]) : 0) / (scaleX || 1),
  661. dy = ((touch.clientY - tapEvt.clientY)
  662. + fallbackOffset.y) / (scaleY || 1)
  663. + (relativeScrollOffset ? (relativeScrollOffset[1] - ghostRelativeParentInitialScroll[1]) : 0) / (scaleY || 1);
  664. // only set the status to dragging, when we are actually dragging
  665. if (!Sortable.active && !awaitingDragStarted) {
  666. if (fallbackTolerance &&
  667. Math.max(Math.abs(touch.clientX - this._lastX), Math.abs(touch.clientY - this._lastY)) < fallbackTolerance
  668. ) {
  669. return;
  670. }
  671. this._onDragStart(evt, true);
  672. }
  673. if (ghostEl) {
  674. if (ghostMatrix) {
  675. ghostMatrix.e += dx - (lastDx || 0);
  676. ghostMatrix.f += dy - (lastDy || 0);
  677. } else {
  678. ghostMatrix = {
  679. a: 1,
  680. b: 0,
  681. c: 0,
  682. d: 1,
  683. e: dx,
  684. f: dy
  685. };
  686. }
  687. let cssMatrix = `matrix(${ghostMatrix.a},${ghostMatrix.b},${ghostMatrix.c},${ghostMatrix.d},${ghostMatrix.e},${ghostMatrix.f})`;
  688. css(ghostEl, 'webkitTransform', cssMatrix);
  689. css(ghostEl, 'mozTransform', cssMatrix);
  690. css(ghostEl, 'msTransform', cssMatrix);
  691. css(ghostEl, 'transform', cssMatrix);
  692. lastDx = dx;
  693. lastDy = dy;
  694. touchEvt = touch;
  695. }
  696. evt.cancelable && evt.preventDefault();
  697. }
  698. },
  699. _appendGhost: function () {
  700. // Bug if using scale(): https://stackoverflow.com/questions/2637058
  701. // Not being adjusted for
  702. if (!ghostEl) {
  703. let container = this.options.fallbackOnBody ? document.body : rootEl,
  704. rect = getRect(dragEl, true, PositionGhostAbsolutely, true, container),
  705. options = this.options;
  706. // Position absolutely
  707. if (PositionGhostAbsolutely) {
  708. // Get relatively positioned parent
  709. ghostRelativeParent = container;
  710. while (
  711. css(ghostRelativeParent, 'position') === 'static' &&
  712. css(ghostRelativeParent, 'transform') === 'none' &&
  713. ghostRelativeParent !== document
  714. ) {
  715. ghostRelativeParent = ghostRelativeParent.parentNode;
  716. }
  717. if (ghostRelativeParent !== document.body && ghostRelativeParent !== document.documentElement) {
  718. if (ghostRelativeParent === document) ghostRelativeParent = getWindowScrollingElement();
  719. rect.top += ghostRelativeParent.scrollTop;
  720. rect.left += ghostRelativeParent.scrollLeft;
  721. } else {
  722. ghostRelativeParent = getWindowScrollingElement();
  723. }
  724. ghostRelativeParentInitialScroll = getRelativeScrollOffset(ghostRelativeParent);
  725. }
  726. ghostEl = dragEl.cloneNode(true);
  727. toggleClass(ghostEl, options.ghostClass, false);
  728. toggleClass(ghostEl, options.fallbackClass, true);
  729. toggleClass(ghostEl, options.dragClass, true);
  730. css(ghostEl, 'transition', '');
  731. css(ghostEl, 'transform', '');
  732. css(ghostEl, 'box-sizing', 'border-box');
  733. css(ghostEl, 'margin', 0);
  734. css(ghostEl, 'top', rect.top);
  735. css(ghostEl, 'left', rect.left);
  736. css(ghostEl, 'width', rect.width);
  737. css(ghostEl, 'height', rect.height);
  738. css(ghostEl, 'opacity', '0.8');
  739. css(ghostEl, 'position', (PositionGhostAbsolutely ? 'absolute' : 'fixed'));
  740. css(ghostEl, 'zIndex', '100000');
  741. css(ghostEl, 'pointerEvents', 'none');
  742. Sortable.ghost = ghostEl;
  743. container.appendChild(ghostEl);
  744. // Set transform-origin
  745. css(ghostEl, 'transform-origin', (tapDistanceLeft / parseInt(ghostEl.style.width) * 100) + '% ' + (tapDistanceTop / parseInt(ghostEl.style.height) * 100) + '%');
  746. }
  747. },
  748. _onDragStart: function (/**Event*/evt, /**boolean*/fallback) {
  749. let _this = this;
  750. let dataTransfer = evt.dataTransfer;
  751. let options = _this.options;
  752. pluginEvent('dragStart', this, { evt });
  753. if (Sortable.eventCanceled) {
  754. this._onDrop();
  755. return;
  756. }
  757. pluginEvent('setupClone', this);
  758. if (!Sortable.eventCanceled) {
  759. cloneEl = clone(dragEl);
  760. cloneEl.draggable = false;
  761. cloneEl.style['will-change'] = '';
  762. this._hideClone();
  763. toggleClass(cloneEl, this.options.chosenClass, false);
  764. Sortable.clone = cloneEl;
  765. }
  766. // #1143: IFrame support workaround
  767. _this.cloneId = _nextTick(function() {
  768. pluginEvent('clone', _this);
  769. if (Sortable.eventCanceled) return;
  770. if (!_this.options.removeCloneOnHide) {
  771. rootEl.insertBefore(cloneEl, dragEl);
  772. }
  773. _this._hideClone();
  774. _dispatchEvent({
  775. sortable: _this,
  776. name: 'clone'
  777. });
  778. });
  779. !fallback && toggleClass(dragEl, options.dragClass, true);
  780. // Set proper drop events
  781. if (fallback) {
  782. ignoreNextClick = true;
  783. _this._loopId = setInterval(_this._emulateDragOver, 50);
  784. } else {
  785. // Undo what was set in _prepareDragStart before drag started
  786. off(document, 'mouseup', _this._onDrop);
  787. off(document, 'touchend', _this._onDrop);
  788. off(document, 'touchcancel', _this._onDrop);
  789. if (dataTransfer) {
  790. dataTransfer.effectAllowed = 'move';
  791. options.setData && options.setData.call(_this, dataTransfer, dragEl);
  792. }
  793. on(document, 'drop', _this);
  794. // #1276 fix:
  795. css(dragEl, 'transform', 'translateZ(0)');
  796. }
  797. awaitingDragStarted = true;
  798. _this._dragStartId = _nextTick(_this._dragStarted.bind(_this, fallback, evt));
  799. on(document, 'selectstart', _this);
  800. moved = true;
  801. if (Safari) {
  802. css(document.body, 'user-select', 'none');
  803. }
  804. },
  805. // Returns true - if no further action is needed (either inserted or another condition)
  806. _onDragOver: function (/**Event*/evt) {
  807. let el = this.el,
  808. target = evt.target,
  809. dragRect,
  810. targetRect,
  811. revert,
  812. options = this.options,
  813. group = options.group,
  814. activeSortable = Sortable.active,
  815. isOwner = (activeGroup === group),
  816. canSort = options.sort,
  817. fromSortable = (putSortable || activeSortable),
  818. vertical,
  819. _this = this,
  820. completedFired = false;
  821. if (_silent) return;
  822. function dragOverEvent(name, extra) {
  823. pluginEvent(name, _this, {
  824. evt,
  825. isOwner,
  826. axis: vertical ? 'vertical' : 'horizontal',
  827. revert,
  828. dragRect,
  829. targetRect,
  830. canSort,
  831. fromSortable,
  832. target,
  833. completed,
  834. onMove(target, after) {
  835. return onMove(rootEl, el, dragEl, dragRect, target, getRect(target), evt, after);
  836. },
  837. changed,
  838. ...extra
  839. });
  840. }
  841. // Capture animation state
  842. function capture() {
  843. dragOverEvent('dragOverAnimationCapture');
  844. _this.captureAnimationState();
  845. if (_this !== fromSortable) {
  846. fromSortable.captureAnimationState();
  847. }
  848. }
  849. // Return invocation when dragEl is inserted (or completed)
  850. function completed(insertion) {
  851. dragOverEvent('dragOverCompleted', { insertion });
  852. if (insertion) {
  853. // Clones must be hidden before folding animation to capture dragRectAbsolute properly
  854. if (isOwner) {
  855. activeSortable._hideClone();
  856. } else {
  857. activeSortable._showClone(_this);
  858. }
  859. if (_this !== fromSortable) {
  860. // Set ghost class to new sortable's ghost class
  861. toggleClass(dragEl, putSortable ? putSortable.options.ghostClass : activeSortable.options.ghostClass, false);
  862. toggleClass(dragEl, options.ghostClass, true);
  863. }
  864. if (putSortable !== _this && _this !== Sortable.active) {
  865. putSortable = _this;
  866. } else if (_this === Sortable.active && putSortable) {
  867. putSortable = null;
  868. }
  869. // Animation
  870. if (fromSortable === _this) {
  871. _this._ignoreWhileAnimating = target;
  872. }
  873. _this.animateAll(function() {
  874. dragOverEvent('dragOverAnimationComplete');
  875. _this._ignoreWhileAnimating = null;
  876. });
  877. if (_this !== fromSortable) {
  878. fromSortable.animateAll();
  879. fromSortable._ignoreWhileAnimating = null;
  880. }
  881. }
  882. // Null lastTarget if it is not inside a previously swapped element
  883. if ((target === dragEl && !dragEl.animated) || (target === el && !target.animated)) {
  884. lastTarget = null;
  885. }
  886. // no bubbling and not fallback
  887. if (!options.dragoverBubble && !evt.rootEl && target !== document) {
  888. dragEl.parentNode[expando]._isOutsideThisEl(evt.target);
  889. // Do not detect for empty insert if already inserted
  890. !insertion && nearestEmptyInsertDetectEvent(evt);
  891. }
  892. !options.dragoverBubble && evt.stopPropagation && evt.stopPropagation();
  893. return (completedFired = true);
  894. }
  895. // Call when dragEl has been inserted
  896. function changed() {
  897. newIndex = index(dragEl);
  898. newDraggableIndex = index(dragEl, options.draggable);
  899. _dispatchEvent({
  900. sortable: _this,
  901. name: 'change',
  902. toEl: el,
  903. newIndex,
  904. newDraggableIndex,
  905. originalEvent: evt
  906. });
  907. }
  908. if (evt.preventDefault !== void 0) {
  909. evt.cancelable && evt.preventDefault();
  910. }
  911. target = closest(target, options.draggable, el, true);
  912. dragOverEvent('dragOver');
  913. if (Sortable.eventCanceled) return completedFired;
  914. if (
  915. dragEl.contains(evt.target) ||
  916. target.animated && target.animatingX && target.animatingY ||
  917. _this._ignoreWhileAnimating === target
  918. ) {
  919. return completed(false);
  920. }
  921. ignoreNextClick = false;
  922. if (activeSortable && !options.disabled &&
  923. (isOwner
  924. ? canSort || (revert = !rootEl.contains(dragEl)) // Reverting item into the original list
  925. : (
  926. putSortable === this ||
  927. (
  928. (this.lastPutMode = activeGroup.checkPull(this, activeSortable, dragEl, evt)) &&
  929. group.checkPut(this, activeSortable, dragEl, evt)
  930. )
  931. )
  932. )
  933. ) {
  934. vertical = this._getDirection(evt, target) === 'vertical';
  935. dragRect = getRect(dragEl);
  936. dragOverEvent('dragOverValid');
  937. if (Sortable.eventCanceled) return completedFired;
  938. if (revert) {
  939. parentEl = rootEl; // actualization
  940. capture();
  941. this._hideClone();
  942. dragOverEvent('revert');
  943. if (!Sortable.eventCanceled) {
  944. if (nextEl) {
  945. rootEl.insertBefore(dragEl, nextEl);
  946. } else {
  947. rootEl.appendChild(dragEl);
  948. }
  949. }
  950. return completed(true);
  951. }
  952. let elLastChild = lastChild(el, options.draggable);
  953. if (!elLastChild || _ghostIsLast(evt, vertical, this) && !elLastChild.animated) {
  954. // If already at end of list: Do not insert
  955. if (elLastChild === dragEl) {
  956. return completed(false);
  957. }
  958. // assign target only if condition is true
  959. if (elLastChild && el === evt.target) {
  960. target = elLastChild;
  961. }
  962. if (target) {
  963. targetRect = getRect(target);
  964. }
  965. if (onMove(rootEl, el, dragEl, dragRect, target, targetRect, evt, !!target) !== false) {
  966. capture();
  967. el.appendChild(dragEl);
  968. parentEl = el; // actualization
  969. changed();
  970. return completed(true);
  971. }
  972. }
  973. else if (target.parentNode === el) {
  974. targetRect = getRect(target);
  975. let direction = 0,
  976. targetBeforeFirstSwap,
  977. differentLevel = dragEl.parentNode !== el,
  978. differentRowCol = !_dragElInRowColumn(dragEl.animated && dragEl.toRect || dragRect, target.animated && target.toRect || targetRect, vertical),
  979. side1 = vertical ? 'top' : 'left',
  980. scrolledPastTop = isScrolledPast(target, 'top', 'top') || isScrolledPast(dragEl, 'top', 'top'),
  981. scrollBefore = scrolledPastTop ? scrolledPastTop.scrollTop : void 0;
  982. if (lastTarget !== target) {
  983. targetBeforeFirstSwap = targetRect[side1];
  984. pastFirstInvertThresh = false;
  985. isCircumstantialInvert = (!differentRowCol && options.invertSwap) || differentLevel;
  986. }
  987. direction = _getSwapDirection(
  988. evt, target, targetRect, vertical,
  989. differentRowCol ? 1 : options.swapThreshold,
  990. options.invertedSwapThreshold == null ? options.swapThreshold : options.invertedSwapThreshold,
  991. isCircumstantialInvert,
  992. lastTarget === target
  993. );
  994. let sibling;
  995. if (direction !== 0) {
  996. // Check if target is beside dragEl in respective direction (ignoring hidden elements)
  997. let dragIndex = index(dragEl);
  998. do {
  999. dragIndex -= direction;
  1000. sibling = parentEl.children[dragIndex];
  1001. } while (sibling && (css(sibling, 'display') === 'none' || sibling === ghostEl));
  1002. }
  1003. // If dragEl is already beside target: Do not insert
  1004. if (
  1005. direction === 0 ||
  1006. sibling === target
  1007. ) {
  1008. return completed(false);
  1009. }
  1010. lastTarget = target;
  1011. lastDirection = direction;
  1012. let nextSibling = target.nextElementSibling,
  1013. after = false;
  1014. after = direction === 1;
  1015. let moveVector = onMove(rootEl, el, dragEl, dragRect, target, targetRect, evt, after);
  1016. if (moveVector !== false) {
  1017. if (moveVector === 1 || moveVector === -1) {
  1018. after = (moveVector === 1);
  1019. }
  1020. _silent = true;
  1021. setTimeout(_unsilent, 30);
  1022. capture();
  1023. if (after && !nextSibling) {
  1024. el.appendChild(dragEl);
  1025. } else {
  1026. target.parentNode.insertBefore(dragEl, after ? nextSibling : target);
  1027. }
  1028. // Undo chrome's scroll adjustment (has no effect on other browsers)
  1029. if (scrolledPastTop) {
  1030. scrollBy(scrolledPastTop, 0, scrollBefore - scrolledPastTop.scrollTop);
  1031. }
  1032. parentEl = dragEl.parentNode; // actualization
  1033. // must be done before animation
  1034. if (targetBeforeFirstSwap !== undefined && !isCircumstantialInvert) {
  1035. targetMoveDistance = Math.abs(targetBeforeFirstSwap - getRect(target)[side1]);
  1036. }
  1037. changed();
  1038. return completed(true);
  1039. }
  1040. }
  1041. if (el.contains(dragEl)) {
  1042. return completed(false);
  1043. }
  1044. }
  1045. return false;
  1046. },
  1047. _ignoreWhileAnimating: null,
  1048. _offMoveEvents: function() {
  1049. off(document, 'mousemove', this._onTouchMove);
  1050. off(document, 'touchmove', this._onTouchMove);
  1051. off(document, 'pointermove', this._onTouchMove);
  1052. off(document, 'dragover', nearestEmptyInsertDetectEvent);
  1053. off(document, 'mousemove', nearestEmptyInsertDetectEvent);
  1054. off(document, 'touchmove', nearestEmptyInsertDetectEvent);
  1055. },
  1056. _offUpEvents: function () {
  1057. let ownerDocument = this.el.ownerDocument;
  1058. off(ownerDocument, 'mouseup', this._onDrop);
  1059. off(ownerDocument, 'touchend', this._onDrop);
  1060. off(ownerDocument, 'pointerup', this._onDrop);
  1061. off(ownerDocument, 'touchcancel', this._onDrop);
  1062. off(document, 'selectstart', this);
  1063. },
  1064. _onDrop: function (/**Event*/evt) {
  1065. let el = this.el,
  1066. options = this.options;
  1067. // Get the index of the dragged element within its parent
  1068. newIndex = index(dragEl);
  1069. newDraggableIndex = index(dragEl, options.draggable);
  1070. pluginEvent('drop', this, {
  1071. evt
  1072. });
  1073. parentEl = dragEl && dragEl.parentNode;
  1074. // Get again after plugin event
  1075. newIndex = index(dragEl);
  1076. newDraggableIndex = index(dragEl, options.draggable);
  1077. if (Sortable.eventCanceled) {
  1078. this._nulling();
  1079. return;
  1080. }
  1081. awaitingDragStarted = false;
  1082. isCircumstantialInvert = false;
  1083. pastFirstInvertThresh = false;
  1084. clearInterval(this._loopId);
  1085. clearTimeout(this._dragStartTimer);
  1086. _cancelNextTick(this.cloneId);
  1087. _cancelNextTick(this._dragStartId);
  1088. // Unbind events
  1089. if (this.nativeDraggable) {
  1090. off(document, 'drop', this);
  1091. off(el, 'dragstart', this._onDragStart);
  1092. }
  1093. this._offMoveEvents();
  1094. this._offUpEvents();
  1095. if (Safari) {
  1096. css(document.body, 'user-select', '');
  1097. }
  1098. css(dragEl, 'transform', '');
  1099. if (evt) {
  1100. if (moved) {
  1101. evt.cancelable && evt.preventDefault();
  1102. !options.dropBubble && evt.stopPropagation();
  1103. }
  1104. ghostEl && ghostEl.parentNode && ghostEl.parentNode.removeChild(ghostEl);
  1105. if (rootEl === parentEl || (putSortable && putSortable.lastPutMode !== 'clone')) {
  1106. // Remove clone(s)
  1107. cloneEl && cloneEl.parentNode && cloneEl.parentNode.removeChild(cloneEl);
  1108. }
  1109. if (dragEl) {
  1110. if (this.nativeDraggable) {
  1111. off(dragEl, 'dragend', this);
  1112. }
  1113. _disableDraggable(dragEl);
  1114. dragEl.style['will-change'] = '';
  1115. // Remove classes
  1116. // ghostClass is added in dragStarted
  1117. if (moved && !awaitingDragStarted) {
  1118. toggleClass(dragEl, putSortable ? putSortable.options.ghostClass : this.options.ghostClass, false);
  1119. }
  1120. toggleClass(dragEl, this.options.chosenClass, false);
  1121. // Drag stop event
  1122. _dispatchEvent({
  1123. sortable: this,
  1124. name: 'unchoose',
  1125. toEl: parentEl,
  1126. newIndex: null,
  1127. newDraggableIndex: null,
  1128. originalEvent: evt
  1129. });
  1130. if (rootEl !== parentEl) {
  1131. if (newIndex >= 0) {
  1132. // Add event
  1133. _dispatchEvent({
  1134. rootEl: parentEl,
  1135. name: 'add',
  1136. toEl: parentEl,
  1137. fromEl: rootEl,
  1138. originalEvent: evt
  1139. });
  1140. // Remove event
  1141. _dispatchEvent({
  1142. sortable: this,
  1143. name: 'remove',
  1144. toEl: parentEl,
  1145. originalEvent: evt
  1146. });
  1147. // drag from one list and drop into another
  1148. _dispatchEvent({
  1149. rootEl: parentEl,
  1150. name: 'sort',
  1151. toEl: parentEl,
  1152. fromEl: rootEl,
  1153. originalEvent: evt
  1154. });
  1155. _dispatchEvent({
  1156. sortable: this,
  1157. name: 'sort',
  1158. toEl: parentEl,
  1159. originalEvent: evt
  1160. });
  1161. }
  1162. putSortable && putSortable.save();
  1163. } else {
  1164. if (newIndex !== oldIndex) {
  1165. if (newIndex >= 0) {
  1166. // drag & drop within the same list
  1167. _dispatchEvent({
  1168. sortable: this,
  1169. name: 'update',
  1170. toEl: parentEl,
  1171. originalEvent: evt
  1172. });
  1173. _dispatchEvent({
  1174. sortable: this,
  1175. name: 'sort',
  1176. toEl: parentEl,
  1177. originalEvent: evt
  1178. });
  1179. }
  1180. }
  1181. }
  1182. if (Sortable.active) {
  1183. /* jshint eqnull:true */
  1184. if (newIndex == null || newIndex === -1) {
  1185. newIndex = oldIndex;
  1186. newDraggableIndex = oldDraggableIndex;
  1187. }
  1188. _dispatchEvent({
  1189. sortable: this,
  1190. name: 'end',
  1191. toEl: parentEl,
  1192. originalEvent: evt
  1193. });
  1194. // Save sorting
  1195. this.save();
  1196. }
  1197. }
  1198. }
  1199. this._nulling();
  1200. },
  1201. _nulling: function() {
  1202. pluginEvent('nulling', this);
  1203. rootEl =
  1204. dragEl =
  1205. parentEl =
  1206. ghostEl =
  1207. nextEl =
  1208. cloneEl =
  1209. lastDownEl =
  1210. cloneHidden =
  1211. tapEvt =
  1212. touchEvt =
  1213. moved =
  1214. newIndex =
  1215. newDraggableIndex =
  1216. oldIndex =
  1217. oldDraggableIndex =
  1218. lastTarget =
  1219. lastDirection =
  1220. putSortable =
  1221. activeGroup =
  1222. Sortable.dragged =
  1223. Sortable.ghost =
  1224. Sortable.clone =
  1225. Sortable.active = null;
  1226. savedInputChecked.forEach(function (el) {
  1227. el.checked = true;
  1228. });
  1229. savedInputChecked.length =
  1230. lastDx =
  1231. lastDy = 0;
  1232. },
  1233. handleEvent: function (/**Event*/evt) {
  1234. switch (evt.type) {
  1235. case 'drop':
  1236. case 'dragend':
  1237. this._onDrop(evt);
  1238. break;
  1239. case 'dragenter':
  1240. case 'dragover':
  1241. if (dragEl) {
  1242. this._onDragOver(evt);
  1243. _globalDragOver(evt);
  1244. }
  1245. break;
  1246. case 'selectstart':
  1247. evt.preventDefault();
  1248. break;
  1249. }
  1250. },
  1251. /**
  1252. * Serializes the item into an array of string.
  1253. * @returns {String[]}
  1254. */
  1255. toArray: function () {
  1256. let order = [],
  1257. el,
  1258. children = this.el.children,
  1259. i = 0,
  1260. n = children.length,
  1261. options = this.options;
  1262. for (; i < n; i++) {
  1263. el = children[i];
  1264. if (closest(el, options.draggable, this.el, false)) {
  1265. order.push(el.getAttribute(options.dataIdAttr) || _generateId(el));
  1266. }
  1267. }
  1268. return order;
  1269. },
  1270. /**
  1271. * Sorts the elements according to the array.
  1272. * @param {String[]} order order of the items
  1273. */
  1274. sort: function (order) {
  1275. let items = {}, rootEl = this.el;
  1276. this.toArray().forEach(function (id, i) {
  1277. let el = rootEl.children[i];
  1278. if (closest(el, this.options.draggable, rootEl, false)) {
  1279. items[id] = el;
  1280. }
  1281. }, this);
  1282. order.forEach(function (id) {
  1283. if (items[id]) {
  1284. rootEl.removeChild(items[id]);
  1285. rootEl.appendChild(items[id]);
  1286. }
  1287. });
  1288. },
  1289. /**
  1290. * Save the current sorting
  1291. */
  1292. save: function () {
  1293. let store = this.options.store;
  1294. store && store.set && store.set(this);
  1295. },
  1296. /**
  1297. * 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.
  1298. * @param {HTMLElement} el
  1299. * @param {String} [selector] default: `options.draggable`
  1300. * @returns {HTMLElement|null}
  1301. */
  1302. closest: function (el, selector) {
  1303. return closest(el, selector || this.options.draggable, this.el, false);
  1304. },
  1305. /**
  1306. * Set/get option
  1307. * @param {string} name
  1308. * @param {*} [value]
  1309. * @returns {*}
  1310. */
  1311. option: function (name, value) {
  1312. let options = this.options;
  1313. if (value === void 0) {
  1314. return options[name];
  1315. } else {
  1316. let modifiedValue = PluginManager.modifyOption(this, name, value);
  1317. if (typeof modifiedValue !== 'undefined') {
  1318. options[name] = modifiedValue;
  1319. } else {
  1320. options[name] = value;
  1321. }
  1322. if (name === 'group') {
  1323. _prepareGroup(options);
  1324. }
  1325. }
  1326. },
  1327. /**
  1328. * Destroy
  1329. */
  1330. destroy: function () {
  1331. pluginEvent('destroy', this);
  1332. let el = this.el;
  1333. el[expando] = null;
  1334. off(el, 'mousedown', this._onTapStart);
  1335. off(el, 'touchstart', this._onTapStart);
  1336. off(el, 'pointerdown', this._onTapStart);
  1337. if (this.nativeDraggable) {
  1338. off(el, 'dragover', this);
  1339. off(el, 'dragenter', this);
  1340. }
  1341. // Remove draggable attributes
  1342. Array.prototype.forEach.call(el.querySelectorAll('[draggable]'), function (el) {
  1343. el.removeAttribute('draggable');
  1344. });
  1345. this._onDrop();
  1346. this._disableDelayedDragEvents();
  1347. sortables.splice(sortables.indexOf(this.el), 1);
  1348. this.el = el = null;
  1349. },
  1350. _hideClone: function() {
  1351. if (!cloneHidden) {
  1352. pluginEvent('hideClone', this);
  1353. if (Sortable.eventCanceled) return;
  1354. css(cloneEl, 'display', 'none');
  1355. if (this.options.removeCloneOnHide && cloneEl.parentNode) {
  1356. cloneEl.parentNode.removeChild(cloneEl);
  1357. }
  1358. cloneHidden = true;
  1359. }
  1360. },
  1361. _showClone: function(putSortable) {
  1362. if (putSortable.lastPutMode !== 'clone') {
  1363. this._hideClone();
  1364. return;
  1365. }
  1366. if (cloneHidden) {
  1367. pluginEvent('showClone', this);
  1368. if (Sortable.eventCanceled) return;
  1369. // show clone at dragEl or original position
  1370. if (rootEl.contains(dragEl) && !this.options.group.revertClone) {
  1371. rootEl.insertBefore(cloneEl, dragEl);
  1372. } else if (nextEl) {
  1373. rootEl.insertBefore(cloneEl, nextEl);
  1374. } else {
  1375. rootEl.appendChild(cloneEl);
  1376. }
  1377. if (this.options.group.revertClone) {
  1378. this.animate(dragEl, cloneEl);
  1379. }
  1380. css(cloneEl, 'display', '');
  1381. cloneHidden = false;
  1382. }
  1383. }
  1384. };
  1385. function _globalDragOver(/**Event*/evt) {
  1386. if (evt.dataTransfer) {
  1387. evt.dataTransfer.dropEffect = 'move';
  1388. }
  1389. evt.cancelable && evt.preventDefault();
  1390. }
  1391. function onMove(fromEl, toEl, dragEl, dragRect, targetEl, targetRect, originalEvent, willInsertAfter) {
  1392. let evt,
  1393. sortable = fromEl[expando],
  1394. onMoveFn = sortable.options.onMove,
  1395. retVal;
  1396. // Support for new CustomEvent feature
  1397. if (window.CustomEvent && !IE11OrLess && !Edge) {
  1398. evt = new CustomEvent('move', {
  1399. bubbles: true,
  1400. cancelable: true
  1401. });
  1402. } else {
  1403. evt = document.createEvent('Event');
  1404. evt.initEvent('move', true, true);
  1405. }
  1406. evt.to = toEl;
  1407. evt.from = fromEl;
  1408. evt.dragged = dragEl;
  1409. evt.draggedRect = dragRect;
  1410. evt.related = targetEl || toEl;
  1411. evt.relatedRect = targetRect || getRect(toEl);
  1412. evt.willInsertAfter = willInsertAfter;
  1413. evt.originalEvent = originalEvent;
  1414. fromEl.dispatchEvent(evt);
  1415. if (onMoveFn) {
  1416. retVal = onMoveFn.call(sortable, evt, originalEvent);
  1417. }
  1418. return retVal;
  1419. }
  1420. function _disableDraggable(el) {
  1421. el.draggable = false;
  1422. }
  1423. function _unsilent() {
  1424. _silent = false;
  1425. }
  1426. function _ghostIsLast(evt, vertical, sortable) {
  1427. let rect = getRect(lastChild(sortable.el, sortable.options.draggable));
  1428. const spacer = 10;
  1429. return vertical ?
  1430. (evt.clientX > rect.right + spacer || evt.clientX <= rect.right && evt.clientY > rect.bottom && evt.clientX >= rect.left) :
  1431. (evt.clientX > rect.right && evt.clientY > rect.top || evt.clientX <= rect.right && evt.clientY > rect.bottom + spacer);
  1432. }
  1433. function _getSwapDirection(evt, target, targetRect, vertical, swapThreshold, invertedSwapThreshold, invertSwap, isLastTarget) {
  1434. let mouseOnAxis = vertical ? evt.clientY : evt.clientX,
  1435. targetLength = vertical ? targetRect.height : targetRect.width,
  1436. targetS1 = vertical ? targetRect.top : targetRect.left,
  1437. targetS2 = vertical ? targetRect.bottom : targetRect.right,
  1438. invert = false;
  1439. if (!invertSwap) {
  1440. // Never invert or create dragEl shadow when target movemenet causes mouse to move past the end of regular swapThreshold
  1441. if (isLastTarget && targetMoveDistance < targetLength * swapThreshold) { // multiplied only by swapThreshold because mouse will already be inside target by (1 - threshold) * targetLength / 2
  1442. // check if past first invert threshold on side opposite of lastDirection
  1443. if (!pastFirstInvertThresh &&
  1444. (lastDirection === 1 ?
  1445. (
  1446. mouseOnAxis > targetS1 + targetLength * invertedSwapThreshold / 2
  1447. ) :
  1448. (
  1449. mouseOnAxis < targetS2 - targetLength * invertedSwapThreshold / 2
  1450. )
  1451. )
  1452. )
  1453. {
  1454. // past first invert threshold, do not restrict inverted threshold to dragEl shadow
  1455. pastFirstInvertThresh = true;
  1456. }
  1457. if (!pastFirstInvertThresh) {
  1458. // dragEl shadow (target move distance shadow)
  1459. if (
  1460. lastDirection === 1 ?
  1461. (
  1462. mouseOnAxis < targetS1 + targetMoveDistance // over dragEl shadow
  1463. ) :
  1464. (
  1465. mouseOnAxis > targetS2 - targetMoveDistance
  1466. )
  1467. )
  1468. {
  1469. return -lastDirection;
  1470. }
  1471. } else {
  1472. invert = true;
  1473. }
  1474. } else {
  1475. // Regular
  1476. if (
  1477. mouseOnAxis > targetS1 + (targetLength * (1 - swapThreshold) / 2) &&
  1478. mouseOnAxis < targetS2 - (targetLength * (1 - swapThreshold) / 2)
  1479. ) {
  1480. return _getInsertDirection(target);
  1481. }
  1482. }
  1483. }
  1484. invert = invert || invertSwap;
  1485. if (invert) {
  1486. // Invert of regular
  1487. if (
  1488. mouseOnAxis < targetS1 + (targetLength * invertedSwapThreshold / 2) ||
  1489. mouseOnAxis > targetS2 - (targetLength * invertedSwapThreshold / 2)
  1490. )
  1491. {
  1492. return ((mouseOnAxis > targetS1 + targetLength / 2) ? 1 : -1);
  1493. }
  1494. }
  1495. return 0;
  1496. }
  1497. /**
  1498. * Gets the direction dragEl must be swapped relative to target in order to make it
  1499. * seem that dragEl has been "inserted" into that element's position
  1500. * @param {HTMLElement} target The target whose position dragEl is being inserted at
  1501. * @return {Number} Direction dragEl must be swapped
  1502. */
  1503. function _getInsertDirection(target) {
  1504. if (index(dragEl) < index(target)) {
  1505. return 1;
  1506. } else {
  1507. return -1;
  1508. }
  1509. }
  1510. /**
  1511. * Generate id
  1512. * @param {HTMLElement} el
  1513. * @returns {String}
  1514. * @private
  1515. */
  1516. function _generateId(el) {
  1517. let str = el.tagName + el.className + el.src + el.href + el.textContent,
  1518. i = str.length,
  1519. sum = 0;
  1520. while (i--) {
  1521. sum += str.charCodeAt(i);
  1522. }
  1523. return sum.toString(36);
  1524. }
  1525. function _saveInputCheckedState(root) {
  1526. savedInputChecked.length = 0;
  1527. let inputs = root.getElementsByTagName('input');
  1528. let idx = inputs.length;
  1529. while (idx--) {
  1530. let el = inputs[idx];
  1531. el.checked && savedInputChecked.push(el);
  1532. }
  1533. }
  1534. function _nextTick(fn) {
  1535. return setTimeout(fn, 0);
  1536. }
  1537. function _cancelNextTick(id) {
  1538. return clearTimeout(id);
  1539. }
  1540. // Fixed #973:
  1541. if (documentExists) {
  1542. on(document, 'touchmove', function(evt) {
  1543. if ((Sortable.active || awaitingDragStarted) && evt.cancelable) {
  1544. evt.preventDefault();
  1545. }
  1546. });
  1547. }
  1548. // Export utils
  1549. Sortable.utils = {
  1550. on: on,
  1551. off: off,
  1552. css: css,
  1553. find: find,
  1554. is: function (el, selector) {
  1555. return !!closest(el, selector, el, false);
  1556. },
  1557. extend: extend,
  1558. throttle: throttle,
  1559. closest: closest,
  1560. toggleClass: toggleClass,
  1561. clone: clone,
  1562. index: index,
  1563. nextTick: _nextTick,
  1564. cancelNextTick: _cancelNextTick,
  1565. detectDirection: _detectDirection,
  1566. getChild: getChild
  1567. };
  1568. /**
  1569. * Get the Sortable instance of an element
  1570. * @param {HTMLElement} element The element
  1571. * @return {Sortable|undefined} The instance of Sortable
  1572. */
  1573. Sortable.get = function(element) {
  1574. return element[expando];
  1575. };
  1576. /**
  1577. * Mount a plugin to Sortable
  1578. * @param {...SortablePlugin|SortablePlugin[]} plugins Plugins being mounted
  1579. */
  1580. Sortable.mount = function(...plugins) {
  1581. if (plugins[0].constructor === Array) plugins = plugins[0];
  1582. plugins.forEach((plugin) => {
  1583. if (!plugin.prototype || !plugin.prototype.constructor) {
  1584. throw `Sortable: Mounted plugin must be a constructor function, not ${ {}.toString.call(plugin) }`;
  1585. }
  1586. if (plugin.utils) Sortable.utils = { ...Sortable.utils, ...plugin.utils };
  1587. PluginManager.mount(plugin);
  1588. });
  1589. };
  1590. /**
  1591. * Create sortable instance
  1592. * @param {HTMLElement} el
  1593. * @param {Object} [options]
  1594. */
  1595. Sortable.create = function (el, options) {
  1596. return new Sortable(el, options);
  1597. };
  1598. // Export
  1599. Sortable.version = version;
  1600. export default Sortable;