import {
|
on,
|
off,
|
css,
|
throttle,
|
cancelThrottle,
|
scrollBy,
|
getParentAutoScrollElement,
|
expando,
|
getRect,
|
getWindowScrollingElement
|
} from '../../src/utils.js';
|
|
import Sortable from '../../src/Sortable.js';
|
|
import { Edge, IE11OrLess, Safari } from '../../src/BrowserInfo.js';
|
|
let autoScrolls = [],
|
scrollEl,
|
scrollRootEl,
|
scrolling = false,
|
lastAutoScrollX,
|
lastAutoScrollY,
|
touchEvt,
|
pointerElemChangedInterval;
|
|
function AutoScrollPlugin() {
|
|
function AutoScroll() {
|
this.defaults = {
|
scroll: true,
|
scrollSensitivity: 30,
|
scrollSpeed: 10,
|
bubbleScroll: true
|
};
|
|
// Bind all private methods
|
for (let fn in this) {
|
if (fn.charAt(0) === '_' && typeof this[fn] === 'function') {
|
this[fn] = this[fn].bind(this);
|
}
|
}
|
}
|
|
AutoScroll.prototype = {
|
dragStarted({ originalEvent }) {
|
if (this.sortable.nativeDraggable) {
|
on(document, 'dragover', this._handleAutoScroll);
|
} else {
|
if (this.options.supportPointer) {
|
on(document, 'pointermove', this._handleFallbackAutoScroll);
|
} else if (originalEvent.touches) {
|
on(document, 'touchmove', this._handleFallbackAutoScroll);
|
} else {
|
on(document, 'mousemove', this._handleFallbackAutoScroll);
|
}
|
}
|
},
|
|
dragOverCompleted({ originalEvent }) {
|
// For when bubbling is canceled and using fallback (fallback 'touchmove' always reached)
|
if (!this.options.dragOverBubble && !originalEvent.rootEl) {
|
this._handleAutoScroll(originalEvent);
|
}
|
},
|
|
drop() {
|
if (this.sortable.nativeDraggable) {
|
off(document, 'dragover', this._handleAutoScroll);
|
} else {
|
off(document, 'pointermove', this._handleFallbackAutoScroll);
|
off(document, 'touchmove', this._handleFallbackAutoScroll);
|
off(document, 'mousemove', this._handleFallbackAutoScroll);
|
}
|
|
clearPointerElemChangedInterval();
|
clearAutoScrolls();
|
cancelThrottle();
|
},
|
|
nulling() {
|
touchEvt =
|
scrollRootEl =
|
scrollEl =
|
scrolling =
|
pointerElemChangedInterval =
|
lastAutoScrollX =
|
lastAutoScrollY = null;
|
|
autoScrolls.length = 0;
|
},
|
|
_handleFallbackAutoScroll(evt) {
|
this._handleAutoScroll(evt, true);
|
},
|
|
_handleAutoScroll(evt, fallback) {
|
const x = (evt.touches ? evt.touches[0] : evt).clientX,
|
y = (evt.touches ? evt.touches[0] : evt).clientY,
|
|
elem = document.elementFromPoint(x, y);
|
|
touchEvt = evt;
|
|
// IE does not seem to have native autoscroll,
|
// Edge's autoscroll seems too conditional,
|
// MACOS Safari does not have autoscroll,
|
// Firefox and Chrome are good
|
if (fallback || Edge || IE11OrLess || Safari) {
|
autoScroll(evt, this.options, elem, fallback);
|
|
// Listener for pointer element change
|
let ogElemScroller = getParentAutoScrollElement(elem, true);
|
if (
|
scrolling &&
|
(
|
!pointerElemChangedInterval ||
|
x !== lastAutoScrollX ||
|
y !== lastAutoScrollY
|
)
|
) {
|
pointerElemChangedInterval && clearPointerElemChangedInterval();
|
// Detect for pointer elem change, emulating native DnD behaviour
|
pointerElemChangedInterval = setInterval(() => {
|
let newElem = getParentAutoScrollElement(document.elementFromPoint(x, y), true);
|
if (newElem !== ogElemScroller) {
|
ogElemScroller = newElem;
|
clearAutoScrolls();
|
}
|
autoScroll(evt, this.options, newElem, fallback);
|
}, 10);
|
lastAutoScrollX = x;
|
lastAutoScrollY = y;
|
}
|
} else {
|
// if DnD is enabled (and browser has good autoscrolling), first autoscroll will already scroll, so get parent autoscroll of first autoscroll
|
if (!this.options.bubbleScroll || getParentAutoScrollElement(elem, true) === getWindowScrollingElement()) {
|
clearAutoScrolls();
|
return;
|
}
|
autoScroll(evt, this.options, getParentAutoScrollElement(elem, false), false);
|
}
|
}
|
};
|
|
return Object.assign(AutoScroll, {
|
pluginName: 'scroll',
|
initializeByDefault: true
|
});
|
}
|
|
function clearAutoScrolls() {
|
autoScrolls.forEach(function(autoScroll) {
|
clearInterval(autoScroll.pid);
|
});
|
autoScrolls = [];
|
}
|
|
function clearPointerElemChangedInterval() {
|
clearInterval(pointerElemChangedInterval);
|
}
|
|
|
const autoScroll = throttle(function(evt, options, rootEl, isFallback) {
|
// Bug: https://bugzilla.mozilla.org/show_bug.cgi?id=505521
|
if (!options.scroll) return;
|
const x = (evt.touches ? evt.touches[0] : evt).clientX,
|
y = (evt.touches ? evt.touches[0] : evt).clientY,
|
sens = options.scrollSensitivity,
|
speed = options.scrollSpeed,
|
winScroller = getWindowScrollingElement();
|
|
let scrollThisInstance = false,
|
scrollCustomFn;
|
|
// New scroll root, set scrollEl
|
if (scrollRootEl !== rootEl) {
|
scrollRootEl = rootEl;
|
|
clearAutoScrolls();
|
|
scrollEl = options.scroll;
|
scrollCustomFn = options.scrollFn;
|
|
if (scrollEl === true) {
|
scrollEl = getParentAutoScrollElement(rootEl, true);
|
}
|
}
|
|
|
let layersOut = 0;
|
let currentParent = scrollEl;
|
do {
|
let el = currentParent,
|
rect = getRect(el),
|
|
top = rect.top,
|
bottom = rect.bottom,
|
left = rect.left,
|
right = rect.right,
|
|
width = rect.width,
|
height = rect.height,
|
|
canScrollX,
|
canScrollY,
|
|
scrollWidth = el.scrollWidth,
|
scrollHeight = el.scrollHeight,
|
|
elCSS = css(el),
|
|
scrollPosX = el.scrollLeft,
|
scrollPosY = el.scrollTop;
|
|
|
if (el === winScroller) {
|
canScrollX = width < scrollWidth && (elCSS.overflowX === 'auto' || elCSS.overflowX === 'scroll' || elCSS.overflowX === 'visible');
|
canScrollY = height < scrollHeight && (elCSS.overflowY === 'auto' || elCSS.overflowY === 'scroll' || elCSS.overflowY === 'visible');
|
} else {
|
canScrollX = width < scrollWidth && (elCSS.overflowX === 'auto' || elCSS.overflowX === 'scroll');
|
canScrollY = height < scrollHeight && (elCSS.overflowY === 'auto' || elCSS.overflowY === 'scroll');
|
}
|
|
let vx = canScrollX && (Math.abs(right - x) <= sens && (scrollPosX + width) < scrollWidth) - (Math.abs(left - x) <= sens && !!scrollPosX);
|
let vy = canScrollY && (Math.abs(bottom - y) <= sens && (scrollPosY + height) < scrollHeight) - (Math.abs(top - y) <= sens && !!scrollPosY);
|
|
|
if (!autoScrolls[layersOut]) {
|
for (let i = 0; i <= layersOut; i++) {
|
if (!autoScrolls[i]) {
|
autoScrolls[i] = {};
|
}
|
}
|
}
|
|
if (autoScrolls[layersOut].vx != vx || autoScrolls[layersOut].vy != vy || autoScrolls[layersOut].el !== el) {
|
autoScrolls[layersOut].el = el;
|
autoScrolls[layersOut].vx = vx;
|
autoScrolls[layersOut].vy = vy;
|
|
clearInterval(autoScrolls[layersOut].pid);
|
|
if (vx != 0 || vy != 0) {
|
scrollThisInstance = true;
|
/* jshint loopfunc:true */
|
autoScrolls[layersOut].pid = setInterval((function () {
|
// emulate drag over during autoscroll (fallback), emulating native DnD behaviour
|
if (isFallback && this.layer === 0) {
|
Sortable.active._onTouchMove(touchEvt); // To move ghost if it is positioned absolutely
|
}
|
let scrollOffsetY = autoScrolls[this.layer].vy ? autoScrolls[this.layer].vy * speed : 0;
|
let scrollOffsetX = autoScrolls[this.layer].vx ? autoScrolls[this.layer].vx * speed : 0;
|
|
if (typeof(scrollCustomFn) === 'function') {
|
if (scrollCustomFn.call(Sortable.dragged.parentNode[expando], scrollOffsetX, scrollOffsetY, evt, touchEvt, autoScrolls[this.layer].el) !== 'continue') {
|
return;
|
}
|
}
|
|
scrollBy(autoScrolls[this.layer].el, scrollOffsetX, scrollOffsetY);
|
}).bind({layer: layersOut}), 24);
|
}
|
}
|
layersOut++;
|
} while (options.bubbleScroll && currentParent !== winScroller && (currentParent = getParentAutoScrollElement(currentParent, false)));
|
scrolling = scrollThisInstance; // in case another function catches scrolling as false in between when it is not
|
}, 30);
|
|
export default AutoScrollPlugin;
|