Sortable.js 34 KB

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