123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270 |
- 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;
|