Sortable.js 32 KB

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