| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670 |
- 'use strict';
- var emitter = require('contra/emitter');
- var crossvent = require('crossvent');
- var classes = require('./classes');
- var doc = document;
- var documentElement = doc.documentElement;
- var animateDuration = 300;
- function dragula (initialContainers, options) {
- var len = arguments.length;
- if (len === 1 && Array.isArray(initialContainers) === false) {
- options = initialContainers;
- initialContainers = [];
- }
- var _mirror; // mirror image
- var _source; // source container
- var _item; // item being dragged
- var _offsetX; // reference x
- var _offsetY; // reference y
- var _moveX; // reference move x
- var _moveY; // reference move y
- var _initialSibling; // reference sibling when grabbed
- var _currentSibling; // reference sibling now
- var _copy; // item used for copying
- var _renderTimer; // timer for setTimeout renderMirrorImage
- var _lastDropTarget = null; // last container item was over
- var _grabbed; // holds mousedown context until first mousemove
- var o = options || {};
- if (o.moves === void 0) { o.moves = always; }
- if (o.accepts === void 0) { o.accepts = always; }
- if (o.invalid === void 0) { o.invalid = invalidTarget; }
- if (o.containers === void 0) { o.containers = initialContainers || []; }
- if (o.isContainer === void 0) { o.isContainer = never; }
- if (o.copy === void 0) { o.copy = false; }
- if (o.copySortSource === void 0) { o.copySortSource = false; }
- if (o.revertOnSpill === void 0) { o.revertOnSpill = false; }
- if (o.removeOnSpill === void 0) { o.removeOnSpill = false; }
- if (o.direction === void 0) { o.direction = 'vertical'; }
- if (o.ignoreInputTextSelection === void 0) { o.ignoreInputTextSelection = true; }
- if (o.mirrorContainer === void 0) { o.mirrorContainer = doc.body; }
- if (o.staticClass === void 0) { o.staticClass = ''; }
- var drake = emitter({
- containers: o.containers,
- start: manualStart,
- end: end,
- cancel: cancel,
- remove: remove,
- destroy: destroy,
- canMove: canMove,
- dragging: false
- });
- if (o.removeOnSpill === true) {
- drake.on('over', spillOver).on('out', spillOut);
- }
- events();
- return drake;
- function isContainer (el) {
- return drake.containers.indexOf(el) !== -1 || o.isContainer(el);
- }
- function events (remove) {
- var op = remove ? 'remove' : 'add';
- touchy(documentElement, op, 'mousedown', grab);
- touchy(documentElement, op, 'mouseup', release);
- }
- function eventualMovements (remove) {
- var op = remove ? 'remove' : 'add';
- touchy(documentElement, op, 'mousemove', startBecauseMouseMoved);
- }
- function movements (remove) {
- var op = remove ? 'remove' : 'add';
- crossvent[op](documentElement, 'selectstart', preventGrabbed); // IE8
- crossvent[op](documentElement, 'click', preventGrabbed);
- }
- function destroy () {
- events(true);
- release({});
- }
- function preventGrabbed (e) {
- if (_grabbed) {
- e.preventDefault();
- }
- }
- function grab (e) {
- _moveX = e.clientX;
- _moveY = e.clientY;
- var ignore = whichMouseButton(e) !== 1 || e.metaKey || e.ctrlKey;
- if (ignore) {
- return; // we only care about honest-to-god left clicks and touch events
- }
- var item = e.target;
- var context = canStart(item);
- if (!context) {
- return;
- }
- _grabbed = context;
- eventualMovements();
- if (e.type === 'mousedown') {
- if (isInput(item)) { // see also: https://github.com/bevacqua/dragula/issues/208
- item.focus(); // fixes https://github.com/bevacqua/dragula/issues/176
- } else {
- e.preventDefault(); // fixes https://github.com/bevacqua/dragula/issues/155
- }
- }
- }
- function startBecauseMouseMoved (e) {
- if (!_grabbed) {
- return;
- }
- if (whichMouseButton(e) === 0) {
- release({});
- return; // when text is selected on an input and then dragged, mouseup doesn't fire. this is our only hope
- }
- // truthy check fixes #239, equality fixes #207
- if (e.clientX !== void 0 && e.clientX === _moveX && e.clientY !== void 0 && e.clientY === _moveY) {
- return;
- }
- if (o.ignoreInputTextSelection) {
- var clientX = getCoord('clientX', e);
- var clientY = getCoord('clientY', e);
- var elementBehindCursor = doc.elementFromPoint(clientX, clientY);
- if (isInput(elementBehindCursor)) {
- return;
- }
- }
- var grabbed = _grabbed; // call to end() unsets _grabbed
- eventualMovements(true);
- movements();
- end();
- start(grabbed);
- var offset = getOffset(_item);
- _offsetX = getCoord('pageX', e) - offset.left;
- _offsetY = getCoord('pageY', e) - offset.top;
- classes.add(_copy || _item, 'gu-transit');
- renderMirrorImage();
- drag(e);
- }
- function canStart (item) {
- if (drake.dragging && _mirror) {
- return;
- }
- if (isContainer(item)) {
- return; // don't drag container itself
- }
- var handle = item;
- while (getParent(item) && isContainer(getParent(item)) === false) {
- if (o.invalid(item, handle)) {
- return;
- }
- item = getParent(item); // drag target should be a top element
- if (!item) {
- return;
- }
- }
- var source = getParent(item);
- if (!source) {
- return;
- }
- if ((o.staticClass && item.classList.contains(o.staticClass))) {
- return;
- }
- if (o.invalid(item, handle)) {
- return;
- }
- var movable = o.moves(item, source, handle, nextEl(item));
- if (!movable) {
- return;
- }
- return {
- item: item,
- source: source
- };
- }
- function canMove (item) {
- return !!canStart(item);
- }
- function manualStart (item) {
- var context = canStart(item);
- if (context) {
- start(context);
- }
- }
- function start (context) {
- if (isCopy(context.item, context.source)) {
- _copy = context.item.cloneNode(true);
- drake.emit('cloned', _copy, context.item, 'copy');
- }
- _source = context.source;
- _item = context.item;
- _initialSibling = _currentSibling = nextEl(context.item);
- drake.dragging = true;
- drake.emit('drag', _item, _source);
- }
- function invalidTarget () {
- return false;
- }
- function end () {
- if (!drake.dragging) {
- return;
- }
- var item = _copy || _item;
- drop(item, getParent(item));
- }
- function ungrab () {
- _grabbed = false;
- eventualMovements(true);
- movements(true);
- }
- function release (e) {
- ungrab();
- if (!drake.dragging) {
- return;
- }
- var item = _copy || _item;
- var clientX = getCoord('clientX', e);
- var clientY = getCoord('clientY', e);
- var elementBehindCursor = getElementBehindPoint(_mirror, clientX, clientY);
- var dropTarget = findDropTarget(elementBehindCursor, clientX, clientY);
- if (dropTarget && ((_copy && o.copySortSource) || (!_copy || dropTarget !== _source))) {
- drop(item, dropTarget);
- } else if (o.removeOnSpill) {
- remove();
- } else {
- cancel();
- }
- }
- function drop (item, target) {
- var parent = getParent(item);
- if (_copy && o.copySortSource && target === _source) {
- parent.removeChild(_item);
- }
- if (isInitialPlacement(target)) {
- drake.emit('cancel', item, _source, _source);
- } else {
- drake.emit('drop', item, target, _source, _currentSibling);
- }
- cleanup();
- }
- function remove () {
- if (!drake.dragging) {
- return;
- }
- var item = _copy || _item;
- var parent = getParent(item);
- if (parent) {
- parent.removeChild(item);
- }
- drake.emit(_copy ? 'cancel' : 'remove', item, parent, _source);
- cleanup();
- }
- function cancel (revert) {
- if (!drake.dragging) {
- return;
- }
- var reverts = arguments.length > 0 ? revert : o.revertOnSpill;
- var item = _copy || _item;
- var parent = getParent(item);
- var initial = isInitialPlacement(parent);
- if (initial === false && reverts) {
- if (_copy) {
- if (parent) {
- parent.removeChild(_copy);
- }
- } else {
- _source.insertBefore(item, _initialSibling);
- }
- }
- if (initial || reverts) {
- drake.emit('cancel', item, _source, _source);
- } else {
- drake.emit('drop', item, parent, _source, _currentSibling);
- }
- cleanup();
- }
- function cleanup () {
- var item = _copy || _item;
- ungrab();
- removeMirrorImage();
- if (item) {
- classes.rm(item, 'gu-transit');
- }
- if (_renderTimer) {
- clearTimeout(_renderTimer);
- }
- drake.dragging = false;
- if (_lastDropTarget) {
- drake.emit('out', item, _lastDropTarget, _source);
- }
- drake.emit('dragend', item);
- _source = _item = _copy = _initialSibling = _currentSibling = _renderTimer = _lastDropTarget = null;
- }
- function isInitialPlacement (target, s) {
- var sibling;
- if (s !== void 0) {
- sibling = s;
- } else if (_mirror) {
- sibling = _currentSibling;
- } else {
- sibling = nextEl(_copy || _item);
- }
- return target === _source && sibling === _initialSibling;
- }
- function findDropTarget (elementBehindCursor, clientX, clientY) {
- var target = elementBehindCursor;
- while (target && !accepted()) {
- target = getParent(target);
- }
- return target;
- function accepted () {
- var droppable = isContainer(target);
- if (droppable === false) {
- return false;
- }
- var immediate = getImmediateChild(target, elementBehindCursor);
- var reference = getReference(target, immediate, clientX, clientY);
- var initial = isInitialPlacement(target, reference);
- if (initial) {
- return true; // should always be able to drop it right back where it was
- }
- return o.accepts(_item, target, _source, reference);
- }
- }
- function drag (e) {
- if (!_mirror) {
- return;
- }
- e.preventDefault();
- var clientX = getCoord('clientX', e);
- var clientY = getCoord('clientY', e);
- var x = clientX - _offsetX;
- var y = clientY - _offsetY;
- _mirror.style.left = x + 'px';
- _mirror.style.top = y + 'px';
- var item = _copy || _item;
- var elementBehindCursor = getElementBehindPoint(_mirror, clientX, clientY);
- var dropTarget = findDropTarget(elementBehindCursor, clientX, clientY);
- var changed = dropTarget !== null && dropTarget !== _lastDropTarget;
- if (changed || dropTarget === null) {
- out();
- _lastDropTarget = dropTarget;
- over();
- }
- var parent = getParent(item);
- if (dropTarget === _source && _copy && !o.copySortSource) {
- if (parent) {
- parent.removeChild(item);
- }
- return;
- }
- var reference;
- var immediate = getImmediateChild(dropTarget, elementBehindCursor);
- if (immediate !== null) {
- reference = getReference(dropTarget, immediate, clientX, clientY);
- } else if (o.revertOnSpill === true && !_copy) {
- reference = _initialSibling;
- dropTarget = _source;
- } else {
- if (_copy && parent) {
- parent.removeChild(item);
- }
- return;
- }
- if (
- (reference === null && changed) ||
- reference !== item &&
- reference !== nextEl(item)
- ) {
- _currentSibling = reference;
- var itemRect = item.getBoundingClientRect();
- var referenceRect = reference ? reference.getBoundingClientRect() : null;
- var direct = o.direction;
- // if isPositive is true, the direction is right or down
- var isPositive;
- if (referenceRect) {
- isPositive = direct === 'horizontal' ? (itemRect.x < referenceRect.x) : (itemRect.y < referenceRect.y);
- }else{
- isPositive = true;
- }
- // mover is the element to be exchange passively
- var mover;
- if (isPositive) {
- mover = reference ? (reference.previousElementSibling ? reference.previousElementSibling : reference) : dropTarget.lastElementChild;
- } else {
- mover = reference; //upward or right
- }
- if (!mover) {
- return;
- }
- if (o.staticClass && mover.classList.contains(o.staticClass)) {
- return;
- }
- var moverRect = mover && mover.getBoundingClientRect();
- dropTarget.insertBefore(item, reference);
- if (mover && moverRect) {
- animate(moverRect, mover);
- animate(itemRect, item);
- }
- drake.emit('shadow', item, dropTarget, _source);
- }
- function moved (type) { drake.emit(type, item, _lastDropTarget, _source); }
- function over () { if (changed) { moved('over'); } }
- function out () { if (_lastDropTarget) { moved('out'); } }
- }
- function spillOver (el) {
- classes.rm(el, 'gu-hide');
- }
- function spillOut (el) {
- if (drake.dragging) { classes.add(el, 'gu-hide'); }
- }
- function renderMirrorImage () {
- if (_mirror) {
- return;
- }
- var rect = _item.getBoundingClientRect();
- _mirror = _item.cloneNode(true);
- _mirror.style.width = getRectWidth(rect) + 'px';
- _mirror.style.height = getRectHeight(rect) + 'px';
- classes.rm(_mirror, 'gu-transit');
- classes.add(_mirror, 'gu-mirror');
- o.mirrorContainer.appendChild(_mirror);
- touchy(documentElement, 'add', 'mousemove', drag);
- classes.add(o.mirrorContainer, 'gu-unselectable');
- drake.emit('cloned', _mirror, _item, 'mirror');
- }
- function removeMirrorImage () {
- if (_mirror) {
- classes.rm(o.mirrorContainer, 'gu-unselectable');
- touchy(documentElement, 'remove', 'mousemove', drag);
- getParent(_mirror).removeChild(_mirror);
- _mirror = null;
- }
- }
- function getImmediateChild (dropTarget, target) {
- var immediate = target;
- while (immediate !== dropTarget && getParent(immediate) !== dropTarget) {
- immediate = getParent(immediate);
- }
- if (immediate === documentElement) {
- return null;
- }
- return immediate;
- }
- function getReference (dropTarget, target, x, y) {
- var horizontal = o.direction === 'horizontal';
- var reference = target !== dropTarget ? inside() : outside();
- return reference;
- function outside () { // slower, but able to figure out any position
- var len = dropTarget.children.length;
- var i;
- var el;
- var rect;
- for (i = 0; i < len; i++) {
- el = dropTarget.children[i];
- rect = el.getBoundingClientRect();
- if (horizontal && (rect.left + rect.width / 2) > x) { return el; }
- if (!horizontal && (rect.top + rect.height / 2) > y) { return el; }
- }
- return null;
- }
- function inside () { // faster, but only available if dropped inside a child element
- var rect = target.getBoundingClientRect();
- if (horizontal) {
- return resolve(x > rect.left + getRectWidth(rect) / 2);
- }
- return resolve(y > rect.top + getRectHeight(rect) / 2);
- }
- function resolve (after) {
- return after ? nextEl(target) : target;
- }
- }
- function isCopy (item, container) {
- return typeof o.copy === 'boolean' ? o.copy : o.copy(item, container);
- }
- }
- function touchy (el, op, type, fn) {
- var touch = {
- mouseup: 'touchend',
- mousedown: 'touchstart',
- mousemove: 'touchmove'
- };
- var pointers = {
- mouseup: 'pointerup',
- mousedown: 'pointerdown',
- mousemove: 'pointermove'
- };
- var microsoft = {
- mouseup: 'MSPointerUp',
- mousedown: 'MSPointerDown',
- mousemove: 'MSPointerMove'
- };
- if (global.navigator.pointerEnabled) {
- crossvent[op](el, pointers[type], fn);
- } else if (global.navigator.msPointerEnabled) {
- crossvent[op](el, microsoft[type], fn);
- } else {
- crossvent[op](el, touch[type], fn);
- crossvent[op](el, type, fn);
- }
- }
- function whichMouseButton (e) {
- if (e.touches !== void 0) { return e.touches.length; }
- if (e.which !== void 0 && e.which !== 0) { return e.which; } // see https://github.com/bevacqua/dragula/issues/261
- if (e.buttons !== void 0) { return e.buttons; }
- var button = e.button;
- if (button !== void 0) { // see https://github.com/jquery/jquery/blob/99e8ff1baa7ae341e94bb89c3e84570c7c3ad9ea/src/event.js#L573-L575
- return button & 1 ? 1 : button & 2 ? 3 : (button & 4 ? 2 : 0);
- }
- }
- function getOffset (el) {
- var rect = el.getBoundingClientRect();
- return {
- left: rect.left + getScroll('scrollLeft', 'pageXOffset'),
- top: rect.top + getScroll('scrollTop', 'pageYOffset')
- };
- }
- function getScroll (scrollProp, offsetProp) {
- if (typeof global[offsetProp] !== 'undefined') {
- return global[offsetProp];
- }
- if (documentElement.clientHeight) {
- return documentElement[scrollProp];
- }
- return doc.body[scrollProp];
- }
- function getElementBehindPoint (point, x, y) {
- var p = point || {};
- var state = p.className;
- var el;
- p.className += ' gu-hide';
- el = doc.elementFromPoint(x, y);
- p.className = state;
- return el;
- }
- function never () { return false; }
- function always () { return true; }
- function getRectWidth (rect) { return rect.width || (rect.right - rect.left); }
- function getRectHeight (rect) { return rect.height || (rect.bottom - rect.top); }
- function getParent (el) { return el.parentNode === doc ? null : el.parentNode; }
- function isInput (el) { return el.tagName === 'INPUT' || el.tagName === 'TEXTAREA' || el.tagName === 'SELECT' || isEditable(el); }
- function isEditable (el) {
- if (!el) { return false; } // no parents were editable
- if (el.contentEditable === 'false') { return false; } // stop the lookup
- if (el.contentEditable === 'true') { return true; } // found a contentEditable element in the chain
- return isEditable(getParent(el)); // contentEditable is set to 'inherit'
- }
- function nextEl (el) {
- return el.nextElementSibling || manually();
- function manually () {
- var sibling = el;
- do {
- sibling = sibling.nextSibling;
- } while (sibling && sibling.nodeType !== 1);
- return sibling;
- }
- }
- /**
- * Create an animation from position before sorting to present position
- * @param prevRect including element's position infomation before sorting
- * @param target element after sorting
- */
- function animate (prevRect, target) {
- if (!prevRect || !target) {
- return;
- }
- var currentRect = target.getBoundingClientRect();
- var originProps = {transition: target.style.transition, transform: target.style.transform};
- Object.assign(target.style, {
- transition: 'none',
- transform: 'translate(' + (prevRect.left - currentRect.left) + 'px,' + (prevRect.top - currentRect.top) + 'px)'
- });
- target.offsetWidth; // repaint
- Object.assign(target.style, {transition: 'all ' + animateDuration + 'ms', transform: 'translate(0,0)'});
- clearTimeout(target.animated);
- target.animated = setTimeout(function () {
- Object.assign(target.style, {originProps: originProps});
- target.animated = false;
- }, animateDuration);
- }
- function getEventHost (e) {
- // on touchend event, we have to use `e.changedTouches`
- // see http://stackoverflow.com/questions/7192563/touchend-event-properties
- // see https://github.com/bevacqua/dragula/issues/34
- if (e.targetTouches && e.targetTouches.length) {
- return e.targetTouches[0];
- }
- if (e.changedTouches && e.changedTouches.length) {
- return e.changedTouches[0];
- }
- return e;
- }
- function getCoord (coord, e) {
- var host = getEventHost(e);
- var missMap = {
- pageX: 'clientX', // IE8
- pageY: 'clientY' // IE8
- };
- if (coord in missMap && !(coord in host) && missMap[coord] in host) {
- coord = missMap[coord];
- }
- return host[coord];
- }
- module.exports = dragula;
|