diff --git a/packages/devextreme/js/viz/chart_components/zoom_and_pan.js b/packages/devextreme/js/viz/chart_components/zoom_and_pan.js index c6493f5a425d..e7bf89b71b2a 100644 --- a/packages/devextreme/js/viz/chart_components/zoom_and_pan.js +++ b/packages/devextreme/js/viz/chart_components/zoom_and_pan.js @@ -24,6 +24,7 @@ const SCROLL_BAR_END_EVENT_NAME = 'dxc-scroll-end' + EVENTS_NS; const GESTURE_TIMEOUT = 300; const MIN_DRAG_DELTA = 5; +const SCROLL_PREVENTION_TIMEOUT = 500; const _min = Math.min; const _max = Math.max; @@ -114,6 +115,7 @@ export default { init: function() { const chart = this; const renderer = this._renderer; + let lastWheelTimer; function getAxesCopy(zoomAndPan, actionField) { let axes = []; @@ -286,13 +288,22 @@ export default { return e.offset[coordField] - actionData.offset[coordField]; } - function preventDefaults(e) { + function setLastWheelTimer() { + clearTimeout(lastWheelTimer); + lastWheelTimer = setTimeout(() => { + lastWheelTimer = undefined; + }, SCROLL_PREVENTION_TIMEOUT); + } + + function preventDefaults(e, stopChartHandler = true) { if(e.cancelable !== false) { e.preventDefault(); e.stopPropagation(); } - chart._stopCurrentHandling(); + if(stopChartHandler) { + chart._stopCurrentHandling(); + } } const zoomAndPan = { @@ -496,12 +507,20 @@ export default { axesZoomed |= canZoom && zoomAxes(e, chart._argumentAxes, getRange, e.delta > 0, { coord: rotated ? coords.y : coords.x }, chart.getArgumentAxis()); } + const isPanningAvailable = targetAxes ? isAxisAvailablePanning(targetAxes) : zoomAndPan.panningVisualRangeEnabled(); + if(axesZoomed) { chart._requestChange(['VISUAL_RANGE']); - if(targetAxes && isAxisAvailablePanning(targetAxes) || !targetAxes && zoomAndPan.panningVisualRangeEnabled()) { + if(isPanningAvailable) { preventDefaults(e); // T249548 + setLastWheelTimer(); } } + + if((!axesZoomed || !isPanningAvailable) && lastWheelTimer) { + preventDefaults(e, false); + setLastWheelTimer(); + } }, cleanup: function() { renderer.root.off(EVENTS_NS); diff --git a/packages/devextreme/testing/tests/DevExpress.viz.charts/zoomAndPan.tests.js b/packages/devextreme/testing/tests/DevExpress.viz.charts/zoomAndPan.tests.js index 918337a47520..112aa78a24c0 100644 --- a/packages/devextreme/testing/tests/DevExpress.viz.charts/zoomAndPan.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.viz.charts/zoomAndPan.tests.js @@ -5,6 +5,7 @@ import 'viz/chart'; const CHART_SVG_SELECTOR = 'svg.dxc.dxc-chart'; const TOOLTIP_CLASS = 'dxc-tooltip'; +const SCROLL_PREVENTION_TIMEOUT = 500; const dataSource = (() => { const arr = []; @@ -3211,6 +3212,57 @@ QUnit.test('Default behavior - no prevent. On mouse wheel', function(assert) { assert.equal(this.trackerStopHandling.callCount, 0); }); +QUnit.test('On mouse wheel. Should prevent scroll page after max zoom level reached (T1314606)', function(assert) { + const preventDefault = sinon.spy(); + const stopPropagation = sinon.spy(); + const onZoomEnd = sinon.spy(); + const wholeRange = { startValue: 0, endValue: 5 }; + const chart = this.createChart({ + argumentAxis: { + visualRange: { + startValue: 0.1, + endValue: 4.9 + }, + wholeRange, + }, + zoomAndPan: { + argumentAxis: 'zoom', + allowMouseWheel: true + }, + onZoomEnd: onZoomEnd + }); + + const $root = $(chart._renderer.root.element); + + $root.trigger(new $.Event('dxmousewheel', { d: -10, pageX: 200, pageY: 250, preventDefault: preventDefault, stopPropagation: stopPropagation })); + + assert.deepEqual(onZoomEnd.getCall(0).args[0].range, wholeRange, 'chart zoomed out to wholeRange'); + assert.strictEqual(preventDefault.callCount, 1, 'after zoom e.preventDefault called'); + assert.strictEqual(stopPropagation.callCount, 1, 'after zoom e.stopPropagation called'); + assert.strictEqual(this.trackerStopHandling.callCount, 1, 'chart stopped wheel event handling'); + + this.clock.tick(SCROLL_PREVENTION_TIMEOUT / 2); + $root.trigger(new $.Event('dxmousewheel', { d: -10, pageX: 200, pageY: 250, preventDefault: preventDefault, stopPropagation: stopPropagation })); + + assert.equal(preventDefault.callCount, 2, 'e.preventDefault called'); + assert.equal(stopPropagation.callCount, 2, 'e.stopPropagation called'); + assert.equal(this.trackerStopHandling.callCount, 1, 'chart not passed event handling in SCROLL_PREVENTION_TIMEOUT window after zoom'); + + this.clock.tick(SCROLL_PREVENTION_TIMEOUT - 10); + $root.trigger(new $.Event('dxmousewheel', { d: -10, pageX: 200, pageY: 250, preventDefault: preventDefault, stopPropagation: stopPropagation })); + + assert.equal(preventDefault.callCount, 3, 'e.preventDefault called'); + assert.equal(stopPropagation.callCount, 3, 'e.stopPropagation called'); + assert.equal(this.trackerStopHandling.callCount, 1, 'chart not passed event handling in SCROLL_PREVENTION_TIMEOUT window after last wheel event on chart'); + + this.clock.tick(SCROLL_PREVENTION_TIMEOUT + 10); + $root.trigger(new $.Event('dxmousewheel', { d: -10, pageX: 200, pageY: 250, preventDefault: preventDefault, stopPropagation: stopPropagation })); + + assert.equal(preventDefault.callCount, 3, 'chart not prevents event handling after SCROLL_PREVENTION_TIMEOUT expires, e.preventDefault not called'); + assert.equal(stopPropagation.callCount, 3, 'chart not prevent event propagation after SCROLL_PREVENTION_TIMEOUT expires, e.stopPropagation not called'); + assert.equal(this.trackerStopHandling.callCount, 1, 'chart._stopCurrentHandling not called after SCROLL_PREVENTION_TIMEOUT window'); +}); + QUnit.test('On pinch zoom', function(assert) { const preventDefault = sinon.spy(); const stopPropagation = sinon.spy();