aboutsummaryrefslogtreecommitdiffstats
path: root/library/Sortable/plugins/AutoScroll
diff options
context:
space:
mode:
Diffstat (limited to 'library/Sortable/plugins/AutoScroll')
-rw-r--r--library/Sortable/plugins/AutoScroll/AutoScroll.js271
-rw-r--r--library/Sortable/plugins/AutoScroll/README.md110
-rw-r--r--library/Sortable/plugins/AutoScroll/index.js1
3 files changed, 382 insertions, 0 deletions
diff --git a/library/Sortable/plugins/AutoScroll/AutoScroll.js b/library/Sortable/plugins/AutoScroll/AutoScroll.js
new file mode 100644
index 000000000..48ac81e28
--- /dev/null
+++ b/library/Sortable/plugins/AutoScroll/AutoScroll.js
@@ -0,0 +1,271 @@
+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,
+ forceAutoScrollFallback: false,
+ 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 || this.options.forceAutoScrollFallback || 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;
diff --git a/library/Sortable/plugins/AutoScroll/README.md b/library/Sortable/plugins/AutoScroll/README.md
new file mode 100644
index 000000000..dd77aede6
--- /dev/null
+++ b/library/Sortable/plugins/AutoScroll/README.md
@@ -0,0 +1,110 @@
+## AutoScroll
+This plugin allows for the page to automatically scroll during dragging near a scrollable element's edge on mobile devices and IE9 (or whenever fallback is enabled), and also enhances most browser's native drag-and-drop autoscrolling.
+Demo:
+ - `window`: https://jsbin.com/dosilir/edit?js,output
+ - `overflow: hidden`: https://jsbin.com/xecihez/edit?html,js,output
+
+**This plugin is a default plugin, and is included in the default UMD and ESM builds of Sortable**
+
+
+---
+
+
+### Mounting
+```js
+import { Sortable, AutoScroll } from 'sortablejs';
+
+Sortable.mount(new AutoScroll());
+```
+
+
+---
+
+
+### Options
+
+```js
+new Sortable(el, {
+ scroll: true, // Enable the plugin. Can be HTMLElement.
+ forceAutoscrollFallback: false, // force autoscroll plugin to enable even when native browser autoscroll is available
+ scrollFn: function(offsetX, offsetY, originalEvent, touchEvt, hoverTargetEl) { ... }, // if you have custom scrollbar scrollFn may be used for autoscrolling
+ scrollSensitivity: 30, // px, how near the mouse must be to an edge to start scrolling.
+ scrollSpeed: 10, // px, speed of the scrolling
+ bubbleScroll: true // apply autoscroll to all parent elements, allowing for easier movement
+});
+```
+
+
+---
+
+
+#### `scroll` option
+Enables the plugin. Defaults to `true`. May also be set to an HTMLElement which will be where autoscrolling is rooted.
+
+**Note: Just because this plugin is enabled does not mean that it will always be used for autoscrolling. Some browsers have native drag and drop autoscroll, in which case this autoscroll plugin won't be invoked. If you wish to have this always be invoked for autoscrolling, set the option `forceAutoScrollFallback` to `true`.**
+
+Demo:
+ - `window`: https://jsbin.com/dosilir/edit?js,output
+ - `overflow: hidden`: https://jsbin.com/xecihez/edit?html,js,output
+
+
+---
+
+
+#### `forceAutoScrollFallback` option
+Enables sortable's autoscroll even when the browser can handle it (with native drag and drop). Defaults to `false`. This will not disable the native autoscrolling. Note that setting `forceFallback: true` in the sortable options will also enable this.
+
+
+---
+
+
+#### `scrollFn` option
+Useful when you have custom scrollbar with dedicated scroll function.
+Defines a function that will be used for autoscrolling. Sortable uses el.scrollTop/el.scrollLeft by default. Set this option if you wish to handle it differently.
+This function should return `'continue'` if it wishes to allow Sortable's native autoscrolling, otherwise Sortable will not scroll anything if this option is set.
+
+**Note that this option will only work if Sortable's autoscroll function is invoked.**
+
+It is invoked if any of the following are true:
+ - The `forceFallback: true` option is set
+ - It is a mobile device
+ - The browser is either Safari, Internet Explorer, or Edge
+
+
+---
+
+
+#### `scrollSensitivity` option
+Defines how near the mouse must be to an edge to start scrolling.
+
+**Note that this option will only work if Sortable's autoscroll function is invoked.**
+
+It is invoked if any of the following are true:
+ - The `forceFallback: true` option is set
+ - It is a mobile device
+ - The browser is either Safari, Internet Explorer, or Edge
+
+
+---
+
+
+#### `scrollSpeed` option
+The speed at which the window should scroll once the mouse pointer gets within the `scrollSensitivity` distance.
+
+**Note that this option will only work if Sortable's autoscroll function is invoked.**
+
+It is invoked if any of the following are true:
+ - The `forceFallback: true` option is set
+ - It is a mobile device
+ - The browser is either Safari, Internet Explorer, or Edge
+
+---
+
+
+#### `bubbleScroll` option
+If set to `true`, the normal `autoscroll` function will also be applied to all parent elements of the element the user is dragging over.
+
+Demo: https://jsbin.com/kesewor/edit?html,js,output
+
+
+---
diff --git a/library/Sortable/plugins/AutoScroll/index.js b/library/Sortable/plugins/AutoScroll/index.js
new file mode 100644
index 000000000..cc79f7e24
--- /dev/null
+++ b/library/Sortable/plugins/AutoScroll/index.js
@@ -0,0 +1 @@
+export { default } from './AutoScroll.js';