sortable-dnd
Advanced tools
Comparing version 0.0.6 to 0.0.7
/*! | ||
* sortable-dnd v0.0.6 | ||
* sortable-dnd v0.0.7 | ||
* open source under the MIT license | ||
@@ -84,6 +84,20 @@ * https://github.com/mfuu/sortable-dnd#readme | ||
}; | ||
var R_SPACE = /\s+/g; | ||
var utils = { | ||
/** | ||
* add specified event listener | ||
* @param {HTMLElement} el | ||
* @param {String} event | ||
* @param {Function} fn | ||
*/ | ||
on: function on(el, event, fn) { | ||
el.addEventListener(event, fn, !IE11OrLess && captureMode); | ||
}, | ||
/** | ||
* remove specified event listener | ||
* @param {HTMLElement} el | ||
* @param {String} event | ||
* @param {Function} fn | ||
*/ | ||
off: function off(el, event, fn) { | ||
@@ -101,2 +115,9 @@ el.removeEventListener(event, fn, !IE11OrLess && captureMode); | ||
}, | ||
/** | ||
* get specified element's index in group | ||
* @param {HTMLElement} group | ||
* @param {HTMLElement} el | ||
* @returns {Number} index | ||
*/ | ||
index: function index(group, el) { | ||
@@ -109,8 +130,45 @@ if (!el || !el.parentNode) return -1; | ||
}, | ||
getRect: function getRect(children, index) { | ||
if (!children.length) return {}; | ||
if (index < 0) return {}; | ||
return children[index].getBoundingClientRect(); | ||
/** | ||
* Returns the "bounding client rect" of given element | ||
* @param {HTMLElement} el The element whose boundingClientRect is wanted | ||
*/ | ||
getRect: function getRect(el) { | ||
if (!el.getBoundingClientRect && el !== window) return; | ||
var rect = { | ||
top: 0, | ||
left: 0, | ||
bottom: 0, | ||
right: 0, | ||
height: 0, | ||
width: 0 | ||
}; | ||
var elRect; | ||
if (el !== window && el.parentNode && el !== this.getWindowScrollingElement()) { | ||
elRect = el.getBoundingClientRect(); | ||
rect.top = elRect.top; | ||
rect.left = elRect.left; | ||
rect.bottom = elRect.bottom; | ||
rect.right = elRect.right; | ||
rect.height = elRect.height; | ||
rect.width = elRect.width; | ||
} else { | ||
rect.top = 0; | ||
rect.left = 0; | ||
rect.bottom = window.innerHeight; | ||
rect.right = window.innerWidth; | ||
rect.height = window.innerHeight; | ||
rect.width = window.innerWidth; | ||
} | ||
return rect; | ||
}, | ||
getElement: function getElement(group, dragging) { | ||
/** | ||
* get target Element in group | ||
* @param {HTMLElement} group | ||
* @param {HTMLElement} el | ||
*/ | ||
getElement: function getElement(group, el) { | ||
var result = { | ||
@@ -125,14 +183,14 @@ index: -1, | ||
var index = children.indexOf(dragging); | ||
var index = children.indexOf(el); | ||
if (index > -1) Object.assign(result, { | ||
index: index, | ||
el: children[index], | ||
rect: children[index].getBoundingClientRect() | ||
rect: this.getRect(children[index]) | ||
}); // children 中无法直接找到对应的dom时,需要向下寻找 | ||
for (var i = 0; i < children.length; i++) { | ||
if (this.isChildOf(dragging, children[i])) Object.assign(result, { | ||
if (this.isChildOf(el, children[i])) Object.assign(result, { | ||
index: i, | ||
el: children[i], | ||
rect: children[i].getBoundingClientRect() | ||
rect: this.getRect(children[i]) | ||
}); | ||
@@ -143,3 +201,9 @@ } | ||
}, | ||
// 判断子元素是否包含在父元素中 | ||
/** | ||
* Check if child element is contained in parent element | ||
* @param {HTMLElement} child | ||
* @param {HTMLElement} parent | ||
* @returns {Boolean} true | false | ||
*/ | ||
isChildOf: function isChildOf(child, parent) { | ||
@@ -159,7 +223,51 @@ var parentNode; | ||
}, | ||
/** | ||
* add or remove element's class | ||
* @param {HTMLElement} el element | ||
* @param {String} name class name | ||
* @param {Boolean} state true: add, false: remove | ||
*/ | ||
toggleClass: function toggleClass(el, name, state) { | ||
if (el && name) { | ||
if (el.classList) { | ||
el.classList[state ? 'add' : 'remove'](name); | ||
} else { | ||
var className = (' ' + el.className + ' ').replace(R_SPACE, ' ').replace(' ' + name + ' ', ' '); | ||
el.className = (className + (state ? ' ' + name : '')).replace(R_SPACE, ' '); | ||
} | ||
} | ||
}, | ||
/** | ||
* Check if a DOM element matches a given selector | ||
* @param {HTMLElement} el | ||
* @param {String} selector | ||
* @returns | ||
*/ | ||
matches: function matches(el, selector) { | ||
if (!selector) return; | ||
selector[0] === '>' && (selector = selector.substring(1)); | ||
if (el) { | ||
try { | ||
if (el.matches) { | ||
return el.matches(selector); | ||
} else if (el.msMatchesSelector) { | ||
return el.msMatchesSelector(selector); | ||
} else if (el.webkitMatchesSelector) { | ||
return el.webkitMatchesSelector(selector); | ||
} | ||
} catch (error) { | ||
return false; | ||
} | ||
} | ||
return false; | ||
}, | ||
animate: function animate(el, preRect) { | ||
var _this = this; | ||
var animation = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 300; | ||
var curRect = el.getBoundingClientRect(); | ||
var animation = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 150; | ||
var curRect = this.getRect(el); | ||
var left = preRect.left - curRect.left; | ||
@@ -216,2 +324,5 @@ var top = preRect.top - curRect.top; | ||
}; | ||
}, | ||
_nextTick: function _nextTick(fn) { | ||
return setTimeout(fn, 0); | ||
} | ||
@@ -268,2 +379,3 @@ }; | ||
var Ghost = /*#__PURE__*/function () { | ||
@@ -344,38 +456,9 @@ function Ghost(options) { | ||
/** | ||
* @interface Options { | ||
* | ||
* group: HTMLElement, | ||
* | ||
* draggable?: Function, return element node selected when dragging, or null | ||
* | ||
* dragEnd?: Function, The callback function when the drag is completed | ||
* | ||
* ghostStyle?: Object, | ||
* | ||
* ghostClass?: String, | ||
* | ||
* } | ||
*/ | ||
var Sortable = /*#__PURE__*/function () { | ||
function Sortable(options) { | ||
function Sortable(el, options) { | ||
_classCallCheck(this, Sortable); | ||
this.group = options.group; // 父级元素 | ||
this.$el = el; // 列表容器元素 | ||
this.dragging = options.dragging; // 必须为函数且必须返回一个 HTMLElement (e) => return e.target | ||
this.dragEnd = options.dragEnd; // 拖拽完成时的回调函数,返回两个值(olddom, newdom) => {} | ||
this.ghostStyle = options.ghostStyle; // 克隆元素包含的属性 | ||
this.ghostClass = options.ghostClass; // 克隆元素的类名 | ||
this.animation = options.animation || 300; // 动画延迟 | ||
this.isMousedown = false; // 记录鼠标按下 | ||
this.isMousemove = false; // 记录鼠标移动 | ||
this.options = options = Object.assign({}, options); | ||
this.dragEl = null; // 拖拽元素 | ||
@@ -385,13 +468,11 @@ | ||
this.diff = new Diff(); // 记录拖拽前后差异 | ||
this.diff = null; // 记录拖拽前后差异 | ||
this.ghost = new Ghost({ | ||
ghostClass: this.ghostClass, | ||
ghostStyle: this.ghostStyle | ||
}); | ||
this.supportPointer = 'PointerEvent' in window && !Safari; | ||
this.ghost = null; // 拖拽时蒙版元素 | ||
this.calcXY = { | ||
x: 0, | ||
y: 0 | ||
}; | ||
}; // 记录拖拽移动时坐标 | ||
utils.debounce(this.init(), 50); // 避免重复执行多次 | ||
@@ -403,7 +484,30 @@ } | ||
value: function init() { | ||
if (!this.group) { | ||
console.error('Error: group is required'); | ||
if (!this.$el) { | ||
console.error('Error: container element is required'); | ||
return; | ||
} | ||
var defaults = { | ||
animation: 150, | ||
// 动画延时 | ||
ghostClass: '', | ||
ghostStyle: {}, | ||
chosenClass: '', | ||
draggable: '', | ||
// String: class, Function: (e) => return true | ||
dragging: null, | ||
// 必须为函数且必须返回一个 HTMLElement (e) => return e.target | ||
dragEnd: null, | ||
// 拖拽完成时的回调函数,返回两个值(olddom, newdom) => {} | ||
supportPointer: 'PointerEvent' in window && !Safari, | ||
ownerDocument: this.$el.ownerDocument | ||
}; // Set default options | ||
for (var name in defaults) { | ||
!(name in this.options) && (this.options[name] = defaults[name]); | ||
} | ||
this.diff = new Diff(); | ||
this.ghost = new Ghost(this.options); | ||
this._bindEventListener(); | ||
@@ -419,15 +523,59 @@ } | ||
}, { | ||
key: "_bindEventListener", | ||
value: function _bindEventListener() { | ||
this._onStart = this._onStart.bind(this); | ||
this._onMove = this._onMove.bind(this); | ||
this._onDrop = this._onDrop.bind(this); | ||
var supportPointer = this.options.supportPointer; | ||
if (supportPointer) { | ||
utils.on(this.$el, 'pointerdown', this._onStart); | ||
} else { | ||
utils.on(this.$el, 'mousedown', this._onStart); | ||
utils.on(this.$el, 'touchstart', this._onStart); | ||
} | ||
} | ||
}, { | ||
key: "_unbindEventListener", | ||
value: function _unbindEventListener() { | ||
utils.off(this.$el, 'pointerdown', this._onStart); | ||
utils.off(this.$el, 'touchstart', this._onStart); | ||
utils.off(this.$el, 'mousedown', this._onStart); | ||
} | ||
}, { | ||
key: "_onStart", | ||
value: function _onStart(e) { | ||
if (e.button !== 0) return true; | ||
if (e.target === this.group) return true; | ||
value: function _onStart(evt) { | ||
var _this$options2 = this.options, | ||
dragging = _this$options2.dragging, | ||
draggable = _this$options2.draggable; | ||
var touch = evt.touches && evt.touches[0] || evt.pointerType && evt.pointerType === 'touch' && evt; | ||
var target = (touch || evt).target; | ||
if (typeof draggable === 'function') { | ||
if (!draggable(touch || evt)) return true; | ||
} else if (draggable) { | ||
if (!utils.matches(target, draggable)) return true; | ||
} | ||
if (/mousedown|pointerdown/.test(evt.type) && evt.button !== 0) return; // only left button and enabled | ||
if (target === this.$el) return true; | ||
try { | ||
// 获取拖拽元素 | ||
var element = this.dragging ? this.dragging(e) : e.target; // 不存在拖拽元素时不允许拖拽 | ||
if (document.selection) { | ||
// Timeout neccessary for IE9 | ||
utils._nextTick(function () { | ||
document.selection.empty(); | ||
}); | ||
} else { | ||
window.getSelection().removeAllRanges(); | ||
} // 获取拖拽元素 | ||
var element = dragging && typeof dragging === 'function' ? dragging(touch || evt) : target; // 不存在拖拽元素时不允许拖拽 | ||
if (!element) return true; | ||
if (element.animated) return; | ||
this.dragEl = element; | ||
} catch (e) { | ||
} catch (err) { | ||
// | ||
@@ -437,5 +585,5 @@ return true; | ||
this.isMousedown = true; // 获取当前元素在列表中的位置 | ||
window.sortableDndOnDown = true; // 获取当前元素在列表中的位置 | ||
var _utils$getElement = utils.getElement(this.group, this.dragEl), | ||
var _utils$getElement = utils.getElement(this.$el, this.dragEl), | ||
index = _utils$getElement.index, | ||
@@ -449,29 +597,35 @@ el = _utils$getElement.el, | ||
this.ghost.init(ghostEl, rect); | ||
this.ghost.set('x', rect.left); | ||
this.ghost.set('y', rect.top); | ||
this.diff.old.rect = rect; | ||
this.ghost.set('x', rect.left); | ||
this.ghost.set('y', rect.top); // 记录拖拽移动时坐标 | ||
this.calcXY = { | ||
x: e.clientX, | ||
y: e.clientY | ||
x: (touch || evt).clientX, | ||
y: (touch || evt).clientY | ||
}; | ||
this._onMoveEvents(); | ||
this._onMoveEvents(touch); | ||
this._onUpEvents(); | ||
this._onUpEvents(touch); | ||
} | ||
}, { | ||
key: "_onMove", | ||
value: function _onMove(e) { | ||
value: function _onMove(evt) { | ||
evt.preventDefault(); | ||
var touch = evt.touches && evt.touches[0]; | ||
var e = touch || evt; | ||
var clientX = e.clientX, | ||
clientY = e.clientY; | ||
var target = touch ? document.elementFromPoint(clientX, clientY) : e.target; | ||
var chosenClass = this.options.chosenClass; | ||
utils.toggleClass(this.dragEl, chosenClass, true); | ||
this.ghost.move(); | ||
e.preventDefault(); | ||
if (!this.isMousedown) return; | ||
if (e.clientX < 0 || e.clientY < 0) return; | ||
if (!window.sortableDndOnDown) return; | ||
if (clientX < 0 || clientY < 0) return; | ||
document.body.style.cursor = 'grabbing'; | ||
this.isMousemove = true; | ||
this.ghost.set('x', this.ghost.x + e.clientX - this.calcXY.x); | ||
this.ghost.set('y', this.ghost.y + e.clientY - this.calcXY.y); | ||
window.sortableDndOnMove = true; | ||
this.ghost.set('x', this.ghost.x + clientX - this.calcXY.x); | ||
this.ghost.set('y', this.ghost.y + clientY - this.calcXY.y); | ||
this.calcXY = { | ||
x: e.clientX, | ||
y: e.clientY | ||
x: clientX, | ||
y: clientY | ||
}; | ||
@@ -482,3 +636,3 @@ this.ghost.move(); | ||
var _utils$getElement2 = utils.getElement(this.group, e.target), | ||
var _utils$getElement2 = utils.getElement(this.$el, target), | ||
index = _utils$getElement2.index, | ||
@@ -492,17 +646,16 @@ el = _utils$getElement2.el, | ||
bottom = rect.bottom; | ||
if (!el || index < 0) return; | ||
if (top < 0 || top - this.ghost.rect.height / 3 < 0) return; | ||
if (!el || index < 0 || top < 0) return; | ||
if (e.clientX > left && e.clientX < right && e.clientY > top && e.clientY < bottom) { | ||
if (clientX > left && clientX < right && clientY > top && clientY < bottom) { | ||
this.dropEl = el; // 拖拽前后元素不一致时交换 | ||
if (this.dropEl !== this.dragEl) { | ||
var dragRect = this.dragEl.getBoundingClientRect(); | ||
var dropRect = this.dropEl.getBoundingClientRect(); | ||
if (this.dropEl.animated) return; | ||
var dragRect = utils.getRect(this.dragEl); | ||
var dropRect = utils.getRect(this.dropEl); | ||
if (utils.index(this.group, this.dragEl) < index) { | ||
this.group.insertBefore(this.dragEl, this.dropEl.nextElementSibling); | ||
if (utils.index(this.$el, this.dragEl) < index) { | ||
this.$el.insertBefore(this.dragEl, this.dropEl.nextElementSibling); | ||
} else { | ||
this.group.insertBefore(this.dragEl, this.dropEl); | ||
this.$el.insertBefore(this.dragEl, this.dropEl); | ||
} // 设置动画 | ||
@@ -517,3 +670,3 @@ | ||
this.diff["new"].rect = this.dropEl.getBoundingClientRect(); | ||
this.diff["new"].rect = utils.getRect(this.dropEl); | ||
} | ||
@@ -529,12 +682,16 @@ } | ||
document.body.style.cursor = ''; | ||
var _this$options3 = this.options, | ||
dragEnd = _this$options3.dragEnd, | ||
chosenClass = _this$options3.chosenClass; | ||
utils.toggleClass(this.dragEl, chosenClass, false); | ||
if (this.isMousedown && this.isMousemove) { | ||
if (window.sortableDndOnDown && window.sortableDndOnMove) { | ||
// 拖拽完成触发回调函数 | ||
if (this.dragEnd && typeof this.dragEnd === 'function') this.dragEnd(this.diff.old, this.diff["new"]); | ||
if (dragEnd && typeof dragEnd === 'function') dragEnd(this.diff.old, this.diff["new"]); | ||
} | ||
this.isMousedown = false; | ||
this.isMousemove = false; | ||
this.diff.destroy(); | ||
this.ghost.destroy(); | ||
this._removeWindowState(); | ||
} | ||
@@ -544,10 +701,11 @@ }, { | ||
value: function _checkRange(e) { | ||
var _this$group$getBoundi = this.group.getBoundingClientRect(), | ||
top = _this$group$getBoundi.top, | ||
left = _this$group$getBoundi.left, | ||
right = _this$group$getBoundi.right, | ||
bottom = _this$group$getBoundi.bottom; | ||
var _utils$getRect = utils.getRect(this.$el), | ||
top = _utils$getRect.top, | ||
left = _utils$getRect.left, | ||
right = _utils$getRect.right, | ||
bottom = _utils$getRect.bottom; | ||
if (e.clientX < left || e.clientX > right || e.clientY < top || e.clientY > bottom) { | ||
document.body.style.cursor = 'not-allowed'; | ||
return; | ||
} | ||
@@ -558,29 +716,30 @@ } | ||
value: function _resetState() { | ||
this.isMousedown = false; | ||
this.isMousemove = false; | ||
this.dragEl = null; | ||
this.dropEl = null; | ||
this.ghost.destroy(); | ||
this.diff = new Diff(); | ||
this.diff.destroy(); | ||
this._removeWindowState(); | ||
} | ||
}, { | ||
key: "_bindEventListener", | ||
value: function _bindEventListener() { | ||
this._onStart = this._onStart.bind(this); | ||
this._onMove = this._onMove.bind(this); | ||
this._onDrop = this._onDrop.bind(this); | ||
if (this.supportPointer) { | ||
utils.on(this.group, 'pointerdown', this._onStart); | ||
} else { | ||
utils.on(this.group, 'mousedown', this._onStart); | ||
} | ||
key: "_removeWindowState", | ||
value: function _removeWindowState() { | ||
window.sortableDndOnDown = null; | ||
window.sortableDndOnMove = null; | ||
delete window.sortableDndOnDown; | ||
delete window.sortableDndOnMove; | ||
} | ||
}, { | ||
key: "_onMoveEvents", | ||
value: function _onMoveEvents() { | ||
if (this.supportPointer) { | ||
utils.on(document, 'pointermove', this._onMove); | ||
value: function _onMoveEvents(touch) { | ||
var _this$options4 = this.options, | ||
supportPointer = _this$options4.supportPointer, | ||
ownerDocument = _this$options4.ownerDocument; | ||
if (supportPointer) { | ||
utils.on(ownerDocument, 'pointermove', this._onMove); | ||
} else if (touch) { | ||
utils.on(ownerDocument, 'touchmove', this._onMove); | ||
} else { | ||
utils.on(document, 'mousemove', this._onMove); | ||
utils.on(ownerDocument, 'mousemove', this._onMove); | ||
} | ||
@@ -591,19 +750,15 @@ } | ||
value: function _onUpEvents() { | ||
if (this.supportPointer) { | ||
utils.on(document, 'pointerup', this._onDrop); | ||
} else { | ||
utils.on(document, 'mouseup', this._onDrop); | ||
} | ||
var ownerDocument = this.options.ownerDocument; | ||
utils.on(ownerDocument, 'pointerup', this._onDrop); | ||
utils.on(ownerDocument, 'touchend', this._onDrop); | ||
utils.on(ownerDocument, 'touchcancel', this._onDrop); | ||
utils.on(ownerDocument, 'mouseup', this._onDrop); | ||
} | ||
}, { | ||
key: "_unbindEventListener", | ||
value: function _unbindEventListener() { | ||
utils.off(this.group, 'mousedown', this._onStart); | ||
utils.off(this.group, 'pointerdown', this._onStart); | ||
} | ||
}, { | ||
key: "_offMoveEvents", | ||
value: function _offMoveEvents() { | ||
utils.off(document, 'mousemove', this._onMove); | ||
utils.off(document, 'pointermove', this._onMove); | ||
var ownerDocument = this.options.ownerDocument; | ||
utils.off(ownerDocument, 'pointermove', this._onMove); | ||
utils.off(ownerDocument, 'touchmove', this._onMove); | ||
utils.off(ownerDocument, 'mousemove', this._onMove); | ||
} | ||
@@ -613,4 +768,7 @@ }, { | ||
value: function _offUpEvents() { | ||
utils.off(document, 'mouseup', this._onDrop); | ||
utils.off(document, 'pointerup', this._onDrop); | ||
var ownerDocument = this.options.ownerDocument; | ||
utils.off(ownerDocument, 'mouseup', this._onDrop); | ||
utils.off(ownerDocument, 'touchend', this._onDrop); | ||
utils.off(ownerDocument, 'touchcancel', this._onDrop); | ||
utils.off(ownerDocument, 'pointerup', this._onDrop); | ||
} | ||
@@ -617,0 +775,0 @@ }]); |
@@ -1,1 +0,1 @@ | ||
!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):(t="undefined"!=typeof globalThis?globalThis:t||self).sortable=e()}(this,function(){"use strict";function n(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}function i(t,e){for(var n=0;n<e.length;n++){var i=e[n];i.enumerable=i.enumerable||!1,i.configurable=!0,"value"in i&&(i.writable=!0),Object.defineProperty(t,i.key,i)}}function o(t,e,n){e&&i(t.prototype,e),n&&i(t,n),Object.defineProperty(t,"prototype",{writable:!1})}function s(t){return function(t){if(Array.isArray(t))return r(t)}(t)||function(t){if("undefined"!=typeof Symbol&&null!=t[Symbol.iterator]||null!=t["@@iterator"])return Array.from(t)}(t)||function(t,e){if(t){if("string"==typeof t)return r(t,e);var n=Object.prototype.toString.call(t).slice(8,-1);return"Map"===(n="Object"===n&&t.constructor?t.constructor.name:n)||"Set"===n?Array.from(t):"Arguments"===n||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)?r(t,e):void 0}}(t)||function(){throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}()}function r(t,e){(null==e||e>t.length)&&(e=t.length);for(var n=0,i=new Array(e);n<e;n++)i[n]=t[n];return i}function t(t){if("undefined"!=typeof window&&window.navigator)return!!navigator.userAgent.match(t)}var l=t(/(?:Trident.*rv[ :]?11\.|msie|iemobile|Windows Phone)/i),h=t(/safari/i)&&!t(/chrome/i)&&!t(/android/i),u={capture:!1,passive:!1},a={on:function(t,e,n){t.addEventListener(e,n,!l&&u)},off:function(t,e,n){t.removeEventListener(e,n,!l&&u)},getWindowScrollingElement:function(){var t=document.scrollingElement;return t||document.documentElement},index:function(t,e){return e&&e.parentNode?s(Array.from(t.children)).indexOf(e):-1},getRect:function(t,e){return!t.length||e<0?{}:t[e].getBoundingClientRect()},getElement:function(t,e){var n={index:-1,el:null,rect:{}},i=s(Array.from(t.children)),t=i.indexOf(e);-1<t&&Object.assign(n,{index:t,el:i[t],rect:i[t].getBoundingClientRect()});for(var o=0;o<i.length;o++)this.isChildOf(e,i[o])&&Object.assign(n,{index:o,el:i[o],rect:i[o].getBoundingClientRect()});return n},isChildOf:function(t,e){var n;if(t&&e)for(n=t.parentNode;n;){if(e===n)return!0;n=n.parentNode}return!1},animate:function(t,e){var n=this,i=2<arguments.length&&void 0!==arguments[2]?arguments[2]:300,o=t.getBoundingClientRect(),s=e.left-o.left,e=e.top-o.top;this.css(t,"transition","none"),this.css(t,"transform","translate3d(".concat(s,"px, ").concat(e,"px, 0)")),t.offsetLeft,this.css(t,"transition","all ".concat(i,"ms")),this.css(t,"transform","translate3d(0px, 0px, 0px)"),clearTimeout(t.animated),t.animated=setTimeout(function(){n.css(t,"transition",""),n.css(t,"transform",""),t.animated=null},i)},css:function(t,e,n){var i=t&&t.style;if(i){if(void 0===n)return document.defaultView&&document.defaultView.getComputedStyle?n=document.defaultView.getComputedStyle(t,""):t.currentStyle&&(n=t.currentStyle),void 0===e?n:n[e];i[e=e in i||-1!==e.indexOf("webkit")?e:"-webkit-"+e]=n+("string"==typeof n?"":"px")}},debounce:function(o,s){return function(){for(var t=this,e=arguments.length,n=new Array(e),i=0;i<e;i++)n[i]=arguments[i];clearTimeout(o.id),o.id=setTimeout(function(){o.call.apply(o,[t].concat(n))},s)}}},d=function(){function t(){n(this,t),this.old={node:null,rect:{}},this.new={node:null,rect:{}}}return o(t,[{key:"get",value:function(t){return this[t]}},{key:"set",value:function(t,e){this[t]=e}},{key:"destroy",value:function(){this.old={node:null,rect:{}},this.new={node:null,rect:{}}}}]),t}(),c=function(){function e(t){n(this,e),this.options=t,this.x=0,this.y=0,this.exist=!1}return o(e,[{key:"init",value:function(t,e){if(t){this.$el=t,this.rect=e;var n,t=this.options,i=t.ghostClass,t=t.ghostStyle,o=void 0===t?{}:t,t=e.width,e=e.height;for(n in this.$el.class=i,this.$el.style.width=t+"px",this.$el.style.height=e+"px",this.$el.style.transform="",this.$el.style.transition="",this.$el.style.position="fixed",this.$el.style.left=0,this.$el.style.top=0,this.$el.style.zIndex=1e5,this.$el.style.opacity=.8,this.$el.style.pointerEvents="none",o)a.css(this.$el,n,o[n])}else console.error("Ghost Element is required")}},{key:"get",value:function(t){return this[t]}},{key:"set",value:function(t,e){this[t]=e,this[t]=e}},{key:"move",value:function(){this.exist||(document.body.appendChild(this.$el),this.exist=!0),this.$el.style.transform="translate3d(".concat(this.x,"px, ").concat(this.y,"px, 0)")}},{key:"destroy",value:function(){this.$el&&this.$el.remove(),this.exist=!1}}]),e}();return function(){function e(t){n(this,e),this.group=t.group,this.dragging=t.dragging,this.dragEnd=t.dragEnd,this.ghostStyle=t.ghostStyle,this.ghostClass=t.ghostClass,this.animation=t.animation||300,this.isMousedown=!1,this.isMousemove=!1,this.dragEl=null,this.dropEl=null,this.diff=new d,this.ghost=new c({ghostClass:this.ghostClass,ghostStyle:this.ghostStyle}),this.supportPointer="PointerEvent"in window&&!h,this.calcXY={x:0,y:0},a.debounce(this.init(),50)}return o(e,[{key:"init",value:function(){this.group?this._bindEventListener():console.error("Error: group is required")}},{key:"destroy",value:function(){this._unbindEventListener(),this._resetState()}},{key:"_onStart",value:function(t){if(0!==t.button)return!0;if(t.target===this.group)return!0;try{var e=this.dragging?this.dragging(t):t.target;if(!e)return!0;if(e.animated)return;this.dragEl=e}catch(t){return!0}this.isMousedown=!0;var e=a.getElement(this.group,this.dragEl),n=e.index,i=e.el,e=e.rect;if(!i||n<0)return!0;i=this.dragEl.cloneNode(!0);this.ghost.init(i,e),this.diff.old.rect=e,this.ghost.set("x",e.left),this.ghost.set("y",e.top),this.calcXY={x:t.clientX,y:t.clientY},this._onMoveEvents(),this._onUpEvents()}},{key:"_onMove",value:function(t){if(this.ghost.move(),t.preventDefault(),this.isMousedown&&!(t.clientX<0||t.clientY<0)){document.body.style.cursor="grabbing",this.isMousemove=!0,this.ghost.set("x",this.ghost.x+t.clientX-this.calcXY.x),this.ghost.set("y",this.ghost.y+t.clientY-this.calcXY.y),this.calcXY={x:t.clientX,y:t.clientY},this.ghost.move(),this._checkRange(t);var e=a.getElement(this.group,t.target),n=e.index,i=e.el,e=e.rect,o=e.left,s=e.right,r=e.top,e=e.bottom;if(i&&!(n<0)&&!(r<0||r-this.ghost.rect.height/3<0)&&t.clientX>o&&t.clientX<s&&t.clientY>r&&t.clientY<e){if(this.dropEl=i,this.dropEl!==this.dragEl){o=this.dragEl.getBoundingClientRect(),s=this.dropEl.getBoundingClientRect();if(this.dropEl.animated)return;a.index(this.group,this.dragEl)<n?this.group.insertBefore(this.dragEl,this.dropEl.nextElementSibling):this.group.insertBefore(this.dragEl,this.dropEl),a.animate(this.dragEl,o,this.animation),a.animate(this.dropEl,s,this.animation),this.diff.old.node=this.dragEl,this.diff.new.node=this.dropEl}this.diff.new.rect=this.dropEl.getBoundingClientRect()}}}},{key:"_onDrop",value:function(){this._offMoveEvents(),this._offUpEvents(),document.body.style.cursor="",this.isMousedown&&this.isMousemove&&this.dragEnd&&"function"==typeof this.dragEnd&&this.dragEnd(this.diff.old,this.diff.new),this.isMousedown=!1,this.isMousemove=!1,this.diff.destroy(),this.ghost.destroy()}},{key:"_checkRange",value:function(t){var e=this.group.getBoundingClientRect(),n=e.top,i=e.left,o=e.right,e=e.bottom;(t.clientX<i||t.clientX>o||t.clientY<n||t.clientY>e)&&(document.body.style.cursor="not-allowed")}},{key:"_resetState",value:function(){this.isMousedown=!1,this.isMousemove=!1,this.dragEl=null,this.dropEl=null,this.ghost.destroy(),this.diff=new d}},{key:"_bindEventListener",value:function(){this._onStart=this._onStart.bind(this),this._onMove=this._onMove.bind(this),this._onDrop=this._onDrop.bind(this),this.supportPointer?a.on(this.group,"pointerdown",this._onStart):a.on(this.group,"mousedown",this._onStart)}},{key:"_onMoveEvents",value:function(){this.supportPointer?a.on(document,"pointermove",this._onMove):a.on(document,"mousemove",this._onMove)}},{key:"_onUpEvents",value:function(){this.supportPointer?a.on(document,"pointerup",this._onDrop):a.on(document,"mouseup",this._onDrop)}},{key:"_unbindEventListener",value:function(){a.off(this.group,"mousedown",this._onStart),a.off(this.group,"pointerdown",this._onStart)}},{key:"_offMoveEvents",value:function(){a.off(document,"mousemove",this._onMove),a.off(document,"pointermove",this._onMove)}},{key:"_offUpEvents",value:function(){a.off(document,"mouseup",this._onDrop),a.off(document,"pointerup",this._onDrop)}}]),e}()}); | ||
!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):(t="undefined"!=typeof globalThis?globalThis:t||self).sortable=e()}(this,function(){"use strict";function i(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}function o(t,e){for(var n=0;n<e.length;n++){var i=e[n];i.enumerable=i.enumerable||!1,i.configurable=!0,"value"in i&&(i.writable=!0),Object.defineProperty(t,i.key,i)}}function s(t,e,n){e&&o(t.prototype,e),n&&o(t,n),Object.defineProperty(t,"prototype",{writable:!1})}function r(t){return function(t){if(Array.isArray(t))return l(t)}(t)||function(t){if("undefined"!=typeof Symbol&&null!=t[Symbol.iterator]||null!=t["@@iterator"])return Array.from(t)}(t)||function(t,e){if(t){if("string"==typeof t)return l(t,e);var n=Object.prototype.toString.call(t).slice(8,-1);return"Map"===(n="Object"===n&&t.constructor?t.constructor.name:n)||"Set"===n?Array.from(t):"Arguments"===n||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)?l(t,e):void 0}}(t)||function(){throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}()}function l(t,e){(null==e||e>t.length)&&(e=t.length);for(var n=0,i=new Array(e);n<e;n++)i[n]=t[n];return i}function t(t){if("undefined"!=typeof window&&window.navigator)return!!navigator.userAgent.match(t)}var h=t(/(?:Trident.*rv[ :]?11\.|msie|iemobile|Windows Phone)/i),a=t(/safari/i)&&!t(/chrome/i)&&!t(/android/i),c={capture:!1,passive:!1},u=/\s+/g,d={on:function(t,e,n){t.addEventListener(e,n,!h&&c)},off:function(t,e,n){t.removeEventListener(e,n,!h&&c)},getWindowScrollingElement:function(){var t=document.scrollingElement;return t||document.documentElement},index:function(t,e){return e&&e.parentNode?r(Array.from(t.children)).indexOf(e):-1},getRect:function(t){var e;if(t.getBoundingClientRect||t===window)return e={top:0,left:0,bottom:0,right:0,height:0,width:0},t!==window&&t.parentNode&&t!==this.getWindowScrollingElement()?(t=t.getBoundingClientRect(),e.top=t.top,e.left=t.left,e.bottom=t.bottom,e.right=t.right,e.height=t.height,e.width=t.width):(e.top=0,e.left=0,e.bottom=window.innerHeight,e.right=window.innerWidth,e.height=window.innerHeight,e.width=window.innerWidth),e},getElement:function(t,e){var n={index:-1,el:null,rect:{}},i=r(Array.from(t.children)),t=i.indexOf(e);-1<t&&Object.assign(n,{index:t,el:i[t],rect:this.getRect(i[t])});for(var o=0;o<i.length;o++)this.isChildOf(e,i[o])&&Object.assign(n,{index:o,el:i[o],rect:this.getRect(i[o])});return n},isChildOf:function(t,e){var n;if(t&&e)for(n=t.parentNode;n;){if(e===n)return!0;n=n.parentNode}return!1},toggleClass:function(t,e,n){var i;t&&e&&(t.classList?t.classList[n?"add":"remove"](e):(i=(" "+t.className+" ").replace(u," ").replace(" "+e+" "," "),t.className=(i+(n?" "+e:"")).replace(u," ")))},matches:function(t,e){if(e){if(">"===e[0]&&(e=e.substring(1)),t)try{if(t.matches)return t.matches(e);if(t.msMatchesSelector)return t.msMatchesSelector(e);if(t.webkitMatchesSelector)return t.webkitMatchesSelector(e)}catch(t){return!1}return!1}},animate:function(t,e){var n=this,i=2<arguments.length&&void 0!==arguments[2]?arguments[2]:150,o=this.getRect(t),s=e.left-o.left,e=e.top-o.top;this.css(t,"transition","none"),this.css(t,"transform","translate3d(".concat(s,"px, ").concat(e,"px, 0)")),t.offsetLeft,this.css(t,"transition","all ".concat(i,"ms")),this.css(t,"transform","translate3d(0px, 0px, 0px)"),clearTimeout(t.animated),t.animated=setTimeout(function(){n.css(t,"transition",""),n.css(t,"transform",""),t.animated=null},i)},css:function(t,e,n){var i=t&&t.style;if(i){if(void 0===n)return document.defaultView&&document.defaultView.getComputedStyle?n=document.defaultView.getComputedStyle(t,""):t.currentStyle&&(n=t.currentStyle),void 0===e?n:n[e];i[e=e in i||-1!==e.indexOf("webkit")?e:"-webkit-"+e]=n+("string"==typeof n?"":"px")}},debounce:function(o,s){return function(){for(var t=this,e=arguments.length,n=new Array(e),i=0;i<e;i++)n[i]=arguments[i];clearTimeout(o.id),o.id=setTimeout(function(){o.call.apply(o,[t].concat(n))},s)}},_nextTick:function(t){return setTimeout(t,0)}},f=function(){function t(){i(this,t),this.old={node:null,rect:{}},this.new={node:null,rect:{}}}return s(t,[{key:"get",value:function(t){return this[t]}},{key:"set",value:function(t,e){this[t]=e}},{key:"destroy",value:function(){this.old={node:null,rect:{}},this.new={node:null,rect:{}}}}]),t}(),p=function(){function e(t){i(this,e),this.options=t,this.x=0,this.y=0,this.exist=!1}return s(e,[{key:"init",value:function(t,e){if(t){this.$el=t,this.rect=e;var n,t=this.options,i=t.ghostClass,t=t.ghostStyle,o=void 0===t?{}:t,t=e.width,e=e.height;for(n in this.$el.class=i,this.$el.style.width=t+"px",this.$el.style.height=e+"px",this.$el.style.transform="",this.$el.style.transition="",this.$el.style.position="fixed",this.$el.style.left=0,this.$el.style.top=0,this.$el.style.zIndex=1e5,this.$el.style.opacity=.8,this.$el.style.pointerEvents="none",o)d.css(this.$el,n,o[n])}else console.error("Ghost Element is required")}},{key:"get",value:function(t){return this[t]}},{key:"set",value:function(t,e){this[t]=e,this[t]=e}},{key:"move",value:function(){this.exist||(document.body.appendChild(this.$el),this.exist=!0),this.$el.style.transform="translate3d(".concat(this.x,"px, ").concat(this.y,"px, 0)")}},{key:"destroy",value:function(){this.$el&&this.$el.remove(),this.exist=!1}}]),e}();return function(){function n(t,e){i(this,n),this.$el=t,this.options=e=Object.assign({},e),this.dragEl=null,this.dropEl=null,this.diff=null,this.ghost=null,this.calcXY={x:0,y:0},d.debounce(this.init(),50)}return s(n,[{key:"init",value:function(){if(this.$el){var t,e={animation:150,ghostClass:"",ghostStyle:{},chosenClass:"",draggable:"",dragging:null,dragEnd:null,supportPointer:"PointerEvent"in window&&!a,ownerDocument:this.$el.ownerDocument};for(t in e)t in this.options||(this.options[t]=e[t]);this.diff=new f,this.ghost=new p(this.options),this._bindEventListener()}else console.error("Error: container element is required")}},{key:"destroy",value:function(){this._unbindEventListener(),this._resetState()}},{key:"_bindEventListener",value:function(){this._onStart=this._onStart.bind(this),this._onMove=this._onMove.bind(this),this._onDrop=this._onDrop.bind(this),this.options.supportPointer?d.on(this.$el,"pointerdown",this._onStart):(d.on(this.$el,"mousedown",this._onStart),d.on(this.$el,"touchstart",this._onStart))}},{key:"_unbindEventListener",value:function(){d.off(this.$el,"pointerdown",this._onStart),d.off(this.$el,"touchstart",this._onStart),d.off(this.$el,"mousedown",this._onStart)}},{key:"_onStart",value:function(t){var e=this.options,n=e.dragging,e=e.draggable,i=t.touches&&t.touches[0]||t.pointerType&&"touch"===t.pointerType&&t,o=(i||t).target;if("function"==typeof e){if(!e(i||t))return!0}else if(e&&!d.matches(o,e))return!0;if(!/mousedown|pointerdown/.test(t.type)||0===t.button){if(o===this.$el)return!0;try{document.selection?d._nextTick(function(){document.selection.empty()}):window.getSelection().removeAllRanges();var s=n&&"function"==typeof n?n(i||t):o;if(!s)return!0;if(s.animated)return;this.dragEl=s}catch(t){return!0}window.sortableDndOnDown=!0;e=d.getElement(this.$el,this.dragEl),n=e.index,o=e.el,s=e.rect;if(!o||n<0)return!0;e=this.dragEl.cloneNode(!0);this.ghost.init(e,s),this.ghost.set("x",s.left),this.ghost.set("y",s.top),this.diff.old.rect=s,this.calcXY={x:(i||t).clientX,y:(i||t).clientY},this._onMoveEvents(i),this._onUpEvents(i)}}},{key:"_onMove",value:function(t){t.preventDefault();var e=t.touches&&t.touches[0],t=e||t,n=t.clientX,i=t.clientY,e=e?document.elementFromPoint(n,i):t.target,o=this.options.chosenClass;if(d.toggleClass(this.dragEl,o,!0),this.ghost.move(),window.sortableDndOnDown&&!(n<0||i<0)){document.body.style.cursor="grabbing",window.sortableDndOnMove=!0,this.ghost.set("x",this.ghost.x+n-this.calcXY.x),this.ghost.set("y",this.ghost.y+i-this.calcXY.y),this.calcXY={x:n,y:i},this.ghost.move(),this._checkRange(t);var o=d.getElement(this.$el,e),t=o.index,e=o.el,o=o.rect,s=o.left,r=o.right,l=o.top,o=o.bottom;if(!(!e||t<0||l<0)&&s<n&&n<r&&l<i&&i<o){if(this.dropEl=e,this.dropEl!==this.dragEl){if(this.dropEl.animated)return;s=d.getRect(this.dragEl),n=d.getRect(this.dropEl);d.index(this.$el,this.dragEl)<t?this.$el.insertBefore(this.dragEl,this.dropEl.nextElementSibling):this.$el.insertBefore(this.dragEl,this.dropEl),d.animate(this.dragEl,s,this.animation),d.animate(this.dropEl,n,this.animation),this.diff.old.node=this.dragEl,this.diff.new.node=this.dropEl}this.diff.new.rect=d.getRect(this.dropEl)}}}},{key:"_onDrop",value:function(){this._offMoveEvents(),this._offUpEvents(),document.body.style.cursor="";var t=this.options,e=t.dragEnd,t=t.chosenClass;d.toggleClass(this.dragEl,t,!1),window.sortableDndOnDown&&window.sortableDndOnMove&&e&&"function"==typeof e&&e(this.diff.old,this.diff.new),this.diff.destroy(),this.ghost.destroy(),this._removeWindowState()}},{key:"_checkRange",value:function(t){var e=d.getRect(this.$el),n=e.top,i=e.left,o=e.right,e=e.bottom;(t.clientX<i||t.clientX>o||t.clientY<n||t.clientY>e)&&(document.body.style.cursor="not-allowed")}},{key:"_resetState",value:function(){this.dragEl=null,this.dropEl=null,this.ghost.destroy(),this.diff.destroy(),this._removeWindowState()}},{key:"_removeWindowState",value:function(){window.sortableDndOnDown=null,window.sortableDndOnMove=null,delete window.sortableDndOnDown,delete window.sortableDndOnMove}},{key:"_onMoveEvents",value:function(t){var e=this.options,n=e.supportPointer,e=e.ownerDocument;n?d.on(e,"pointermove",this._onMove):t?d.on(e,"touchmove",this._onMove):d.on(e,"mousemove",this._onMove)}},{key:"_onUpEvents",value:function(){var t=this.options.ownerDocument;d.on(t,"pointerup",this._onDrop),d.on(t,"touchend",this._onDrop),d.on(t,"touchcancel",this._onDrop),d.on(t,"mouseup",this._onDrop)}},{key:"_offMoveEvents",value:function(){var t=this.options.ownerDocument;d.off(t,"pointermove",this._onMove),d.off(t,"touchmove",this._onMove),d.off(t,"mousemove",this._onMove)}},{key:"_offUpEvents",value:function(){var t=this.options.ownerDocument;d.off(t,"mouseup",this._onDrop),d.off(t,"touchend",this._onDrop),d.off(t,"touchcancel",this._onDrop),d.off(t,"pointerup",this._onDrop)}}]),n}()}); |
{ | ||
"name": "sortable-dnd", | ||
"version": "0.0.6", | ||
"version": "0.0.7", | ||
"description": "JS Library for Drag and Drop, supports Sortable and Draggable", | ||
@@ -5,0 +5,0 @@ "main": "dist/sortable.js", |
@@ -25,11 +25,16 @@ <p> | ||
var DND = new Sortable({ | ||
group: document.getElementById('content'), | ||
dragging: (e) => { | ||
return e.target | ||
}, | ||
dragEnd: (pre, cur) => { | ||
... | ||
var DND = new Sortable( | ||
document.getElementById('content'), | ||
{ | ||
draggable: (e) => { | ||
return true | ||
}, // or draggable: '.class' | ||
dragging: (e) => { | ||
return e.target | ||
}, | ||
dragEnd: (pre, cur) => { | ||
... | ||
} | ||
} | ||
}) | ||
) | ||
``` | ||
@@ -47,3 +52,3 @@ | ||
|-------------|--------------|--------------|--------------| | ||
| `group` | `HTMLElement` | - | List parent element | | ||
| `draggable` | `String | Function` | - | Specifies which items inside the element should be draggable, the function type must return a boolean | | ||
| `dragging` | `Function` | (e) => e.target | Specifies the drag and drop element, which must return an HTMLElement | | ||
@@ -53,2 +58,3 @@ | `dragEnd` | `Function` | (pre, cur) => {} | The callback function when the drag is completed | | ||
| `ghostClass` | `String` | '' | The class of the mask element when dragging | | ||
| `chosenClass` | `String` | {} | The class of the selected element when dragging | | ||
| `animation` | `Number` | 300 | animation delay | | ||
@@ -55,0 +61,0 @@ |
335
src/index.js
import utils from './utils.js' | ||
import { Safari } from './brower.js' | ||
import { Ghost, Diff } from './constructors.js' | ||
/** | ||
* @interface Options { | ||
* | ||
* group: HTMLElement, | ||
* | ||
* draggable?: Function, return element node selected when dragging, or null | ||
* | ||
* dragEnd?: Function, The callback function when the drag is completed | ||
* | ||
* ghostStyle?: Object, | ||
* | ||
* ghostClass?: String, | ||
* | ||
* } | ||
* 拖拽前后差异初始化 | ||
*/ | ||
class Diff { | ||
constructor() { | ||
this.old = { node: null, rect: {} } | ||
this.new = { node: null, rect: {} } | ||
} | ||
class Sortable { | ||
get(key) { | ||
return this[key] | ||
} | ||
set(key, value) { | ||
this[key] = value | ||
} | ||
destroy() { | ||
this.old = { node: null, rect: {} } | ||
this.new = { node: null, rect: {} } | ||
} | ||
} | ||
/** | ||
* 拖拽中的元素 | ||
*/ | ||
class Ghost { | ||
constructor(options) { | ||
this.group = options.group // 父级元素 | ||
this.options = options | ||
this.x = 0 | ||
this.y = 0 | ||
this.exist = false | ||
} | ||
this.dragging = options.dragging // 必须为函数且必须返回一个 HTMLElement (e) => return e.target | ||
this.dragEnd = options.dragEnd // 拖拽完成时的回调函数,返回两个值(olddom, newdom) => {} | ||
init(el, rect) { | ||
if (!el) { | ||
console.error('Ghost Element is required') | ||
return | ||
} | ||
this.$el = el | ||
this.rect = rect | ||
const { ghostClass, ghostStyle = {} } = this.options | ||
const { width, height } = rect | ||
this.$el.class = ghostClass | ||
this.$el.style.width = width + 'px' | ||
this.$el.style.height = height + 'px' | ||
this.$el.style.transform = '' | ||
this.$el.style.transition = '' | ||
this.$el.style.position = 'fixed' | ||
this.$el.style.left = 0 | ||
this.$el.style.top = 0 | ||
this.$el.style.zIndex = 100000 | ||
this.$el.style.opacity = 0.8 | ||
this.$el.style.pointerEvents = 'none' | ||
this.ghostStyle = options.ghostStyle // 克隆元素包含的属性 | ||
this.ghostClass = options.ghostClass // 克隆元素的类名 | ||
for (const key in ghostStyle) { | ||
utils.css(this.$el, key, ghostStyle[key]) | ||
} | ||
} | ||
this.animation = options.animation || 300 // 动画延迟 | ||
get (key) { | ||
return this[key] | ||
} | ||
this.isMousedown = false // 记录鼠标按下 | ||
this.isMousemove = false // 记录鼠标移动 | ||
set (key, value) { | ||
this[key] = value | ||
this[key] = value | ||
} | ||
this.dragEl = null // 拖拽元素 | ||
this.dropEl = null // 释放元素 | ||
this.diff = new Diff() // 记录拖拽前后差异 | ||
move() { | ||
// 将初始化放在 move 事件中,避免与鼠标点击事件冲突 | ||
if (!this.exist) { | ||
document.body.appendChild(this.$el) | ||
this.exist = true | ||
} | ||
this.$el.style.transform = `translate3d(${this.x}px, ${this.y}px, 0)` | ||
} | ||
this.ghost = new Ghost({ | ||
ghostClass: this.ghostClass, | ||
ghostStyle: this.ghostStyle | ||
}) | ||
destroy() { | ||
if (this.$el) this.$el.remove() | ||
this.exist = false | ||
} | ||
} | ||
this.supportPointer = ('PointerEvent' in window) && !Safari | ||
class Sortable { | ||
constructor(el, options) { | ||
this.$el = el // 列表容器元素 | ||
this.options = options = Object.assign({}, options) | ||
this.calcXY = { x: 0, y: 0 } | ||
this.dragEl = null // 拖拽元素 | ||
this.dropEl = null // 释放元素 | ||
this.diff = null // 记录拖拽前后差异 | ||
this.ghost = null // 拖拽时蒙版元素 | ||
this.calcXY = { x: 0, y: 0 } // 记录拖拽移动时坐标 | ||
@@ -52,6 +103,28 @@ utils.debounce(this.init(), 50) // 避免重复执行多次 | ||
init() { | ||
if (!this.group) { | ||
console.error('Error: group is required') | ||
if (!this.$el) { | ||
console.error('Error: container element is required') | ||
return | ||
} | ||
const defaults = { | ||
animation: 150, // 动画延时 | ||
ghostClass: '', | ||
ghostStyle: {}, | ||
chosenClass: '', | ||
draggable: '', // String: class, Function: (e) => return true | ||
dragging: null, // 必须为函数且必须返回一个 HTMLElement (e) => return e.target | ||
dragEnd: null, // 拖拽完成时的回调函数,返回两个值(olddom, newdom) => {} | ||
supportPointer: ('PointerEvent' in window) && !Safari, | ||
ownerDocument: this.$el.ownerDocument, | ||
} | ||
// Set default options | ||
for (const name in defaults) { | ||
!(name in this.options) && (this.options[name] = defaults[name]) | ||
} | ||
this.diff = new Diff() | ||
this.ghost = new Ghost(this.options) | ||
this._bindEventListener() | ||
@@ -64,20 +137,60 @@ } | ||
_onStart(e) { | ||
if (e.button !== 0) return true | ||
if (e.target === this.group) return true | ||
_bindEventListener() { | ||
this._onStart = this._onStart.bind(this) | ||
this._onMove = this._onMove.bind(this) | ||
this._onDrop = this._onDrop.bind(this) | ||
const { supportPointer } = this.options | ||
if (supportPointer) { | ||
utils.on(this.$el, 'pointerdown', this._onStart) | ||
} else { | ||
utils.on(this.$el, 'mousedown', this._onStart) | ||
utils.on(this.$el, 'touchstart', this._onStart) | ||
} | ||
} | ||
_unbindEventListener() { | ||
utils.off(this.$el, 'pointerdown', this._onStart) | ||
utils.off(this.$el, 'touchstart', this._onStart) | ||
utils.off(this.$el, 'mousedown', this._onStart) | ||
} | ||
_onStart(evt) { | ||
const { dragging, draggable } = this.options | ||
const touch = (evt.touches && evt.touches[0]) || (evt.pointerType && evt.pointerType === 'touch' && evt) | ||
const target = (touch || evt).target | ||
if (typeof draggable === 'function') { | ||
if (!draggable(touch || evt)) return true | ||
} else if (draggable) { | ||
if (!utils.matches(target, draggable)) return true | ||
} | ||
if (/mousedown|pointerdown/.test(evt.type) && evt.button !== 0) return // only left button and enabled | ||
if (target === this.$el) return true | ||
try { | ||
if (document.selection) { | ||
// Timeout neccessary for IE9 | ||
utils._nextTick(() => { document.selection.empty() }) | ||
} else { | ||
window.getSelection().removeAllRanges() | ||
} | ||
// 获取拖拽元素 | ||
const element = this.dragging ? this.dragging(e) : e.target | ||
const element = dragging && typeof dragging === 'function' ? dragging(touch || evt) : target | ||
// 不存在拖拽元素时不允许拖拽 | ||
if (!element) return true | ||
if (element.animated) return | ||
this.dragEl = element | ||
} catch(e) { | ||
} catch (err) { | ||
// | ||
return true | ||
} | ||
this.isMousedown = true | ||
} | ||
window.sortableDndOnDown = true | ||
// 获取当前元素在列表中的位置 | ||
const { index, el, rect } = utils.getElement(this.group, this.dragEl) | ||
const { index, el, rect } = utils.getElement(this.$el, this.dragEl) | ||
@@ -88,32 +201,34 @@ if (!el || index < 0) return true | ||
const ghostEl = this.dragEl.cloneNode(true) | ||
this.ghost.init(ghostEl, rect) | ||
this.diff.old.rect = rect | ||
this.ghost.set('x', rect.left) | ||
this.ghost.set('y', rect.top) | ||
// 记录拖拽移动时坐标 | ||
this.calcXY = { x: e.clientX, y: e.clientY } | ||
this.diff.old.rect = rect | ||
this.calcXY = { x: (touch || evt).clientX, y: (touch || evt).clientY } | ||
this._onMoveEvents() | ||
this._onUpEvents() | ||
this._onMoveEvents(touch) | ||
this._onUpEvents(touch) | ||
} | ||
_onMove(e) { | ||
_onMove(evt) { | ||
evt.preventDefault() | ||
const touch = evt.touches && evt.touches[0] | ||
const e = touch || evt | ||
const { clientX, clientY } = e | ||
const target = touch ? document.elementFromPoint(clientX, clientY) : e.target | ||
const { chosenClass } = this.options | ||
utils.toggleClass(this.dragEl, chosenClass, true) | ||
this.ghost.move() | ||
e.preventDefault() | ||
if (!this.isMousedown) return | ||
if (e.clientX < 0 || e.clientY < 0) return | ||
if (!window.sortableDndOnDown) return | ||
if (clientX < 0 || clientY < 0) return | ||
document.body.style.cursor = 'grabbing' | ||
this.isMousemove = true | ||
window.sortableDndOnMove = true | ||
this.ghost.set('x', this.ghost.x + e.clientX - this.calcXY.x) | ||
this.ghost.set('y', this.ghost.y + e.clientY - this.calcXY.y) | ||
this.calcXY = { x: e.clientX, y: e.clientY } | ||
this.ghost.set('x', this.ghost.x + clientX - this.calcXY.x) | ||
this.ghost.set('y', this.ghost.y + clientY - this.calcXY.y) | ||
this.calcXY = { x: clientX, y: clientY } | ||
this.ghost.move() | ||
@@ -123,24 +238,20 @@ | ||
const { index, el, rect } = utils.getElement(this.group, e.target) | ||
const { index, el, rect } = utils.getElement(this.$el, target) | ||
const { left, right, top, bottom } = rect | ||
if (!el || index < 0) return | ||
if (!el || index < 0 || top < 0) return | ||
if (top < 0 || top - (this.ghost.rect.height / 3) < 0) return | ||
if (e.clientX > left && e.clientX < right && e.clientY > top && e.clientY < bottom) { | ||
if (clientX > left && clientX < right && clientY > top && clientY < bottom) { | ||
this.dropEl = el | ||
// 拖拽前后元素不一致时交换 | ||
if (this.dropEl !== this.dragEl) { | ||
if (this.dropEl.animated) return | ||
const dragRect = this.dragEl.getBoundingClientRect() | ||
const dropRect = this.dropEl.getBoundingClientRect() | ||
const dragRect = utils.getRect(this.dragEl) | ||
const dropRect = utils.getRect(this.dropEl) | ||
if (this.dropEl.animated) return | ||
if (utils.index(this.group, this.dragEl) < index) { | ||
this.group.insertBefore(this.dragEl, this.dropEl.nextElementSibling) | ||
if (utils.index(this.$el, this.dragEl) < index) { | ||
this.$el.insertBefore(this.dragEl, this.dropEl.nextElementSibling) | ||
} else { | ||
this.group.insertBefore(this.dragEl, this.dropEl) | ||
this.$el.insertBefore(this.dragEl, this.dropEl) | ||
} | ||
@@ -155,3 +266,3 @@ | ||
} | ||
this.diff.new.rect = this.dropEl.getBoundingClientRect() | ||
this.diff.new.rect = utils.getRect(this.dropEl) | ||
} | ||
@@ -164,18 +275,23 @@ } | ||
document.body.style.cursor = '' | ||
if (this.isMousedown && this.isMousemove) { | ||
const { dragEnd, chosenClass } = this.options | ||
utils.toggleClass(this.dragEl, chosenClass, false) | ||
if (window.sortableDndOnDown && window.sortableDndOnMove) { | ||
// 拖拽完成触发回调函数 | ||
if (this.dragEnd && typeof this.dragEnd === 'function') | ||
this.dragEnd(this.diff.old, this.diff.new) | ||
if (dragEnd && typeof dragEnd === 'function') dragEnd(this.diff.old, this.diff.new) | ||
} | ||
this.isMousedown = false | ||
this.isMousemove = false | ||
this.diff.destroy() | ||
this.ghost.destroy() | ||
this._removeWindowState() | ||
} | ||
_checkRange(e) { | ||
const { top, left, right, bottom } = this.group.getBoundingClientRect() | ||
const { top, left, right, bottom } = utils.getRect(this.$el) | ||
if (e.clientX < left || e.clientX > right || e.clientY < top || e.clientY > bottom) { | ||
document.body.style.cursor = 'not-allowed' | ||
return | ||
} | ||
@@ -185,27 +301,24 @@ } | ||
_resetState() { | ||
this.isMousedown = false | ||
this.isMousemove = false | ||
this.dragEl = null | ||
this.dropEl = null | ||
this.ghost.destroy() | ||
this.diff = new Diff() | ||
this.diff.destroy() | ||
this._removeWindowState() | ||
} | ||
_bindEventListener() { | ||
this._onStart = this._onStart.bind(this) | ||
this._onMove = this._onMove.bind(this) | ||
this._onDrop = this._onDrop.bind(this) | ||
if (this.supportPointer) { | ||
utils.on(this.group, 'pointerdown', this._onStart) | ||
} else { | ||
utils.on(this.group, 'mousedown', this._onStart) | ||
} | ||
_removeWindowState() { | ||
window.sortableDndOnDown = null | ||
window.sortableDndOnMove = null | ||
delete window.sortableDndOnDown | ||
delete window.sortableDndOnMove | ||
} | ||
_onMoveEvents() { | ||
if (this.supportPointer) { | ||
utils.on(document, 'pointermove', this._onMove) | ||
_onMoveEvents(touch) { | ||
const { supportPointer, ownerDocument } = this.options | ||
if (supportPointer) { | ||
utils.on(ownerDocument, 'pointermove', this._onMove) | ||
} else if (touch) { | ||
utils.on(ownerDocument, 'touchmove', this._onMove) | ||
} else { | ||
utils.on(document, 'mousemove', this._onMove) | ||
utils.on(ownerDocument, 'mousemove', this._onMove) | ||
} | ||
@@ -215,22 +328,22 @@ } | ||
_onUpEvents() { | ||
if (this.supportPointer) { | ||
utils.on(document, 'pointerup', this._onDrop) | ||
} else { | ||
utils.on(document, 'mouseup', this._onDrop) | ||
} | ||
const { ownerDocument } = this.options | ||
utils.on(ownerDocument, 'pointerup', this._onDrop) | ||
utils.on(ownerDocument, 'touchend', this._onDrop) | ||
utils.on(ownerDocument, 'touchcancel', this._onDrop) | ||
utils.on(ownerDocument, 'mouseup', this._onDrop) | ||
} | ||
_unbindEventListener() { | ||
utils.off(this.group, 'mousedown', this._onStart) | ||
utils.off(this.group, 'pointerdown', this._onStart) | ||
} | ||
_offMoveEvents() { | ||
utils.off(document, 'mousemove', this._onMove) | ||
utils.off(document, 'pointermove', this._onMove) | ||
const { ownerDocument } = this.options | ||
utils.off(ownerDocument, 'pointermove', this._onMove) | ||
utils.off(ownerDocument, 'touchmove', this._onMove) | ||
utils.off(ownerDocument, 'mousemove', this._onMove) | ||
} | ||
_offUpEvents() { | ||
utils.off(document, 'mouseup', this._onDrop) | ||
utils.off(document, 'pointerup', this._onDrop) | ||
const { ownerDocument } = this.options | ||
utils.off(ownerDocument, 'mouseup', this._onDrop) | ||
utils.off(ownerDocument, 'touchend', this._onDrop) | ||
utils.off(ownerDocument, 'touchcancel', this._onDrop) | ||
utils.off(ownerDocument, 'pointerup', this._onDrop) | ||
} | ||
@@ -237,0 +350,0 @@ } |
136
src/utils.js
@@ -8,4 +8,12 @@ import { IE11OrLess } from './brower.js' | ||
const R_SPACE = /\s+/g | ||
export default { | ||
/** | ||
* add specified event listener | ||
* @param {HTMLElement} el | ||
* @param {String} event | ||
* @param {Function} fn | ||
*/ | ||
on(el, event, fn) { | ||
@@ -15,2 +23,8 @@ el.addEventListener(event, fn, !IE11OrLess && captureMode) | ||
/** | ||
* remove specified event listener | ||
* @param {HTMLElement} el | ||
* @param {String} event | ||
* @param {Function} fn | ||
*/ | ||
off(el, event, fn) { | ||
@@ -30,2 +44,8 @@ el.removeEventListener(event, fn, !IE11OrLess && captureMode) | ||
/** | ||
* get specified element's index in group | ||
* @param {HTMLElement} group | ||
* @param {HTMLElement} el | ||
* @returns {Number} index | ||
*/ | ||
index(group, el) { | ||
@@ -38,15 +58,51 @@ if (!el || !el.parentNode) return -1 | ||
getRect(children, index) { | ||
if (!children.length) return {} | ||
if (index < 0) return {} | ||
return children[index].getBoundingClientRect() | ||
/** | ||
* Returns the "bounding client rect" of given element | ||
* @param {HTMLElement} el The element whose boundingClientRect is wanted | ||
*/ | ||
getRect(el) { | ||
if (!el.getBoundingClientRect && el !== window) return | ||
const rect = { | ||
top: 0, | ||
left: 0, | ||
bottom: 0, | ||
right: 0, | ||
height: 0, | ||
width: 0, | ||
} | ||
let elRect | ||
if (el !== window && el.parentNode && el !== this.getWindowScrollingElement()) { | ||
elRect = el.getBoundingClientRect() | ||
rect.top = elRect.top | ||
rect.left = elRect.left | ||
rect.bottom = elRect.bottom | ||
rect.right = elRect.right | ||
rect.height = elRect.height | ||
rect.width = elRect.width | ||
} else { | ||
rect.top = 0 | ||
rect.left = 0 | ||
rect.bottom = window.innerHeight | ||
rect.right = window.innerWidth | ||
rect.height = window.innerHeight | ||
rect.width = window.innerWidth | ||
} | ||
return rect | ||
}, | ||
getElement(group, dragging) { | ||
/** | ||
* get target Element in group | ||
* @param {HTMLElement} group | ||
* @param {HTMLElement} el | ||
*/ | ||
getElement(group, el) { | ||
const result = { index: -1, el: null, rect: {} } | ||
const children = [...Array.from(group.children)] | ||
// 如果能直接在子元素中找到,返回对应的index | ||
const index = children.indexOf(dragging) | ||
const index = children.indexOf(el) | ||
if (index > -1) | ||
@@ -56,3 +112,3 @@ Object.assign(result, { | ||
el: children[index], | ||
rect: children[index].getBoundingClientRect() | ||
rect: this.getRect(children[index]) | ||
}) | ||
@@ -62,7 +118,7 @@ | ||
for (let i = 0; i < children.length; i++) { | ||
if (this.isChildOf(dragging, children[i])) | ||
if (this.isChildOf(el, children[i])) | ||
Object.assign(result, { | ||
index: i, | ||
el: children[i], | ||
rect: children[i].getBoundingClientRect() | ||
rect: this.getRect(children[i]) | ||
}) | ||
@@ -74,3 +130,8 @@ } | ||
// 判断子元素是否包含在父元素中 | ||
/** | ||
* Check if child element is contained in parent element | ||
* @param {HTMLElement} child | ||
* @param {HTMLElement} parent | ||
* @returns {Boolean} true | false | ||
*/ | ||
isChildOf(child, parent) { | ||
@@ -88,5 +149,50 @@ let parentNode | ||
animate(el, preRect, animation = 300) { | ||
const curRect = el.getBoundingClientRect() | ||
/** | ||
* add or remove element's class | ||
* @param {HTMLElement} el element | ||
* @param {String} name class name | ||
* @param {Boolean} state true: add, false: remove | ||
*/ | ||
toggleClass(el, name, state) { | ||
if (el && name) { | ||
if (el.classList) { | ||
el.classList[state ? 'add' : 'remove'](name) | ||
} else { | ||
const className = (' ' + el.className + ' ').replace(R_SPACE, ' ').replace(' ' + name + ' ', ' ') | ||
el.className = (className + (state ? ' ' + name : '')).replace(R_SPACE, ' ') | ||
} | ||
} | ||
}, | ||
/** | ||
* Check if a DOM element matches a given selector | ||
* @param {HTMLElement} el | ||
* @param {String} selector | ||
* @returns | ||
*/ | ||
matches(el, selector) { | ||
if (!selector) return | ||
selector[0] === '>' && (selector = selector.substring(1)) | ||
if (el) { | ||
try { | ||
if (el.matches) { | ||
return el.matches(selector) | ||
} else if (el.msMatchesSelector) { | ||
return el.msMatchesSelector(selector) | ||
} else if (el.webkitMatchesSelector) { | ||
return el.webkitMatchesSelector(selector) | ||
} | ||
} catch(error) { | ||
return false | ||
} | ||
} | ||
return false | ||
}, | ||
animate(el, preRect, animation = 150) { | ||
const curRect = this.getRect(el) | ||
const left = preRect.left - curRect.left | ||
@@ -136,4 +242,8 @@ const top = preRect.top - curRect.top | ||
} | ||
}, | ||
_nextTick(fn) { | ||
return setTimeout(fn, 0) | ||
} | ||
} |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
53720
1189
64
8