From f13eab910cfc3bbfe4221eb3381e403eb6c35bd1 Mon Sep 17 00:00:00 2001 From: Gautier Fauchart Date: Sun, 10 Jan 2016 20:07:34 +0000 Subject: [PATCH] Fix HammerJS Pan config bug --- ionic/components/menu/menu-gestures.ts | 5 +- ionic/components/menu/menu.ts | 2 +- ionic/gestures/gesture.ts | 2 +- ionic/gestures/hammer.ts | 230 ++++++++++++++++++------- ionic/gestures/slide-edge-gesture.ts | 12 +- 5 files changed, 174 insertions(+), 77 deletions(-) diff --git a/ionic/components/menu/menu-gestures.ts b/ionic/components/menu/menu-gestures.ts index 1989db6f4d..ad20f3811e 100644 --- a/ionic/components/menu/menu-gestures.ts +++ b/ionic/components/menu/menu-gestures.ts @@ -9,7 +9,8 @@ export class MenuContentGesture extends SlideEdgeGesture { super(targetEl, util.extend({ direction: (menu.side === 'left' || menu.side === 'right') ? 'x' : 'y', edge: menu.side, - threshold: menu.threshold || 75 + threshold: 0, + maxEdgeStart: menu.maxEdgeStart || 75 }, options)); this.menu = menu; @@ -53,7 +54,7 @@ export class MenuContentGesture extends SlideEdgeGesture { export class TargetGesture extends MenuContentGesture { constructor(menu: Menu) { super(menu, menu.getNativeElement(), { - threshold: 0 + maxEdgeStart: 0 }); } } diff --git a/ionic/components/menu/menu.ts b/ionic/components/menu/menu.ts index 9221853bcc..f44685828d 100644 --- a/ionic/components/menu/menu.ts +++ b/ionic/components/menu/menu.ts @@ -96,7 +96,7 @@ import * as gestures from './menu-gestures'; 'id', 'side', 'type', - 'threshold' + 'maxEdgeStart' ], defaultInputs: { 'side': 'left', diff --git a/ionic/gestures/gesture.ts b/ionic/gestures/gesture.ts index 49397cc817..020caa7725 100644 --- a/ionic/gestures/gesture.ts +++ b/ionic/gestures/gesture.ts @@ -42,7 +42,7 @@ export class Gesture { } listen() { - this.hammertime = Hammer(this.element, this._options); + this.hammertime = new Hammer(this.element, this._options); } unlisten() { diff --git a/ionic/gestures/hammer.ts b/ionic/gestures/hammer.ts index 827f765b58..1ace7a3fba 100644 --- a/ionic/gestures/hammer.ts +++ b/ionic/gestures/hammer.ts @@ -1,10 +1,10 @@ -/*! Hammer.JS - v2.0.4 - 2014-09-28 +/*! Hammer.JS - v2.0.6 - 2015-12-23 * http://hammerjs.github.io/ * - * Copyright (c) 2014 Jorik Tangelder; - * Licensed under the MIT license */ + * Copyright (c) 2015 Jorik Tangelder; + * Licensed under the license */ -var VENDOR_PREFIXES = ['', 'webkit', 'moz', 'MS', 'ms', 'o']; +var VENDOR_PREFIXES = ['', 'webkit', 'Moz', 'MS', 'ms', 'o']; var TEST_ELEMENT = document.createElement('div'); var TYPE_FUNCTION = 'function'; @@ -69,15 +69,69 @@ function each(obj, iterator, context) { } } +/** + * wrap a method with a deprecation warning and stack trace + * @param {Function} method + * @param {String} name + * @param {String} message + * @returns {Function} A new function wrapping the supplied method. + */ +function deprecate(method, name, message) { + var deprecationMessage = 'DEPRECATED METHOD: ' + name + '\n' + message + ' AT \n'; + return function() { + var e = new Error('get-stack-trace'); + var stack = e && e.stack ? e.stack.replace(/^[^\(]+?[\n$]/gm, '') + .replace(/^\s+at\s+/gm, '') + .replace(/^Object.\s*\(/gm, '{anonymous}()@') : 'Unknown Stack Trace'; + + var log = window.console && (window.console.warn || window.console.log); + if (log) { + log.call(window.console, deprecationMessage, stack); + } + return method.apply(this, arguments); + }; +} + +/** + * extend object. + * means that properties in dest will be overwritten by the ones in src. + * @param {Object} target + * @param {...Object} objects_to_assign + * @returns {Object} target + */ +var assign; +if (typeof Object.assign !== 'function') { + assign = function assign(target) { + if (target === undefined || target === null) { + throw new TypeError('Cannot convert undefined or null to object'); + } + + var output = Object(target); + for (var index = 1; index < arguments.length; index++) { + var source = arguments[index]; + if (source !== undefined && source !== null) { + for (var nextKey in source) { + if (source.hasOwnProperty(nextKey)) { + output[nextKey] = source[nextKey]; + } + } + } + } + return output; + }; +} else { + assign = Object.assign; +} + /** * extend object. * means that properties in dest will be overwritten by the ones in src. * @param {Object} dest * @param {Object} src - * @param {Boolean} [merge] + * @param {Boolean=false} [merge] * @returns {Object} dest */ -function extend(dest, src, merge) { +var extend = deprecate(function extend(dest, src, merge) { var keys = Object.keys(src); var i = 0; while (i < keys.length) { @@ -87,7 +141,7 @@ function extend(dest, src, merge) { i++; } return dest; -} +}, 'extend', 'Use `assign`.'); /** * merge the values from src in the dest. @@ -96,9 +150,9 @@ function extend(dest, src, merge) { * @param {Object} src * @returns {Object} dest */ -function merge(dest, src) { +var merge = deprecate(function merge(dest, src) { return extend(dest, src, true); -} +}, 'merge', 'Use `assign`.'); /** * simple class inheritance @@ -115,7 +169,7 @@ function inherit(child, base, properties) { childP._super = baseP; if (properties) { - extend(childP, properties); + assign(childP, properties); } } @@ -163,7 +217,6 @@ function ifUndefined(val1, val2) { */ function addEventListeners(target, types, handler) { each(splitStr(types), function(type) { - //console.debug('hammer addEventListener', type, target.tagName); target.addEventListener(type, handler, false); }); } @@ -176,7 +229,6 @@ function addEventListeners(target, types, handler) { */ function removeEventListeners(target, types, handler) { each(splitStr(types), function(type) { - //console.debug('hammer removeEventListener', type, target.tagName); target.removeEventListener(type, handler, false); }); } @@ -320,8 +372,8 @@ function uniqueId() { * @returns {DocumentView|Window} */ function getWindowForElement(element) { - var doc = element.ownerDocument; - return (doc.defaultView || doc.parentWindow); + var doc = element.ownerDocument || element; + return (doc.defaultView || doc.parentWindow || window); } var MOBILE_REGEX = /mobile|tablet|ip(ad|hone|od)|android/i; @@ -392,7 +444,6 @@ Input.prototype = { * bind the events */ init: function() { - //console.debug('hammer Input init') this.evEl && addEventListeners(this.element, this.evEl, this.domHandler); this.evTarget && addEventListeners(this.target, this.evTarget, this.domHandler); this.evWin && addEventListeners(getWindowForElement(this.element), this.evWin, this.domHandler); @@ -501,9 +552,17 @@ function computeInputData(manager, input) { computeDeltaXY(session, input); input.offsetDirection = getDirection(input.deltaX, input.deltaY); + var overallVelocity = getVelocity(input.deltaTime, input.deltaX, input.deltaY); + input.overallVelocityX = overallVelocity.x; + input.overallVelocityY = overallVelocity.y; + input.overallVelocity = (abs(overallVelocity.x) > abs(overallVelocity.y)) ? overallVelocity.x : overallVelocity.y; + input.scale = firstMultiple ? getScale(firstMultiple.pointers, pointers) : 1; input.rotation = firstMultiple ? getRotation(firstMultiple.pointers, pointers) : 0; + input.maxPointers = !session.prevInput ? input.pointers.length : ((input.pointers.length > + session.prevInput.maxPointers) ? input.pointers.length : session.prevInput.maxPointers); + computeIntervalInputData(session, input); // find the correct target @@ -547,8 +606,8 @@ function computeIntervalInputData(session, input) { velocity, velocityX, velocityY, direction; if (input.eventType != INPUT_CANCEL && (deltaTime > COMPUTE_INTERVAL || last.velocity === undefined)) { - var deltaX = last.deltaX - input.deltaX; - var deltaY = last.deltaY - input.deltaY; + var deltaX = input.deltaX - last.deltaX; + var deltaY = input.deltaY - last.deltaY; var v = getVelocity(deltaTime, deltaX, deltaY); velocityX = v.x; @@ -653,9 +712,9 @@ function getDirection(x, y) { } if (abs(x) >= abs(y)) { - return x > 0 ? DIRECTION_LEFT : DIRECTION_RIGHT; + return x < 0 ? DIRECTION_LEFT : DIRECTION_RIGHT; } - return y > 0 ? DIRECTION_UP : DIRECTION_DOWN; + return y < 0 ? DIRECTION_UP : DIRECTION_DOWN; } /** @@ -698,7 +757,7 @@ function getAngle(p1, p2, props) { * @return {Number} rotation */ function getRotation(start, end) { - return getAngle(end[1], end[0], PROPS_CLIENT_XY) - getAngle(start[1], start[0], PROPS_CLIENT_XY); + return getAngle(end[1], end[0], PROPS_CLIENT_XY) + getAngle(start[1], start[0], PROPS_CLIENT_XY); } /** @@ -791,7 +850,7 @@ var POINTER_ELEMENT_EVENTS = 'pointerdown'; var POINTER_WINDOW_EVENTS = 'pointermove pointerup pointercancel'; // IE10 has prefixed support, and case-sensitive -if (window.MSPointerEvent) { +if (window.MSPointerEvent && !window.PointerEvent) { POINTER_ELEMENT_EVENTS = 'MSPointerDown'; POINTER_WINDOW_EVENTS = 'MSPointerMove MSPointerUp MSPointerCancel'; } @@ -1115,7 +1174,7 @@ TouchAction.prototype = { value = this.compute(); } - if (NATIVE_TOUCH_ACTION) { + if (NATIVE_TOUCH_ACTION && this.manager.element.style) { this.manager.element.style[PREFIXED_TOUCH_ACTION] = value; } this.actions = value.toLowerCase().trim(); @@ -1166,6 +1225,23 @@ TouchAction.prototype = { var hasPanY = inStr(actions, TOUCH_ACTION_PAN_Y); var hasPanX = inStr(actions, TOUCH_ACTION_PAN_X); + if (hasNone) { + //do not prevent defaults if this is a tap gesture + + var isTapPointer = input.pointers.length === 1; + var isTapMovement = input.distance < 2; + var isTapTouchTime = input.deltaTime < 250; + + if (isTapPointer && isTapMovement && isTapTouchTime) { + return; + } + } + + if (hasPanX && hasPanY) { + // `pan-x pan-y` means browser handles all scrolling/panning, do not prevent + return; + } + if (hasNone || (hasPanY && direction & DIRECTION_HORIZONTAL) || (hasPanX && direction & DIRECTION_VERTICAL)) { @@ -1197,9 +1273,12 @@ function cleanTouchActions(actions) { var hasPanX = inStr(actions, TOUCH_ACTION_PAN_X); var hasPanY = inStr(actions, TOUCH_ACTION_PAN_Y); - // pan-x and pan-y can be combined + // if both pan-x and pan-y are set (different recognizers + // for different directions, e.g. horizontal pan but vertical swipe?) + // we need none (as otherwise with pan-x pan-y combined none of these + // recognizers will work, since the browser would handle all panning if (hasPanX && hasPanY) { - return TOUCH_ACTION_PAN_X + ' ' + TOUCH_ACTION_PAN_Y; + return TOUCH_ACTION_NONE; } // pan-x OR pan-y @@ -1257,10 +1336,11 @@ var STATE_FAILED = 32; * @param {Object} options */ function Recognizer(options) { + this.options = assign({}, this.defaults, options || {}); + this.id = uniqueId(); this.manager = null; - this.options = merge(options || {}, this.defaults); // default is enable true this.options.enable = ifUndefined(this.options.enable, true); @@ -1284,7 +1364,7 @@ Recognizer.prototype = { * @return {Recognizer} */ set: function(options) { - extend(this.options, options); + assign(this.options, options); // also update the touchAction, in case something changed about the directions/enabled state this.manager && this.manager.touchAction.update(); @@ -1388,20 +1468,24 @@ Recognizer.prototype = { var self = this; var state = this.state; - function emit(withState) { - self.manager.emit(self.options.event + (withState ? stateStr(state) : ''), input); + function emit(event) { + self.manager.emit(event, input); } // 'panstart' and 'panmove' if (state < STATE_ENDED) { - emit(true); + emit(self.options.event + stateStr(state)); } - emit(); // simple 'eventName' events + emit(self.options.event); // simple 'eventName' events + + if (input.additionalEvent) { // additional event(panleft, panright, pinchin, pinchout...) + emit(input.additionalEvent); + } // panend and pancancel if (state >= STATE_ENDED) { - emit(true); + emit(self.options.event + stateStr(state)); } }, @@ -1441,7 +1525,7 @@ Recognizer.prototype = { recognize: function(inputData) { // make a new copy of the inputData // so we can change the inputData without messing up the other recognizers - var inputDataClone = extend({}, inputData); + var inputDataClone = assign({}, inputData); // is is enabled and allow recognizing? if (!boolOrFn(this.options.enable, [this, inputDataClone])) { @@ -1666,14 +1750,15 @@ inherit(PanRecognizer, AttrRecognizer, { }, emit: function(input) { + this.pX = input.deltaX; this.pY = input.deltaY; var direction = directionStr(input.direction); - if (direction) { - this.manager.emit(this.options.event + direction, input); - } + if (direction) { + input.additionalEvent = this.options.event + direction; + } this._super.emit.call(this, input); } }); @@ -1709,11 +1794,11 @@ inherit(PinchRecognizer, AttrRecognizer, { }, emit: function(input) { - this._super.emit.call(this, input); if (input.scale !== 1) { var inOut = input.scale < 1 ? 'in' : 'out'; - this.manager.emit(this.options.event + inOut, input); + input.additionalEvent = this.options.event + inOut; } + this._super.emit.call(this, input); } }); @@ -1738,8 +1823,8 @@ inherit(PressRecognizer, Recognizer, { defaults: { event: 'press', pointers: 1, - time: 500, // minimal time of the pointer to be pressed - threshold: 5 // a minimal movement is ok, but keep it low + time: 251, // minimal time of the pointer to be pressed + threshold: 9 // a minimal movement is ok, but keep it low }, getTouchAction: function() { @@ -1837,7 +1922,7 @@ inherit(SwipeRecognizer, AttrRecognizer, { defaults: { event: 'swipe', threshold: 10, - velocity: 0.65, + velocity: 0.3, direction: DIRECTION_HORIZONTAL | DIRECTION_VERTICAL, pointers: 1 }, @@ -1851,21 +1936,22 @@ inherit(SwipeRecognizer, AttrRecognizer, { var velocity; if (direction & (DIRECTION_HORIZONTAL | DIRECTION_VERTICAL)) { - velocity = input.velocity; + velocity = input.overallVelocity; } else if (direction & DIRECTION_HORIZONTAL) { - velocity = input.velocityX; + velocity = input.overallVelocityX; } else if (direction & DIRECTION_VERTICAL) { - velocity = input.velocityY; + velocity = input.overallVelocityY; } return this._super.attrTest.call(this, input) && - direction & input.direction && + direction & input.offsetDirection && input.distance > this.options.threshold && + input.maxPointers == this.options.pointers && abs(velocity) > this.options.velocity && input.eventType & INPUT_END; }, emit: function(input) { - var direction = directionStr(input.direction); + var direction = directionStr(input.offsetDirection); if (direction) { this.manager.emit(this.options.event + direction, input); } @@ -1908,7 +1994,7 @@ inherit(TapRecognizer, Recognizer, { taps: 1, interval: 300, // max time between the multi-tap taps time: 250, // max time of the pointer to be down (like finger on the screen) - threshold: 2, // a minimal movement is ok, but keep it low + threshold: 9, // a minimal movement is ok, but keep it low posThreshold: 10 // a multi-tap can be a bit off the initial position }, @@ -1982,7 +2068,7 @@ inherit(TapRecognizer, Recognizer, { }, emit: function() { - if (this.state == STATE_RECOGNIZED ) { + if (this.state == STATE_RECOGNIZED) { this._input.tapCount = this.count; this.manager.emit(this.options.event, this._input); } @@ -1990,7 +2076,7 @@ inherit(TapRecognizer, Recognizer, { }); /** - * Simple way to create an manager with a default set of recognizers. + * Simple way to create a manager with a default set of recognizers. * @param {HTMLElement} element * @param {Object} [options] * @constructor @@ -2004,7 +2090,7 @@ function Hammer(element, options) { /** * @const {string} */ -Hammer.VERSION = '2.0.4'; +Hammer.VERSION = '2.0.6'; /** * default settings @@ -2056,12 +2142,12 @@ Hammer.defaults = { */ preset: [ // RecognizerClass, options, [recognizeWith, ...], [requireFailure, ...] - [RotateRecognizer, { enable: false }], - [PinchRecognizer, { enable: false }, ['rotate']], - [SwipeRecognizer,{ direction: DIRECTION_HORIZONTAL }], - [PanRecognizer, { direction: DIRECTION_HORIZONTAL }, ['swipe']], + [RotateRecognizer, {enable: false}], + [PinchRecognizer, {enable: false}, ['rotate']], + [SwipeRecognizer, {direction: DIRECTION_HORIZONTAL}], + [PanRecognizer, {direction: DIRECTION_HORIZONTAL}, ['swipe']], [TapRecognizer], - [TapRecognizer, { event: 'doubletap', taps: 2 }, ['tap']], + [TapRecognizer, {event: 'doubletap', taps: 2}, ['tap']], [PressRecognizer] ], @@ -2128,9 +2214,8 @@ var FORCED_STOP = 2; * @constructor */ function Manager(element, options) { - options = options || {}; + this.options = assign({}, Hammer.defaults, options || {}); - this.options = merge(options, Hammer.defaults); this.options.inputTarget = this.options.inputTarget || element; this.handlers = {}; @@ -2143,7 +2228,7 @@ function Manager(element, options) { toggleCssProps(this, true); - each(options.recognizers, function(item) { + each(this.options.recognizers, function(item) { var recognizer = this.add(new (item[0])(item[1])); item[2] && recognizer.recognizeWith(item[2]); item[3] && recognizer.requireFailure(item[3]); @@ -2157,7 +2242,7 @@ Manager.prototype = { * @returns {Manager} */ set: function(options) { - extend(this.options, options); + assign(this.options, options); // Options that need a little more setup if (options.touchAction) { @@ -2291,11 +2376,19 @@ Manager.prototype = { return this; } - var recognizers = this.recognizers; recognizer = this.get(recognizer); - recognizers.splice(inArray(recognizers, recognizer), 1); - this.touchAction.update(); + // let's make sure this recognizer exists + if (recognizer) { + var recognizers = this.recognizers; + var index = inArray(recognizers, recognizer); + + if (index !== -1) { + recognizers.splice(index, 1); + this.touchAction.update(); + } + } + return this; }, @@ -2326,7 +2419,7 @@ Manager.prototype = { if (!handler) { delete handlers[event]; } else { - handlers[event].splice(inArray(handlers[event], handler), 1); + handlers[event] && handlers[event].splice(inArray(handlers[event], handler), 1); } }); return this; @@ -2382,6 +2475,9 @@ Manager.prototype = { */ function toggleCssProps(manager, add) { var element = manager.element; + if (!element.style) { + return; + } each(manager.options.cssProps, function(value, name) { element.style[prefixed(element.style, name)] = add ? value : ''; }); @@ -2399,7 +2495,7 @@ function triggerDomEvent(event, data) { data.target.dispatchEvent(gestureEvent); } -extend(Hammer, { +assign(Hammer, { INPUT_START: INPUT_START, INPUT_MOVE: INPUT_MOVE, INPUT_END: INPUT_END, @@ -2446,12 +2542,12 @@ extend(Hammer, { each: each, merge: merge, extend: extend, + assign: assign, inherit: inherit, bindFn: bindFn, prefixed: prefixed }); - -// attach to window for angular2 gesture listeners -window.Hammer = Hammer; -export {Hammer}; +window.Hammer = Hammer; + +export {Hammer}; diff --git a/ionic/gestures/slide-edge-gesture.ts b/ionic/gestures/slide-edge-gesture.ts index e8ce058c33..199d1968ea 100644 --- a/ionic/gestures/slide-edge-gesture.ts +++ b/ionic/gestures/slide-edge-gesture.ts @@ -7,12 +7,12 @@ export class SlideEdgeGesture extends SlideGesture { constructor(element: Element, opts: Object = {}) { defaults(opts, { edge: 'left', - threshold: 50 + maxEdgeStart: 50 }); super(element, opts); // Can check corners through use of eg 'left top' this.edges = opts.edge.split(' '); - this.threshold = opts.threshold; + this.maxEdgeStart = opts.maxEdgeStart; } canStart(ev) { @@ -31,10 +31,10 @@ export class SlideEdgeGesture extends SlideGesture { _checkEdge(edge, pos) { switch (edge) { - case 'left': return pos.x <= this._d.left + this.threshold; - case 'right': return pos.x >= this._d.width - this.threshold; - case 'top': return pos.y <= this._d.top + this.threshold; - case 'bottom': return pos.y >= this._d.height - this.threshold; + case 'left': return pos.x <= this._d.left + this.maxEdgeStart; + case 'right': return pos.x >= this._d.width - this.maxEdgeStart; + case 'top': return pos.y <= this._d.top + this.maxEdgeStart; + case 'bottom': return pos.y >= this._d.height - this.maxEdgeStart; } }