From a7e4a041a6ffce2d5835eeceb64c929cd80aacf8 Mon Sep 17 00:00:00 2001 From: Christophe Krebser Date: Fri, 18 Jul 2014 14:56:16 +0200 Subject: [PATCH] fix(ngTouch): leaving an swipe element must not terminate swiping when user leaves the area of the swiped element, it must keeps on swiping until until mouse is up. closes: #6821 --- src/ngTouch/swipe.js | 76 +++++++++++++++++----- test/ngTouch/directive/ngSwipeSpec.js | 92 +++++++++++++++++---------- 2 files changed, 117 insertions(+), 51 deletions(-) diff --git a/src/ngTouch/swipe.js b/src/ngTouch/swipe.js index 884e0800d83c..83f02741576b 100644 --- a/src/ngTouch/swipe.js +++ b/src/ngTouch/swipe.js @@ -21,7 +21,7 @@ * documentation for `bind` below. */ -ngTouch.factory('$swipe', [function() { +ngTouch.factory('$swipe', ['$window', function($window) { // The total distance in any direction before we make the call on swipe vs. scroll. var MOVE_BUFFER_RADIUS = 10; @@ -29,13 +29,17 @@ ngTouch.factory('$swipe', [function() { 'mouse': { start: 'mousedown', move: 'mousemove', - end: 'mouseup' + end: 'mouseup', + leave: 'mouseout', + enter: 'mouseover' }, 'touch': { start: 'touchstart', move: 'touchmove', end: 'touchend', - cancel: 'touchcancel' + cancel: 'touchcancel', + leave: 'touchleave', + enter: 'touchenter' } }; @@ -105,25 +109,47 @@ ngTouch.factory('$swipe', [function() { var lastPos; // Whether a swipe is active. var active = false; + // Whether mouse is outside element + var elementOutside = false; pointerTypes = pointerTypes || ['mouse', 'touch']; - element.on(getEvents(pointerTypes, 'start'), function(event) { + + function ifOutside(func) { + return function(event) { + if (!elementOutside) { + return; + } + func(event); + }; + } + + function swipeStart(event) { startCoords = getCoordinates(event); active = true; totalX = 0; totalY = 0; lastPos = startCoords; - eventHandlers['start'] && eventHandlers['start'](startCoords, event); - }); - var events = getEvents(pointerTypes, 'cancel'); - if (events) { - element.on(events, function(event) { - active = false; - eventHandlers['cancel'] && eventHandlers['cancel'](event); + + element.on(getEvents(pointerTypes, 'leave'), function() { + elementOutside = true; }); + element.on(getEvents(pointerTypes, 'enter'), function() { + elementOutside = false; + }); + eventHandlers['start'] && eventHandlers['start'](startCoords, event); } - element.on(getEvents(pointerTypes, 'move'), function(event) { + function swipeEnd(event) { + if (!active) return; + active = false; + + element.off(getEvents(pointerTypes, 'enter')); + element.off(getEvents(pointerTypes, 'leave')); + + eventHandlers['end'] && eventHandlers['end'](getCoordinates(event), event); + } + + function swipeMove(event) { if (!active) return; // Android will send a touchcancel if it thinks we're starting to scroll. @@ -155,13 +181,29 @@ ngTouch.factory('$swipe', [function() { event.preventDefault(); eventHandlers['move'] && eventHandlers['move'](coords, event); } - }); + } - element.on(getEvents(pointerTypes, 'end'), function(event) { - if (!active) return; + function swipeCancel(event) { + if (!active) { + return; + } active = false; - eventHandlers['end'] && eventHandlers['end'](getCoordinates(event), event); - }); + eventHandlers['cancel'] && eventHandlers['cancel'](event); + } + + function bindEvent(eventName, callback) { + var events = getEvents(pointerTypes, eventName); + if (events) { + element.on(events, callback); + angular.element($window).on(events, ifOutside(callback)); + } + } + + element.on(getEvents(pointerTypes, 'start'), swipeStart); + + bindEvent('cancel', swipeCancel); + bindEvent('move', swipeMove); + bindEvent('end', swipeEnd); } }; }]); diff --git a/test/ngTouch/directive/ngSwipeSpec.js b/test/ngTouch/directive/ngSwipeSpec.js index 5b8fa7537dd5..5c1c7d073359 100644 --- a/test/ngTouch/directive/ngSwipeSpec.js +++ b/test/ngTouch/directive/ngSwipeSpec.js @@ -1,7 +1,7 @@ 'use strict'; // Wrapper to abstract over using touch events or mouse events. -var swipeTests = function(description, restrictBrowsers, startEvent, moveEvent, endEvent) { +var swipeTests = function(description, restrictBrowsers, startEvent, moveEvent, endEvent, enterEvent, leaveEvent) { describe('ngSwipe with ' + description + ' events', function() { var element; @@ -36,11 +36,11 @@ var swipeTests = function(description, restrictBrowsers, startEvent, moveEvent, expect($rootScope.swiped).toBeUndefined(); browserTrigger(element, startEvent, { - keys : [], - x : 100, - y : 20 + keys: [], + x: 100, + y: 20 }); - browserTrigger(element, endEvent,{ + browserTrigger(element, endEvent, { keys: [], x: 20, y: 20 @@ -53,12 +53,12 @@ var swipeTests = function(description, restrictBrowsers, startEvent, moveEvent, $rootScope.$digest(); expect($rootScope.swiped).toBeUndefined(); - browserTrigger(element, startEvent,{ + browserTrigger(element, startEvent, { keys: [], x: 20, y: 20 }); - browserTrigger(element, endEvent,{ + browserTrigger(element, endEvent, { keys: [], x: 90, y: 20 @@ -72,11 +72,11 @@ var swipeTests = function(description, restrictBrowsers, startEvent, moveEvent, expect($rootScope.swiped).toBeUndefined(); browserTrigger(element, startEvent, { - keys : [], - x : 100, - y : 20 + keys: [], + x: 100, + y: 20 }); - browserTrigger(element, endEvent,{ + browserTrigger(element, endEvent, { keys: [], x: 20, y: 20 @@ -89,11 +89,11 @@ var swipeTests = function(description, restrictBrowsers, startEvent, moveEvent, $rootScope.$digest(); browserTrigger(element, startEvent, { - keys : [], - x : 100, - y : 20 + keys: [], + x: 100, + y: 20 }); - browserTrigger(element, endEvent,{ + browserTrigger(element, endEvent, { keys: [], x: 20, y: 20 @@ -108,17 +108,17 @@ var swipeTests = function(description, restrictBrowsers, startEvent, moveEvent, expect($rootScope.swiped).toBeUndefined(); - browserTrigger(element, startEvent,{ + browserTrigger(element, startEvent, { keys: [], x: 90, y: 20 }); - browserTrigger(element, moveEvent,{ + browserTrigger(element, moveEvent, { keys: [], x: 70, y: 200 }); - browserTrigger(element, endEvent,{ + browserTrigger(element, endEvent, { keys: [], x: 20, y: 20 @@ -134,12 +134,12 @@ var swipeTests = function(description, restrictBrowsers, startEvent, moveEvent, expect($rootScope.swiped).toBeUndefined(); - browserTrigger(element, startEvent,{ + browserTrigger(element, startEvent, { keys: [], x: 90, y: 20 }); - browserTrigger(element, endEvent,{ + browserTrigger(element, endEvent, { keys: [], x: 80, y: 20 @@ -148,25 +148,50 @@ var swipeTests = function(description, restrictBrowsers, startEvent, moveEvent, expect($rootScope.swiped).toBeUndefined(); })); - it('should not swipe if the swipe leaves the element', inject(function($rootScope, $compile, $rootElement) { + it('should swipe to the left if the swipe leaves the element', inject(function($rootScope, $compile, $rootElement, $window) { + element = $compile('
')($rootScope); + + $rootElement.append(element); + $rootScope.$digest(); + + expect($rootScope.swiped).toBeUndefined(); + + browserTrigger(element, startEvent, { + keys: [], + x: 100, + y: 20 + }); + browserTrigger(element, leaveEvent, {}); + browserTrigger(angular.element($window.document.body), endEvent, { + keys: [], + x: 20, + y: 20 + }); + + expect($rootScope.swiped).toBeDefined(); + })); + + it('should swipe to the right if the swipe leaves the element', inject(function($rootScope, $compile, $rootElement, $window) { element = $compile('
')($rootScope); + $rootElement.append(element); $rootScope.$digest(); expect($rootScope.swiped).toBeUndefined(); - browserTrigger(element, startEvent,{ + browserTrigger(element, startEvent, { keys: [], x: 20, y: 20 }); - browserTrigger(element, moveEvent,{ + browserTrigger(element, leaveEvent, {}); + browserTrigger(angular.element($window.document.body), endEvent, { keys: [], - x: 40, + x: 100, y: 20 }); - expect($rootScope.swiped).toBeUndefined(); + expect($rootScope.swiped).toBeDefined(); })); it('should not swipe if the swipe starts outside the element', inject(function($rootScope, $compile, $rootElement) { @@ -176,12 +201,12 @@ var swipeTests = function(description, restrictBrowsers, startEvent, moveEvent, expect($rootScope.swiped).toBeUndefined(); - browserTrigger(element, moveEvent,{ + browserTrigger(element, moveEvent, { keys: [], x: 10, y: 20 }); - browserTrigger(element, endEvent,{ + browserTrigger(element, endEvent, { keys: [], x: 90, y: 20 @@ -201,12 +226,12 @@ var swipeTests = function(description, restrictBrowsers, startEvent, moveEvent, eventFired = true; }); - browserTrigger(element, startEvent,{ + browserTrigger(element, startEvent, { keys: [], x: 100, y: 20 }); - browserTrigger(element, endEvent,{ + browserTrigger(element, endEvent, { keys: [], x: 20, y: 20 @@ -225,12 +250,12 @@ var swipeTests = function(description, restrictBrowsers, startEvent, moveEvent, eventFired = true; }); - browserTrigger(element, startEvent,{ + browserTrigger(element, startEvent, { keys: [], x: 20, y: 20 }); - browserTrigger(element, endEvent,{ + browserTrigger(element, endEvent, { keys: [], x: 100, y: 20 @@ -240,6 +265,5 @@ var swipeTests = function(description, restrictBrowsers, startEvent, moveEvent, }); }; -swipeTests('touch', /* restrictBrowers */ true, 'touchstart', 'touchmove', 'touchend'); -swipeTests('mouse', /* restrictBrowers */ false, 'mousedown', 'mousemove', 'mouseup'); - +swipeTests('touch', /* restrictBrowers */ true, 'touchstart', 'touchmove', 'touchend', 'touchenter', 'touchleave'); +swipeTests('mouse', /* restrictBrowers */ false, 'mousedown', 'mousemove', 'mouseup', 'mouseenter', 'mouseout'); \ No newline at end of file