Sortable.js

Sortable — is a JavaScript library for reorderable drag-and-drop lists on modern browsers and touch devices. No jQuery. Supports Meteor, AngularJS, React, Polymer, Knockout and any CSS library, e.g. Bootstrap. http://rubaxa.github.io/Sortable/

此腳本不應該直接安裝,它是一個供其他腳本使用的函式庫。欲使用本函式庫,請在腳本 metadata 寫上: // @require https://update.gf.qytechs.cn/scripts/36108/235503/Sortablejs.js

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

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址