diff --git a/package-lock.json b/package-lock.json index c8ed4701bf4..7ff5a15252e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1973,6 +1973,17 @@ "sha.js": "2.4.10" } }, + "cross-spawn": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", + "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=", + "dev": true, + "requires": { + "lru-cache": "4.1.1", + "shebang-command": "1.2.0", + "which": "1.3.0" + } + }, "cryptiles": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-3.1.2.tgz", @@ -2691,9 +2702,9 @@ } }, "ecstatic": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/ecstatic/-/ecstatic-3.1.1.tgz", - "integrity": "sha512-D9UcjcxDMMqjaQxC0mSsFh/IjJSdiZVPnHrhjHuKXlhLByk5QGGPX1GUIDIjRzhTq4UDCPYwWblw79VBEh3r1w==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/ecstatic/-/ecstatic-3.2.0.tgz", + "integrity": "sha512-Goilx/2cfU9vvfQjgtNgc2VmJAD8CasQ6rZDqCd2u4Hsyd/qFET6nBf60jiHodevR3nl3IGzNKtrzPXWP88utQ==", "dev": true, "requires": { "he": "1.1.1", @@ -2909,9 +2920,9 @@ } }, "eslint": { - "version": "4.16.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-4.16.0.tgz", - "integrity": "sha512-YVXV4bDhNoHHcv0qzU4Meof7/P26B4EuaktMi5L1Tnt52Aov85KmYA8c5D+xyZr/BkhvwUqr011jDSD/QTULxg==", + "version": "4.17.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-4.17.0.tgz", + "integrity": "sha512-AyxBUCANU/o/xC0ijGMKavo5Ls3oK6xykiOITlMdjFjrKOsqLrA7Nf5cnrDgcKrHzBirclAZt63XO7YZlVUPwA==", "dev": true, "requires": { "ajv": "5.5.2", @@ -2923,7 +2934,7 @@ "doctrine": "2.1.0", "eslint-scope": "3.7.1", "eslint-visitor-keys": "1.0.0", - "espree": "3.5.2", + "espree": "3.5.3", "esquery": "1.0.0", "esutils": "2.0.2", "file-entry-cache": "2.0.0", @@ -2959,17 +2970,6 @@ "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", "dev": true }, - "cross-spawn": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", - "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=", - "dev": true, - "requires": { - "lru-cache": "4.1.1", - "shebang-command": "1.2.0", - "which": "1.3.0" - } - }, "debug": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", @@ -3027,13 +3027,21 @@ "dev": true }, "espree": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/espree/-/espree-3.5.2.tgz", - "integrity": "sha512-sadKeYwaR/aJ3stC2CdvgXu1T16TdYN+qwCpcWbMnGJ8s0zNWemzrvb2GbD4OhmJ/fwpJjudThAlLobGbWZbCQ==", + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/espree/-/espree-3.5.3.tgz", + "integrity": "sha512-Zy3tAJDORxQZLl2baguiRU1syPERAIg0L+JB2MWorORgTu/CplzvxS9WWA7Xh4+Q+eOQihNs/1o1Xep8cvCxWQ==", "dev": true, "requires": { - "acorn": "5.3.0", + "acorn": "5.4.1", "acorn-jsx": "3.0.1" + }, + "dependencies": { + "acorn": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.4.1.tgz", + "integrity": "sha512-XLmq3H/BVvW6/GbxKryGxWORz1ebilSsUDlyC27bXhWGWAZWkGwS6FLHjOlwFXNFoWFQEO/Df4u0YYd0K3BQgQ==", + "dev": true + } } }, "esprima": { @@ -7299,48 +7307,12 @@ "dev": true }, "log-symbols": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-1.0.2.tgz", - "integrity": "sha1-N2/3tY6jCGoPCfrMdGF+ylAeGhg=", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-2.2.0.tgz", + "integrity": "sha512-VeIAFslyIerEJLXHziedo2basKbMKtTw3vfn5IzG0XTjhAVEJyNHnL2p7vc+wBDSdQuUpNw3M2u6xb9QsAY5Eg==", "dev": true, "requires": { - "chalk": "1.1.3" - }, - "dependencies": { - "ansi-styles": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", - "dev": true - }, - "chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", - "dev": true, - "requires": { - "ansi-styles": "2.2.1", - "escape-string-regexp": "1.0.5", - "has-ansi": "2.0.0", - "strip-ansi": "3.0.1", - "supports-color": "2.0.0" - } - }, - "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "dev": true, - "requires": { - "ansi-regex": "2.1.1" - } - }, - "supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", - "dev": true - } + "chalk": "2.3.0" } }, "log4js": { @@ -7661,9 +7633,9 @@ } }, "madge": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/madge/-/madge-3.0.0.tgz", - "integrity": "sha512-fqMlHRGo3CHJ+e1+cHuoMC6YtpPEKhCC0JZnr+7tdXhRgXEObP7V1VLhggDJy3LUKAy0Yv+azP9dnNxAnfwHqw==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/madge/-/madge-3.0.1.tgz", + "integrity": "sha512-6aR8+aNJMQjlmd0oSkdEPPdaLn9S0Yjyux/CQlFCOfIknWZn28Gh1HPAGMj2GfNa+Sj5ZNoqepAEtZgm49oPjg==", "dev": true, "requires": { "chalk": "2.3.0", @@ -7673,7 +7645,7 @@ "dependency-tree": "6.0.0", "graphviz": "0.0.8", "mz": "2.7.0", - "ora": "1.3.0", + "ora": "1.4.0", "pluralize": "7.0.0", "pretty-ms": "3.1.0", "rc": "1.2.5", @@ -8058,9 +8030,9 @@ } }, "mimic-fn": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.1.0.tgz", - "integrity": "sha1-5md4PZLonb00KBi1IwudYqZyrRg=", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", + "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==", "dev": true }, "minify-stream": { @@ -9005,7 +8977,7 @@ "integrity": "sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=", "dev": true, "requires": { - "mimic-fn": "1.1.0" + "mimic-fn": "1.2.0" } }, "open": { @@ -9057,51 +9029,15 @@ } }, "ora": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/ora/-/ora-1.3.0.tgz", - "integrity": "sha1-gAeN0rkqk0r2ajrXKluRBpTt5Ro=", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/ora/-/ora-1.4.0.tgz", + "integrity": "sha512-iMK1DOQxzzh2MBlVsU42G80mnrvUhqsMh74phHtDlrcTZPK0pH6o7l7DRshK+0YsxDyEuaOkziVdvM3T0QTzpw==", "dev": true, "requires": { - "chalk": "1.1.3", + "chalk": "2.3.0", "cli-cursor": "2.1.0", "cli-spinners": "1.1.0", - "log-symbols": "1.0.2" - }, - "dependencies": { - "ansi-styles": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", - "dev": true - }, - "chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", - "dev": true, - "requires": { - "ansi-styles": "2.2.1", - "escape-string-regexp": "1.0.5", - "has-ansi": "2.0.0", - "strip-ansi": "3.0.1", - "supports-color": "2.0.0" - } - }, - "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "dev": true, - "requires": { - "ansi-regex": "2.1.1" - } - }, - "supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", - "dev": true - } + "log-symbols": "2.2.0" } }, "orbit-camera-controller": { diff --git a/package.json b/package.json index a8ca757da31..4015757365e 100644 --- a/package.json +++ b/package.json @@ -85,6 +85,7 @@ "gl-surface3d": "^1.3.4", "glslify": "^6.1.0", "has-hover": "^1.0.1", + "has-passive-events": "^1.0.0", "kdgrass": "^1.0.1", "mapbox-gl": "^0.22.0", "matrix-camera-controller": "^2.1.3", @@ -117,8 +118,8 @@ "browserify-transform-tools": "^1.7.0", "check-node-version": "^3.2.0", "deep-equal": "^1.0.1", - "ecstatic": "^3.1.1", - "eslint": "^4.16.0", + "ecstatic": "^3.2.0", + "eslint": "^4.17.0", "falafel": "^2.0.0", "fs-extra": "^2.0.0", "fuse.js": "^3.2.0", @@ -137,7 +138,7 @@ "karma-jasmine-spec-tags": "^1.0.1", "karma-spec-reporter": "0.0.32", "karma-verbose-reporter": "0.0.6", - "madge": "^3.0.0", + "madge": "^3.0.1", "minify-stream": "^1.1.0", "minimist": "^1.2.0", "node-sass": "^4.7.2", diff --git a/src/components/dragelement/index.js b/src/components/dragelement/index.js index 57ff1f554ee..90785a2eb0d 100644 --- a/src/components/dragelement/index.js +++ b/src/components/dragelement/index.js @@ -11,6 +11,7 @@ var mouseOffset = require('mouse-event-offset'); var hasHover = require('has-hover'); +var supportsPassive = require('has-passive-events'); var Plotly = require('../../plotly'); var Lib = require('../../lib'); @@ -27,7 +28,6 @@ var unhover = require('./unhover'); dragElement.unhover = unhover.wrapped; dragElement.unhoverRaw = unhover.raw; -var supportsPassive = Lib.eventListenerOptionsSupported(); /** * Abstracts click & drag interactions @@ -124,6 +124,8 @@ dragElement.init = function init(options) { var clampFn = options.clampFn || _clampFn; function onStart(e) { + e.preventDefault(); + // make dragging and dragged into properties of gd // so that others can look at and modify them gd._dragged = false; @@ -164,10 +166,12 @@ dragElement.init = function init(options) { document.addEventListener('touchmove', onMove); document.addEventListener('touchend', onDone); - return Lib.pauseEvent(e); + return; } function onMove(e) { + e.preventDefault(); + var offset = pointerOffset(e); var minDrag = options.minDrag || constants.MINDRAG; var dxdy = clampFn(offset[0] - startX, offset[1] - startY, minDrag); @@ -181,7 +185,7 @@ dragElement.init = function init(options) { if(gd._dragged && options.moveFn && !rightClick) options.moveFn(dx, dy); - return Lib.pauseEvent(e); + return; } function onDone(e) { @@ -190,6 +194,8 @@ dragElement.init = function init(options) { document.removeEventListener('touchmove', onMove); document.removeEventListener('touchend', onDone); + e.preventDefault(); + if(hasHover) { Lib.removeElement(dragCover); } @@ -246,7 +252,7 @@ dragElement.init = function init(options) { gd._dragged = false; - return Lib.pauseEvent(e); + return; } }; diff --git a/src/lib/index.js b/src/lib/index.js index 7c44a2c8d6e..ce9170ffbe5 100644 --- a/src/lib/index.js +++ b/src/lib/index.js @@ -150,20 +150,6 @@ lib.swapAttrs = function(cont, attrList, part1, part2) { } }; -/** - * to prevent event bubbling, in particular text selection during drag. - * see http://stackoverflow.com/questions/5429827/ - * how-can-i-prevent-text-element-selection-with-cursor-drag - * for maximum effect use: - * return pauseEvent(e); - */ -lib.pauseEvent = function(e) { - if(e.stopPropagation) e.stopPropagation(); - if(e.preventDefault) e.preventDefault(); - e.cancelBubble = true; - return false; -}; - /** * SVG painter's algo worked around with reinsertion */ @@ -890,25 +876,3 @@ lib.subplotSort = function(a, b) { } return numB - numA; }; - -/* - * test if event listener options supported - */ -lib.eventListenerOptionsSupported = function() { - var supported = false; - - try { - var opts = Object.defineProperty({}, 'passive', { - get: function() { - supported = true; - } - }); - - window.addEventListener('test', null, opts); - window.removeEventListener('test', null, opts); - } catch(e) { - supported = false; - } - - return supported; -}; diff --git a/src/plots/cartesian/dragbox.js b/src/plots/cartesian/dragbox.js index 29af0cb593c..3a7bcbea755 100644 --- a/src/plots/cartesian/dragbox.js +++ b/src/plots/cartesian/dragbox.js @@ -11,6 +11,7 @@ var d3 = require('d3'); var tinycolor = require('tinycolor2'); +var supportsPassive = require('has-passive-events'); var Plotly = require('../../plotly'); var Registry = require('../../registry'); @@ -34,7 +35,6 @@ var constants = require('./constants'); var MINDRAG = constants.MINDRAG; var MINZOOM = constants.MINZOOM; -var supportsPassive = Lib.eventListenerOptionsSupported(); // flag for showing "doubleclick to zoom out" only at the beginning var SHOWZOOMOUTTIP = true; @@ -360,7 +360,9 @@ function makeDragBox(gd, plotinfo, x, y, w, h, ns, ew) { // If a transition is in progress, then disable any behavior: if(gd._transitioningWithDuration) { - return Lib.pauseEvent(e); + e.preventDefault(); + e.stopPropagation(); + return; } var pc = gd.querySelector('.plotly'); @@ -434,7 +436,8 @@ function makeDragBox(gd, plotinfo, x, y, w, h, ns, ew) { dragTail(zoomMode); }, REDRAWDELAY); - return Lib.pauseEvent(e); + e.preventDefault(); + return; } // everything but the corners gets wheel zoom diff --git a/src/plots/gl2d/camera.js b/src/plots/gl2d/camera.js index 19c93f38f29..cad0080961d 100644 --- a/src/plots/gl2d/camera.js +++ b/src/plots/gl2d/camera.js @@ -13,6 +13,7 @@ var mouseChange = require('mouse-change'); var mouseWheel = require('mouse-wheel'); var mouseOffset = require('mouse-event-offset'); var cartesianConstants = require('../cartesian/constants'); +var hasPassive = require('has-passive-events'); module.exports = createCamera; @@ -63,15 +64,21 @@ function createCamera(scene) { var xy = mouseOffset(ev.changedTouches[0], element); handleInteraction(0, xy[0], xy[1]); handleInteraction(1, xy[0], xy[1]); - }); + + ev.preventDefault(); + }, hasPassive ? {passive: false} : false); element.addEventListener('touchmove', function(ev) { ev.preventDefault(); var xy = mouseOffset(ev.changedTouches[0], element); handleInteraction(1, xy[0], xy[1]); - }); - element.addEventListener('touchend', function() { + + ev.preventDefault(); + }, hasPassive ? {passive: false} : false); + element.addEventListener('touchend', function(ev) { handleInteraction(0, result.lastPos[0], result.lastPos[1]); - }); + + ev.preventDefault(); + }, hasPassive ? {passive: false} : false); function handleInteraction(buttons, x, y) { var dataBox = scene.calcDataBox(), @@ -287,7 +294,7 @@ function createCamera(scene) { scene.relayoutCallback(); return true; - }); + }, true); return result; } diff --git a/src/plots/gl3d/camera.js b/src/plots/gl3d/camera.js index bc6aac99a8f..3fe1f4ea570 100644 --- a/src/plots/gl3d/camera.js +++ b/src/plots/gl3d/camera.js @@ -15,6 +15,7 @@ var createView = require('3d-view'); var mouseChange = require('mouse-change'); var mouseWheel = require('mouse-wheel'); var mouseOffset = require('mouse-event-offset'); +var supportsPassive = require('has-passive-events'); function createCamera(element, options) { element = element || document.body; @@ -188,14 +189,20 @@ function createCamera(element, options) { var xy = mouseOffset(ev.changedTouches[0], element); handleInteraction(0, xy[0], xy[1], lastMods); handleInteraction(1, xy[0], xy[1], lastMods); - }); + + ev.preventDefault(); + }, supportsPassive ? {passive: false} : false); element.addEventListener('touchmove', function(ev) { var xy = mouseOffset(ev.changedTouches[0], element); handleInteraction(1, xy[0], xy[1], lastMods); - }); - element.addEventListener('touchend', function() { + + ev.preventDefault(); + }, supportsPassive ? {passive: false} : false); + element.addEventListener('touchend', function(ev) { handleInteraction(0, lastX, lastY, lastMods); - }); + + ev.preventDefault(); + }, supportsPassive ? {passive: false} : false); function handleInteraction(buttons, x, y, mods) { var keyBindingMode = camera.keyBindingMode; diff --git a/src/plots/gl3d/scene.js b/src/plots/gl3d/scene.js index 7d1d051b298..2ee604891f0 100644 --- a/src/plots/gl3d/scene.js +++ b/src/plots/gl3d/scene.js @@ -11,6 +11,7 @@ var createPlot = require('gl-plot3d'); var getContext = require('webgl-context'); +var passiveSupported = require('has-passive-events'); var Registry = require('../../registry'); var Lib = require('../../lib'); @@ -27,6 +28,7 @@ var createAxesOptions = require('./layout/convert'); var createSpikeOptions = require('./layout/spikes'); var computeTickMarks = require('./layout/tick_marks'); + var STATIC_CANVAS, STATIC_CONTEXT; function render(scene) { @@ -190,7 +192,7 @@ function initializeGLPlot(scene, fullLayout, canvas, gl) { }; scene.glplot.canvas.addEventListener('mouseup', relayoutCallback.bind(null, scene)); - scene.glplot.canvas.addEventListener('wheel', relayoutCallback.bind(null, scene)); + scene.glplot.canvas.addEventListener('wheel', relayoutCallback.bind(null, scene), passiveSupported ? {passive: false} : false); if(!scene.staticMode) { scene.glplot.canvas.addEventListener('webglcontextlost', function(ev) { diff --git a/test/jasmine/assets/mouse_event.js b/test/jasmine/assets/mouse_event.js index 5ae9356d0b0..311842902f3 100644 --- a/test/jasmine/assets/mouse_event.js +++ b/test/jasmine/assets/mouse_event.js @@ -4,7 +4,8 @@ module.exports = function(type, x, y, opts) { var fullOpts = { bubbles: true, clientX: x, - clientY: y + clientY: y, + cancelable: true }; // https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent diff --git a/test/jasmine/assets/touch_event.js b/test/jasmine/assets/touch_event.js index ea16b1db664..52fdf444847 100644 --- a/test/jasmine/assets/touch_event.js +++ b/test/jasmine/assets/touch_event.js @@ -19,7 +19,8 @@ module.exports = function(type, x, y, opts) { touches: [touchObj], targetTouches: [], changedTouches: [touchObj], - bubbles: true + bubbles: true, + cancelable: true }; if(opts && opts.altKey) { diff --git a/test/jasmine/tests/gl2d_plot_interact_test.js b/test/jasmine/tests/gl2d_plot_interact_test.js index b2a52c5db6a..8c1063f3c3d 100644 --- a/test/jasmine/tests/gl2d_plot_interact_test.js +++ b/test/jasmine/tests/gl2d_plot_interact_test.js @@ -10,6 +10,7 @@ var createGraphDiv = require('../assets/create_graph_div'); var destroyGraphDiv = require('../assets/destroy_graph_div'); var fail = require('../assets/fail_test'); var mouseEvent = require('../assets/mouse_event'); +var touchEvent = require('../assets/touch_event'); var drag = require('../assets/drag'); var selectButton = require('../assets/modebar_button'); var delay = require('../assets/delay'); @@ -647,6 +648,61 @@ describe('@gl Test gl2d plots', function() { .then(done); }); + + it('should not scroll document while panning', function(done) { + var mock = { + data: [ + { type: 'scattergl', y: [1, 2, 3], x: [1, 2, 3] } + ], + layout: { + width: 500, + height: 500 + } + }; + + var sceneTarget, relayoutCallback = jasmine.createSpy('relayoutCallback'); + + function scroll(target, amt) { + return new Promise(function(resolve) { + target.dispatchEvent(new WheelEvent('wheel', {deltaY: amt || 1, cancelable: true})); + setTimeout(resolve, 0); + }); + } + + function touchDrag(target, start, end) { + return new Promise(function(resolve) { + touchEvent('touchstart', start[0], start[1], {element: target}); + touchEvent('touchmove', end[0], end[1], {element: target}); + touchEvent('touchend', end[0], end[1], {element: target}); + setTimeout(resolve, 0); + }); + } + + function assertEvent(e) { + expect(e.defaultPrevented).toEqual(true); + relayoutCallback(); + } + + gd.addEventListener('touchstart', assertEvent); + gd.addEventListener('wheel', assertEvent); + + Plotly.plot(gd, mock) + .then(function() { + sceneTarget = gd.querySelector('.nsewdrag'); + + return touchDrag(sceneTarget, [100, 100], [0, 0]); + }) + .then(function() { + return scroll(sceneTarget); + }) + .then(function() { + expect(relayoutCallback).toHaveBeenCalledTimes(1); + + }) + .catch(fail) + .then(done); + }); + it('should restyle opacity', function(done) { // #2299 spyOn(ScatterGl, 'calc'); diff --git a/test/jasmine/tests/gl3d_plot_interact_test.js b/test/jasmine/tests/gl3d_plot_interact_test.js index 75cd632d040..df339ea19b6 100644 --- a/test/jasmine/tests/gl3d_plot_interact_test.js +++ b/test/jasmine/tests/gl3d_plot_interact_test.js @@ -9,6 +9,7 @@ var createGraphDiv = require('../assets/create_graph_div'); var destroyGraphDiv = require('../assets/destroy_graph_div'); var fail = require('../assets/fail_test'); var mouseEvent = require('../assets/mouse_event'); +var touchEvent = require('../assets/touch_event'); var selectButton = require('../assets/modebar_button'); var delay = require('../assets/delay'); @@ -651,26 +652,88 @@ describe('@gl Test gl3d modebar handlers', function() { }); describe('@gl Test gl3d drag and wheel interactions', function() { - var gd, relayoutCallback; + var gd; - function scroll(target) { + function scroll(target, amt) { return new Promise(function(resolve) { - target.dispatchEvent(new WheelEvent('wheel', {deltaY: 1})); + target.dispatchEvent(new WheelEvent('wheel', {deltaY: amt || 1, cancelable: true})); setTimeout(resolve, 0); }); } - function drag(target) { + function drag(target, start, end) { return new Promise(function(resolve) { - target.dispatchEvent(new MouseEvent('mousedown', {x: 0, y: 0})); - target.dispatchEvent(new MouseEvent('mousemove', { x: 100, y: 100})); - target.dispatchEvent(new MouseEvent('mouseup', { x: 100, y: 100})); + mouseEvent('mousedown', start[0], start[1], {element: target}); + mouseEvent('mousemove', end[0], end[1], {element: target}); + mouseEvent('mouseup', end[0], end[1], {element: target}); setTimeout(resolve, 0); }); } - beforeEach(function(done) { + function touchDrag(target, start, end) { + return new Promise(function(resolve) { + touchEvent('touchstart', start[0], start[1], {element: target}); + touchEvent('touchmove', end[0], end[1], {element: target}); + touchEvent('touchend', end[0], end[1], {element: target}); + setTimeout(resolve, 0); + }); + } + + beforeEach(function() { gd = createGraphDiv(); + jasmine.DEFAULT_TIMEOUT_INTERVAL = 3000; + }); + + afterEach(function() { + Plotly.purge(gd); + destroyGraphDiv(); + }); + + it('should not scroll document while panning', function(done) { + var mock = { + data: [ + { type: 'scatter3d' } + ], + layout: { + width: 500, + height: 500, + scene: { camera: { eye: { x: 0.1, y: 0.1, z: 1 }}} + } + }; + + var sceneTarget, relayoutCallback = jasmine.createSpy('relayoutCallback'); + + function assertEvent(e) { + expect(e.defaultPrevented).toEqual(true); + relayoutCallback(); + } + + gd.addEventListener('touchend', assertEvent); + gd.addEventListener('touchstart', assertEvent); + gd.addEventListener('touchmove', assertEvent); + gd.addEventListener('wheel', assertEvent); + + Plotly.plot(gd, mock) + .then(function() { + sceneTarget = gd.querySelector('.svg-container .gl-container #scene'); + + return touchDrag(sceneTarget, [100, 100], [0, 0]); + }) + .then(function() { + return drag(sceneTarget, [100, 100], [0, 0]); + }) + .then(function() { + return scroll(sceneTarget); + }) + .then(function() { + expect(relayoutCallback).toHaveBeenCalledTimes(3); + }) + .catch(fail) + .then(done); + }); + + it('should update the scene camera', function(done) { + var sceneLayout, sceneLayout2, sceneTarget, sceneTarget2, relayoutCallback; var mock = { data: [ @@ -684,31 +747,23 @@ describe('@gl Test gl3d drag and wheel interactions', function() { }; Plotly.plot(gd, mock) - .then(delay(20)) .then(function() { relayoutCallback = jasmine.createSpy('relayoutCallback'); gd.on('plotly_relayout', relayoutCallback); - }) - .then(done); - }); - afterEach(function() { - Plotly.purge(gd); - destroyGraphDiv(); - }); - - it('should update the scene camera', function(done) { - var sceneLayout = gd._fullLayout.scene, - sceneLayout2 = gd._fullLayout.scene2, - sceneTarget = gd.querySelector('.svg-container .gl-container #scene canvas'), + sceneLayout = gd._fullLayout.scene; + sceneLayout2 = gd._fullLayout.scene2; + sceneTarget = gd.querySelector('.svg-container .gl-container #scene canvas'); sceneTarget2 = gd.querySelector('.svg-container .gl-container #scene2 canvas'); - expect(sceneLayout.camera.eye) - .toEqual({x: 0.1, y: 0.1, z: 1}); - expect(sceneLayout2.camera.eye) - .toEqual({x: 2.5, y: 2.5, z: 2.5}); + expect(sceneLayout.camera.eye) + .toEqual({x: 0.1, y: 0.1, z: 1}); + expect(sceneLayout2.camera.eye) + .toEqual({x: 2.5, y: 2.5, z: 2.5}); - scroll(sceneTarget).then(function() { + return scroll(sceneTarget); + }) + .then(function() { expect(relayoutCallback).toHaveBeenCalledTimes(1); relayoutCallback.calls.reset(); @@ -718,13 +773,13 @@ describe('@gl Test gl3d drag and wheel interactions', function() { expect(relayoutCallback).toHaveBeenCalledTimes(1); relayoutCallback.calls.reset(); - return drag(sceneTarget2); + return drag(sceneTarget2, [0, 0], [100, 100]); }) .then(function() { expect(relayoutCallback).toHaveBeenCalledTimes(1); relayoutCallback.calls.reset(); - return drag(sceneTarget); + return drag(sceneTarget, [0, 0], [100, 100]); }) .then(function() { expect(relayoutCallback).toHaveBeenCalledTimes(1); @@ -739,10 +794,10 @@ describe('@gl Test gl3d drag and wheel interactions', function() { expect(relayoutCallback).toHaveBeenCalledTimes(1); relayoutCallback.calls.reset(); - return drag(sceneTarget); + return drag(sceneTarget, [0, 0], [100, 100]); }) .then(function() { - return drag(sceneTarget2); + return drag(sceneTarget2, [0, 0], [100, 100]); }) .then(function() { expect(relayoutCallback).toHaveBeenCalledTimes(0); @@ -756,14 +811,15 @@ describe('@gl Test gl3d drag and wheel interactions', function() { expect(relayoutCallback).toHaveBeenCalledTimes(1); relayoutCallback.calls.reset(); - return drag(sceneTarget); + return drag(sceneTarget, [0, 0], [100, 100]); }) .then(function() { - return drag(sceneTarget2); + return drag(sceneTarget2, [0, 0], [100, 100]); }) .then(function() { expect(relayoutCallback).toHaveBeenCalledTimes(2); }) + .catch(fail) .then(done); }); });