Difference between revisions of "Team:CityU HK/Template/js"

 
(One intermediate revision by the same user not shown)
Line 1: Line 1:
 +
 
/****************************
 
/****************************
 
     custom js
 
     custom js

Latest revision as of 08:44, 27 August 2015

/****************************

   custom js
                                                          • /

jQuery(function($) {

 // Mobile sidebars
 $.fn.expandableSidebar = function(expandedClass) {
   var $me = this;
   $me.on('click', function() {
     if(!$me.hasClass(expandedClass)) {
       $me.addClass(expandedClass);
     } else {
       $me.removeClass(expandedClass);
     }
   });
 }
 
 // Interval loop
 $.fn.intervalLoop = function(condition, action, duration, limit) {
   var counter = 0;
   var looper = setInterval(function(){
     if (counter >= limit || $.fn.checkIfElementExists(condition)) {
       clearInterval(looper);
     } else {
       action();
       counter++;
     }
   }, duration);
 }
 // Check if element exists
 $.fn.checkIfElementExists = function(selector) {
   return $(selector).length;
 }
 var parisController = {
   init: function(opts) {
     var base = this;
     // Add classes to elements
     base._addClasses();
     setTimeout(function(){
       base._checkCartItems();
       base._attachEvents();
     }, 1000);
   },
   _addClasses: function() {
     var base = this;
     // Add fade in class to nav + logo
     $('.landing-page').addClass('fade-in');
     // Add class to nav items with subnav
     $('.wsite-menu-default').find('li.wsite-menu-item-wrap').each(function(){
       var $me = $(this);
       if($me.children('.wsite-menu-wrap').length > 0) {
         $me.addClass('has-submenu');
         $('').insertAfter($me.children('a.wsite-menu-item'));
       }
     });
     // ADd class to subnav items with subnav
     $('.wsite-menu').find('li.wsite-menu-subitem-wrap').each(function(){
       var $me = $(this);
       if($me.children('.wsite-menu-wrap').length > 0) {
         $me.addClass('has-submenu');
         $('').insertAfter($me.children('a.wsite-menu-subitem'));
       }
     });
     // Keep subnav open if submenu item is active
     $('.wsite-menu-wrap').find('li.wsite-menu-subitem-wrap').each(function(){
       var $me = $(this);
       if($me.hasClass('wsite-nav-current')) {
         $me.parents().addClass('open');
       }
     });
     // Add placeholder text to inputs
     $('.wsite-form-sublabel').each(function(){
       var sublabel = $(this).text();
       $(this).prev('.wsite-form-input').attr('placeholder', sublabel);
     });
     // Add fullwidth class to gallery thumbs if less than 6
     $('.imageGallery').each(function(){
       if ($(this).children('div').length <= 6) {
         $(this).children('div').addClass('fullwidth-mobile');
       }
     });
   },
   _checkCartItems: function() {
     var base = this;
     
     if($('#wsite-mini-cart').find('li.wsite-product-item').length > 0) {
       $('body').addClass('cart-full');
     } else {
       $('body').removeClass('cart-full');
     }
   },
   _moveLogin: function() {
     var loginDetach = $('#member-login').detach();
     $('.mobile-nav .wsite-menu-default li:last-child').after(loginDetach);
   },
   _attachEvents: function() {
     var base = this;
     // Move cart + login
     if ($(window).width() <= 992) {
       $.fn.intervalLoop('.mobile-nav #member-login', base._moveLogin, 800, 5);
     }
     // Subnav toggle
     $('li.has-submenu').each(function(){
       var $me = $(this);
       var caret = $me.children('span.icon-caret');
       caret.on('click', function(){          
         if($me.children('.wsite-menu-wrap.open').length > 0) {
           caret.siblings('.wsite-menu-wrap').removeClass('open');
         } else {
           caret.siblings('.wsite-menu-wrap').addClass('open');
         }
       });
     });
     // Scroll on landing page
     $('#contentArrow').on('click', function() {
       $('html, body').animate({
         scrollTop: $('.main-wrap').offset().top - $('.paris-header').outerHeight()
       }, 500);
     });
     // Store category dropdown
     $('.wsite-com-sidebar').expandableSidebar('sidebar-expanded');
     // Search filters dropdown
     $('#wsite-search-sidebar').expandableSidebar('sidebar-expanded');
     // Init fancybox swipe on mobile
     if ('ontouchstart' in window) {
       $('body').on('click', 'a.w-fancybox', function() {
         base._initSwipeGallery();
       });
     }
   },
   _initSwipeGallery: function() {
     var base = this;
     setTimeout(function(){
       var touchGallery = document.getElementsByClassName('fancybox-wrap')[0];
       var mc = new Hammer(touchGallery);
       mc.on("panleft panright", function(ev) {
         if (ev.type == "panleft") {
           $("a.fancybox-next").trigger("click");
         } else if (ev.type == "panright") {
           $("a.fancybox-prev").trigger("click");
         }
         base._initSwipeGallery();
       });
     }, 500);
   }
 } 


 $(document).ready(function(){
   parisController.init();
 }); 

});

/***********************************

   Plugin js
                                                                      • /

/*! Hammer.JS - v2.0.4 - 2014-09-28

* http://hammerjs.github.io/
*
* Copyright (c) 2014 Jorik Tangelder;
* Licensed under the MIT license */

(function(window, document, exportName, undefined) {

 'use strict';

var VENDOR_PREFIXES = [, 'webkit', 'moz', 'MS', 'ms', 'o']; var TEST_ELEMENT = document.createElement('div');

var TYPE_FUNCTION = 'function';

var round = Math.round; var abs = Math.abs; var now = Date.now;

/**

* set a timeout with a given scope
* @param {Function} fn
* @param {Number} timeout
* @param {Object} context
* @returns {number}
*/

function setTimeoutContext(fn, timeout, context) {

   return setTimeout(bindFn(fn, context), timeout);

}

/**

* if the argument is an array, we want to execute the fn on each entry
* if it aint an array we don't want to do a thing.
* this is used by all the methods that accept a single and array argument.
* @param {*|Array} arg
* @param {String} fn
* @param {Object} [context]
* @returns {Boolean}
*/

function invokeArrayArg(arg, fn, context) {

   if (Array.isArray(arg)) {
       each(arg, context[fn], context);
       return true;
   }
   return false;

}

/**

* walk objects and arrays
* @param {Object} obj
* @param {Function} iterator
* @param {Object} context
*/

function each(obj, iterator, context) {

   var i;
   if (!obj) {
       return;
   }
   if (obj.forEach) {
       obj.forEach(iterator, context);
   } else if (obj.length !== undefined) {
       i = 0;
       while (i < obj.length) {
           iterator.call(context, obj[i], i, obj);
           i++;
       }
   } else {
       for (i in obj) {
           obj.hasOwnProperty(i) && iterator.call(context, obj[i], i, obj);
       }
   }

}

/**

* extend object.
* means that properties in dest will be overwritten by the ones in src.
* @param {Object} dest
* @param {Object} src
* @param {Boolean} [merge]
* @returns {Object} dest
*/

function extend(dest, src, merge) {

   var keys = Object.keys(src);
   var i = 0;
   while (i < keys.length) {
       if (!merge || (merge && dest[keys[i]] === undefined)) {
           dest[keys[i]] = src[keys[i]];
       }
       i++;
   }
   return dest;

}

/**

* merge the values from src in the dest.
* means that properties that exist in dest will not be overwritten by src
* @param {Object} dest
* @param {Object} src
* @returns {Object} dest
*/

function merge(dest, src) {

   return extend(dest, src, true);

}

/**

* simple class inheritance
* @param {Function} child
* @param {Function} base
* @param {Object} [properties]
*/

function inherit(child, base, properties) {

   var baseP = base.prototype,
       childP;
   childP = child.prototype = Object.create(baseP);
   childP.constructor = child;
   childP._super = baseP;
   if (properties) {
       extend(childP, properties);
   }

}

/**

* simple function bind
* @param {Function} fn
* @param {Object} context
* @returns {Function}
*/

function bindFn(fn, context) {

   return function boundFn() {
       return fn.apply(context, arguments);
   };

}

/**

* let a boolean value also be a function that must return a boolean
* this first item in args will be used as the context
* @param {Boolean|Function} val
* @param {Array} [args]
* @returns {Boolean}
*/

function boolOrFn(val, args) {

   if (typeof val == TYPE_FUNCTION) {
       return val.apply(args ? args[0] || undefined : undefined, args);
   }
   return val;

}

/**

* use the val2 when val1 is undefined
* @param {*} val1
* @param {*} val2
* @returns {*}
*/

function ifUndefined(val1, val2) {

   return (val1 === undefined) ? val2 : val1;

}

/**

* addEventListener with multiple events at once
* @param {EventTarget} target
* @param {String} types
* @param {Function} handler
*/

function addEventListeners(target, types, handler) {

   each(splitStr(types), function(type) {
       target.addEventListener(type, handler, false);
   });

}

/**

* removeEventListener with multiple events at once
* @param {EventTarget} target
* @param {String} types
* @param {Function} handler
*/

function removeEventListeners(target, types, handler) {

   each(splitStr(types), function(type) {
       target.removeEventListener(type, handler, false);
   });

}

/**

* find if a node is in the given parent
* @method hasParent
* @param {HTMLElement} node
* @param {HTMLElement} parent
* @return {Boolean} found
*/

function hasParent(node, parent) {

   while (node) {
       if (node == parent) {
           return true;
       }
       node = node.parentNode;
   }
   return false;

}

/**

* small indexOf wrapper
* @param {String} str
* @param {String} find
* @returns {Boolean} found
*/

function inStr(str, find) {

   return str.indexOf(find) > -1;

}

/**

* split string on whitespace
* @param {String} str
* @returns {Array} words
*/

function splitStr(str) {

   return str.trim().split(/\s+/g);

}

/**

* find if a array contains the object using indexOf or a simple polyFill
* @param {Array} src
* @param {String} find
* @param {String} [findByKey]
* @return {Boolean|Number} false when not found, or the index
*/

function inArray(src, find, findByKey) {

   if (src.indexOf && !findByKey) {
       return src.indexOf(find);
   } else {
       var i = 0;
       while (i < src.length) {
           if ((findByKey && src[i][findByKey] == find) || (!findByKey && src[i] === find)) {
               return i;
           }
           i++;
       }
       return -1;
   }

}

/**

* convert array-like objects to real arrays
* @param {Object} obj
* @returns {Array}
*/

function toArray(obj) {

   return Array.prototype.slice.call(obj, 0);

}

/**

* unique array with objects based on a key (like 'id') or just by the array's value
* @param {Array} src [{id:1},{id:2},{id:1}]
* @param {String} [key]
* @param {Boolean} [sort=False]
* @returns {Array} [{id:1},{id:2}]
*/

function uniqueArray(src, key, sort) {

   var results = [];
   var values = [];
   var i = 0;
   while (i < src.length) {
       var val = key ? src[i][key] : src[i];
       if (inArray(values, val) < 0) {
           results.push(src[i]);
       }
       values[i] = val;
       i++;
   }
   if (sort) {
       if (!key) {
           results = results.sort();
       } else {
           results = results.sort(function sortUniqueArray(a, b) {
               return a[key] > b[key];
           });
       }
   }
   return results;

}

/**

* get the prefixed property
* @param {Object} obj
* @param {String} property
* @returns {String|Undefined} prefixed
*/

function prefixed(obj, property) {

   var prefix, prop;
   var camelProp = property[0].toUpperCase() + property.slice(1);
   var i = 0;
   while (i < VENDOR_PREFIXES.length) {
       prefix = VENDOR_PREFIXES[i];
       prop = (prefix) ? prefix + camelProp : property;
       if (prop in obj) {
           return prop;
       }
       i++;
   }
   return undefined;

}

/**

* get a unique id
* @returns {number} uniqueId
*/

var _uniqueId = 1; function uniqueId() {

   return _uniqueId++;

}

/**

* get the window object of an element
* @param {HTMLElement} element
* @returns {DocumentView|Window}
*/

function getWindowForElement(element) {

   var doc = element.ownerDocument;
   return (doc.defaultView || doc.parentWindow);

}

var MOBILE_REGEX = /mobile|tablet|ip(ad|hone|od)|android/i;

var SUPPORT_TOUCH = ('ontouchstart' in window); var SUPPORT_POINTER_EVENTS = prefixed(window, 'PointerEvent') !== undefined; var SUPPORT_ONLY_TOUCH = SUPPORT_TOUCH && MOBILE_REGEX.test(navigator.userAgent);

var INPUT_TYPE_TOUCH = 'touch'; var INPUT_TYPE_PEN = 'pen'; var INPUT_TYPE_MOUSE = 'mouse'; var INPUT_TYPE_KINECT = 'kinect';

var COMPUTE_INTERVAL = 25;

var INPUT_START = 1; var INPUT_MOVE = 2; var INPUT_END = 4; var INPUT_CANCEL = 8;

var DIRECTION_NONE = 1; var DIRECTION_LEFT = 2; var DIRECTION_RIGHT = 4; var DIRECTION_UP = 8; var DIRECTION_DOWN = 16;

var DIRECTION_HORIZONTAL = DIRECTION_LEFT | DIRECTION_RIGHT; var DIRECTION_VERTICAL = DIRECTION_UP | DIRECTION_DOWN; var DIRECTION_ALL = DIRECTION_HORIZONTAL | DIRECTION_VERTICAL;

var PROPS_XY = ['x', 'y']; var PROPS_CLIENT_XY = ['clientX', 'clientY'];

/**

* create new input type manager
* @param {Manager} manager
* @param {Function} callback
* @returns {Input}
* @constructor
*/

function Input(manager, callback) {

   var self = this;
   this.manager = manager;
   this.callback = callback;
   this.element = manager.element;
   this.target = manager.options.inputTarget;
   // smaller wrapper around the handler, for the scope and the enabled state of the manager,
   // so when disabled the input events are completely bypassed.
   this.domHandler = function(ev) {
       if (boolOrFn(manager.options.enable, [manager])) {
           self.handler(ev);
       }
   };
   this.init();

}

Input.prototype = {

   /**
    * should handle the inputEvent data and trigger the callback
    * @virtual
    */
   handler: function() { },
   /**
    * bind the events
    */
   init: function() {
       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);
   },
   /**
    * unbind the events
    */
   destroy: function() {
       this.evEl && removeEventListeners(this.element, this.evEl, this.domHandler);
       this.evTarget && removeEventListeners(this.target, this.evTarget, this.domHandler);
       this.evWin && removeEventListeners(getWindowForElement(this.element), this.evWin, this.domHandler);
   }

};

/**

* create new input type manager
* called by the Manager constructor
* @param {Hammer} manager
* @returns {Input}
*/

function createInputInstance(manager) {

   var Type;
   var inputClass = manager.options.inputClass;
   if (inputClass) {
       Type = inputClass;
   } else if (SUPPORT_POINTER_EVENTS) {
       Type = PointerEventInput;
   } else if (SUPPORT_ONLY_TOUCH) {
       Type = TouchInput;
   } else if (!SUPPORT_TOUCH) {
       Type = MouseInput;
   } else {
       Type = TouchMouseInput;
   }
   return new (Type)(manager, inputHandler);

}

/**

* handle input events
* @param {Manager} manager
* @param {String} eventType
* @param {Object} input
*/

function inputHandler(manager, eventType, input) {

   var pointersLen = input.pointers.length;
   var changedPointersLen = input.changedPointers.length;
   var isFirst = (eventType & INPUT_START && (pointersLen - changedPointersLen === 0));
   var isFinal = (eventType & (INPUT_END | INPUT_CANCEL) && (pointersLen - changedPointersLen === 0));
   input.isFirst = !!isFirst;
   input.isFinal = !!isFinal;
   if (isFirst) {
       manager.session = {};
   }
   // source event is the normalized value of the domEvents
   // like 'touchstart, mouseup, pointerdown'
   input.eventType = eventType;
   // compute scale, rotation etc
   computeInputData(manager, input);
   // emit secret event
   manager.emit('hammer.input', input);
   manager.recognize(input);
   manager.session.prevInput = input;

}

/**

* extend the data with some usable properties like scale, rotate, velocity etc
* @param {Object} manager
* @param {Object} input
*/

function computeInputData(manager, input) {

   var session = manager.session;
   var pointers = input.pointers;
   var pointersLength = pointers.length;
   // store the first input to calculate the distance and direction
   if (!session.firstInput) {
       session.firstInput = simpleCloneInputData(input);
   }
   // to compute scale and rotation we need to store the multiple touches
   if (pointersLength > 1 && !session.firstMultiple) {
       session.firstMultiple = simpleCloneInputData(input);
   } else if (pointersLength === 1) {
       session.firstMultiple = false;
   }
   var firstInput = session.firstInput;
   var firstMultiple = session.firstMultiple;
   var offsetCenter = firstMultiple ? firstMultiple.center : firstInput.center;
   var center = input.center = getCenter(pointers);
   input.timeStamp = now();
   input.deltaTime = input.timeStamp - firstInput.timeStamp;
   input.angle = getAngle(offsetCenter, center);
   input.distance = getDistance(offsetCenter, center);
   computeDeltaXY(session, input);
   input.offsetDirection = getDirection(input.deltaX, input.deltaY);
   input.scale = firstMultiple ? getScale(firstMultiple.pointers, pointers) : 1;
   input.rotation = firstMultiple ? getRotation(firstMultiple.pointers, pointers) : 0;
   computeIntervalInputData(session, input);
   // find the correct target
   var target = manager.element;
   if (hasParent(input.srcEvent.target, target)) {
       target = input.srcEvent.target;
   }
   input.target = target;

}

function computeDeltaXY(session, input) {

   var center = input.center;
   var offset = session.offsetDelta || {};
   var prevDelta = session.prevDelta || {};
   var prevInput = session.prevInput || {};
   if (input.eventType === INPUT_START || prevInput.eventType === INPUT_END) {
       prevDelta = session.prevDelta = {
           x: prevInput.deltaX || 0,
           y: prevInput.deltaY || 0
       };
       offset = session.offsetDelta = {
           x: center.x,
           y: center.y
       };
   }
   input.deltaX = prevDelta.x + (center.x - offset.x);
   input.deltaY = prevDelta.y + (center.y - offset.y);

}

/**

* velocity is calculated every x ms
* @param {Object} session
* @param {Object} input
*/

function computeIntervalInputData(session, input) {

   var last = session.lastInterval || input,
       deltaTime = input.timeStamp - last.timeStamp,
       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 v = getVelocity(deltaTime, deltaX, deltaY);
       velocityX = v.x;
       velocityY = v.y;
       velocity = (abs(v.x) > abs(v.y)) ? v.x : v.y;
       direction = getDirection(deltaX, deltaY);
       session.lastInterval = input;
   } else {
       // use latest velocity info if it doesn't overtake a minimum period
       velocity = last.velocity;
       velocityX = last.velocityX;
       velocityY = last.velocityY;
       direction = last.direction;
   }
   input.velocity = velocity;
   input.velocityX = velocityX;
   input.velocityY = velocityY;
   input.direction = direction;

}

/**

* create a simple clone from the input used for storage of firstInput and firstMultiple
* @param {Object} input
* @returns {Object} clonedInputData
*/

function simpleCloneInputData(input) {

   // make a simple copy of the pointers because we will get a reference if we don't
   // we only need clientXY for the calculations
   var pointers = [];
   var i = 0;
   while (i < input.pointers.length) {
       pointers[i] = {
           clientX: round(input.pointers[i].clientX),
           clientY: round(input.pointers[i].clientY)
       };
       i++;
   }
   return {
       timeStamp: now(),
       pointers: pointers,
       center: getCenter(pointers),
       deltaX: input.deltaX,
       deltaY: input.deltaY
   };

}

/**

* get the center of all the pointers
* @param {Array} pointers
* @return {Object} center contains `x` and `y` properties
*/

function getCenter(pointers) {

   var pointersLength = pointers.length;
   // no need to loop when only one touch
   if (pointersLength === 1) {
       return {
           x: round(pointers[0].clientX),
           y: round(pointers[0].clientY)
       };
   }
   var x = 0, y = 0, i = 0;
   while (i < pointersLength) {
       x += pointers[i].clientX;
       y += pointers[i].clientY;
       i++;
   }
   return {
       x: round(x / pointersLength),
       y: round(y / pointersLength)
   };

}

/**

* calculate the velocity between two points. unit is in px per ms.
* @param {Number} deltaTime
* @param {Number} x
* @param {Number} y
* @return {Object} velocity `x` and `y`
*/

function getVelocity(deltaTime, x, y) {

   return {
       x: x / deltaTime || 0,
       y: y / deltaTime || 0
   };

}

/**

* get the direction between two points
* @param {Number} x
* @param {Number} y
* @return {Number} direction
*/

function getDirection(x, y) {

   if (x === y) {
       return DIRECTION_NONE;
   }
   if (abs(x) >= abs(y)) {
       return x > 0 ? DIRECTION_LEFT : DIRECTION_RIGHT;
   }
   return y > 0 ? DIRECTION_UP : DIRECTION_DOWN;

}

/**

* calculate the absolute distance between two points
* @param {Object} p1 {x, y}
* @param {Object} p2 {x, y}
* @param {Array} [props] containing x and y keys
* @return {Number} distance
*/

function getDistance(p1, p2, props) {

   if (!props) {
       props = PROPS_XY;
   }
   var x = p2[props[0]] - p1[props[0]],
       y = p2[props[1]] - p1[props[1]];
   return Math.sqrt((x * x) + (y * y));

}

/**

* calculate the angle between two coordinates
* @param {Object} p1
* @param {Object} p2
* @param {Array} [props] containing x and y keys
* @return {Number} angle
*/

function getAngle(p1, p2, props) {

   if (!props) {
       props = PROPS_XY;
   }
   var x = p2[props[0]] - p1[props[0]],
       y = p2[props[1]] - p1[props[1]];
   return Math.atan2(y, x) * 180 / Math.PI;

}

/**

* calculate the rotation degrees between two pointersets
* @param {Array} start array of pointers
* @param {Array} end array of pointers
* @return {Number} rotation
*/

function getRotation(start, end) {

   return getAngle(end[1], end[0], PROPS_CLIENT_XY) - getAngle(start[1], start[0], PROPS_CLIENT_XY);

}

/**

* calculate the scale factor between two pointersets
* no scale is 1, and goes down to 0 when pinched together, and bigger when pinched out
* @param {Array} start array of pointers
* @param {Array} end array of pointers
* @return {Number} scale
*/

function getScale(start, end) {

   return getDistance(end[0], end[1], PROPS_CLIENT_XY) / getDistance(start[0], start[1], PROPS_CLIENT_XY);

}

var MOUSE_INPUT_MAP = {

   mousedown: INPUT_START,
   mousemove: INPUT_MOVE,
   mouseup: INPUT_END

};

var MOUSE_ELEMENT_EVENTS = 'mousedown'; var MOUSE_WINDOW_EVENTS = 'mousemove mouseup';

/**

* Mouse events input
* @constructor
* @extends Input
*/

function MouseInput() {

   this.evEl = MOUSE_ELEMENT_EVENTS;
   this.evWin = MOUSE_WINDOW_EVENTS;
   this.allow = true; // used by Input.TouchMouse to disable mouse events
   this.pressed = false; // mousedown state
   Input.apply(this, arguments);

}

inherit(MouseInput, Input, {

   /**
    * handle mouse events
    * @param {Object} ev
    */
   handler: function MEhandler(ev) {
       var eventType = MOUSE_INPUT_MAP[ev.type];
       // on start we want to have the left mouse button down
       if (eventType & INPUT_START && ev.button === 0) {
           this.pressed = true;
       }
       if (eventType & INPUT_MOVE && ev.which !== 1) {
           eventType = INPUT_END;
       }
       // mouse must be down, and mouse events are allowed (see the TouchMouse input)
       if (!this.pressed || !this.allow) {
           return;
       }
       if (eventType & INPUT_END) {
           this.pressed = false;
       }
       this.callback(this.manager, eventType, {
           pointers: [ev],
           changedPointers: [ev],
           pointerType: INPUT_TYPE_MOUSE,
           srcEvent: ev
       });
   }

});

var POINTER_INPUT_MAP = {

   pointerdown: INPUT_START,
   pointermove: INPUT_MOVE,
   pointerup: INPUT_END,
   pointercancel: INPUT_CANCEL,
   pointerout: INPUT_CANCEL

};

// in IE10 the pointer types is defined as an enum var IE10_POINTER_TYPE_ENUM = {

   2: INPUT_TYPE_TOUCH,
   3: INPUT_TYPE_PEN,
   4: INPUT_TYPE_MOUSE,
   5: INPUT_TYPE_KINECT // see https://twitter.com/jacobrossi/status/480596438489890816

};

var POINTER_ELEMENT_EVENTS = 'pointerdown'; var POINTER_WINDOW_EVENTS = 'pointermove pointerup pointercancel';

// IE10 has prefixed support, and case-sensitive if (window.MSPointerEvent) {

   POINTER_ELEMENT_EVENTS = 'MSPointerDown';
   POINTER_WINDOW_EVENTS = 'MSPointerMove MSPointerUp MSPointerCancel';

}

/**

* Pointer events input
* @constructor
* @extends Input
*/

function PointerEventInput() {

   this.evEl = POINTER_ELEMENT_EVENTS;
   this.evWin = POINTER_WINDOW_EVENTS;
   Input.apply(this, arguments);
   this.store = (this.manager.session.pointerEvents = []);

}

inherit(PointerEventInput, Input, {

   /**
    * handle mouse events
    * @param {Object} ev
    */
   handler: function PEhandler(ev) {
       var store = this.store;
       var removePointer = false;
       var eventTypeNormalized = ev.type.toLowerCase().replace('ms', );
       var eventType = POINTER_INPUT_MAP[eventTypeNormalized];
       var pointerType = IE10_POINTER_TYPE_ENUM[ev.pointerType] || ev.pointerType;
       var isTouch = (pointerType == INPUT_TYPE_TOUCH);
       // get index of the event in the store
       var storeIndex = inArray(store, ev.pointerId, 'pointerId');
       // start and mouse must be down
       if (eventType & INPUT_START && (ev.button === 0 || isTouch)) {
           if (storeIndex < 0) {
               store.push(ev);
               storeIndex = store.length - 1;
           }
       } else if (eventType & (INPUT_END | INPUT_CANCEL)) {
           removePointer = true;
       }
       // it not found, so the pointer hasn't been down (so it's probably a hover)
       if (storeIndex < 0) {
           return;
       }
       // update the event in the store
       store[storeIndex] = ev;
       this.callback(this.manager, eventType, {
           pointers: store,
           changedPointers: [ev],
           pointerType: pointerType,
           srcEvent: ev
       });
       if (removePointer) {
           // remove from the store
           store.splice(storeIndex, 1);
       }
   }

});

var SINGLE_TOUCH_INPUT_MAP = {

   touchstart: INPUT_START,
   touchmove: INPUT_MOVE,
   touchend: INPUT_END,
   touchcancel: INPUT_CANCEL

};

var SINGLE_TOUCH_TARGET_EVENTS = 'touchstart'; var SINGLE_TOUCH_WINDOW_EVENTS = 'touchstart touchmove touchend touchcancel';

/**

* Touch events input
* @constructor
* @extends Input
*/

function SingleTouchInput() {

   this.evTarget = SINGLE_TOUCH_TARGET_EVENTS;
   this.evWin = SINGLE_TOUCH_WINDOW_EVENTS;
   this.started = false;
   Input.apply(this, arguments);

}

inherit(SingleTouchInput, Input, {

   handler: function TEhandler(ev) {
       var type = SINGLE_TOUCH_INPUT_MAP[ev.type];
       // should we handle the touch events?
       if (type === INPUT_START) {
           this.started = true;
       }
       if (!this.started) {
           return;
       }
       var touches = normalizeSingleTouches.call(this, ev, type);
       // when done, reset the started state
       if (type & (INPUT_END | INPUT_CANCEL) && touches[0].length - touches[1].length === 0) {
           this.started = false;
       }
       this.callback(this.manager, type, {
           pointers: touches[0],
           changedPointers: touches[1],
           pointerType: INPUT_TYPE_TOUCH,
           srcEvent: ev
       });
   }

});

/**

* @this {TouchInput}
* @param {Object} ev
* @param {Number} type flag
* @returns {undefined|Array} [all, changed]
*/

function normalizeSingleTouches(ev, type) {

   var all = toArray(ev.touches);
   var changed = toArray(ev.changedTouches);
   if (type & (INPUT_END | INPUT_CANCEL)) {
       all = uniqueArray(all.concat(changed), 'identifier', true);
   }
   return [all, changed];

}

var TOUCH_INPUT_MAP = {

   touchstart: INPUT_START,
   touchmove: INPUT_MOVE,
   touchend: INPUT_END,
   touchcancel: INPUT_CANCEL

};

var TOUCH_TARGET_EVENTS = 'touchstart touchmove touchend touchcancel';

/**

* Multi-user touch events input
* @constructor
* @extends Input
*/

function TouchInput() {

   this.evTarget = TOUCH_TARGET_EVENTS;
   this.targetIds = {};
   Input.apply(this, arguments);

}

inherit(TouchInput, Input, {

   handler: function MTEhandler(ev) {
       var type = TOUCH_INPUT_MAP[ev.type];
       var touches = getTouches.call(this, ev, type);
       if (!touches) {
           return;
       }
       this.callback(this.manager, type, {
           pointers: touches[0],
           changedPointers: touches[1],
           pointerType: INPUT_TYPE_TOUCH,
           srcEvent: ev
       });
   }

});

/**

* @this {TouchInput}
* @param {Object} ev
* @param {Number} type flag
* @returns {undefined|Array} [all, changed]
*/

function getTouches(ev, type) {

   var allTouches = toArray(ev.touches);
   var targetIds = this.targetIds;
   // when there is only one touch, the process can be simplified
   if (type & (INPUT_START | INPUT_MOVE) && allTouches.length === 1) {
       targetIds[allTouches[0].identifier] = true;
       return [allTouches, allTouches];
   }
   var i,
       targetTouches,
       changedTouches = toArray(ev.changedTouches),
       changedTargetTouches = [],
       target = this.target;
   // get target touches from touches
   targetTouches = allTouches.filter(function(touch) {
       return hasParent(touch.target, target);
   });
   // collect touches
   if (type === INPUT_START) {
       i = 0;
       while (i < targetTouches.length) {
           targetIds[targetTouches[i].identifier] = true;
           i++;
       }
   }
   // filter changed touches to only contain touches that exist in the collected target ids
   i = 0;
   while (i < changedTouches.length) {
       if (targetIds[changedTouches[i].identifier]) {
           changedTargetTouches.push(changedTouches[i]);
       }
       // cleanup removed touches
       if (type & (INPUT_END | INPUT_CANCEL)) {
           delete targetIds[changedTouches[i].identifier];
       }
       i++;
   }
   if (!changedTargetTouches.length) {
       return;
   }
   return [
       // merge targetTouches with changedTargetTouches so it contains ALL touches, including 'end' and 'cancel'
       uniqueArray(targetTouches.concat(changedTargetTouches), 'identifier', true),
       changedTargetTouches
   ];

}

/**

* Combined touch and mouse input
*
* Touch has a higher priority then mouse, and while touching no mouse events are allowed.
* This because touch devices also emit mouse events while doing a touch.
*
* @constructor
* @extends Input
*/

function TouchMouseInput() {

   Input.apply(this, arguments);
   var handler = bindFn(this.handler, this);
   this.touch = new TouchInput(this.manager, handler);
   this.mouse = new MouseInput(this.manager, handler);

}

inherit(TouchMouseInput, Input, {

   /**
    * handle mouse and touch events
    * @param {Hammer} manager
    * @param {String} inputEvent
    * @param {Object} inputData
    */
   handler: function TMEhandler(manager, inputEvent, inputData) {
       var isTouch = (inputData.pointerType == INPUT_TYPE_TOUCH),
           isMouse = (inputData.pointerType == INPUT_TYPE_MOUSE);
       // when we're in a touch event, so  block all upcoming mouse events
       // most mobile browser also emit mouseevents, right after touchstart
       if (isTouch) {
           this.mouse.allow = false;
       } else if (isMouse && !this.mouse.allow) {
           return;
       }
       // reset the allowMouse when we're done
       if (inputEvent & (INPUT_END | INPUT_CANCEL)) {
           this.mouse.allow = true;
       }
       this.callback(manager, inputEvent, inputData);
   },
   /**
    * remove the event listeners
    */
   destroy: function destroy() {
       this.touch.destroy();
       this.mouse.destroy();
   }

});

var PREFIXED_TOUCH_ACTION = prefixed(TEST_ELEMENT.style, 'touchAction'); var NATIVE_TOUCH_ACTION = PREFIXED_TOUCH_ACTION !== undefined;

// magical touchAction value var TOUCH_ACTION_COMPUTE = 'compute'; var TOUCH_ACTION_AUTO = 'auto'; var TOUCH_ACTION_MANIPULATION = 'manipulation'; // not implemented var TOUCH_ACTION_NONE = 'none'; var TOUCH_ACTION_PAN_X = 'pan-x'; var TOUCH_ACTION_PAN_Y = 'pan-y';

/**

* Touch Action
* sets the touchAction property or uses the js alternative
* @param {Manager} manager
* @param {String} value
* @constructor
*/

function TouchAction(manager, value) {

   this.manager = manager;
   this.set(value);

}

TouchAction.prototype = {

   /**
    * set the touchAction value on the element or enable the polyfill
    * @param {String} value
    */
   set: function(value) {
       // find out the touch-action by the event handlers
       if (value == TOUCH_ACTION_COMPUTE) {
           value = this.compute();
       }
       if (NATIVE_TOUCH_ACTION) {
           this.manager.element.style[PREFIXED_TOUCH_ACTION] = value;
       }
       this.actions = value.toLowerCase().trim();
   },
   /**
    * just re-set the touchAction value
    */
   update: function() {
       this.set(this.manager.options.touchAction);
   },
   /**
    * compute the value for the touchAction property based on the recognizer's settings
    * @returns {String} value
    */
   compute: function() {
       var actions = [];
       each(this.manager.recognizers, function(recognizer) {
           if (boolOrFn(recognizer.options.enable, [recognizer])) {
               actions = actions.concat(recognizer.getTouchAction());
           }
       });
       return cleanTouchActions(actions.join(' '));
   },
   /**
    * this method is called on each input cycle and provides the preventing of the browser behavior
    * @param {Object} input
    */
   preventDefaults: function(input) {
       // not needed with native support for the touchAction property
       if (NATIVE_TOUCH_ACTION) {
           return;
       }
       var srcEvent = input.srcEvent;
       var direction = input.offsetDirection;
       // if the touch action did prevented once this session
       if (this.manager.session.prevented) {
           srcEvent.preventDefault();
           return;
       }
       var actions = this.actions;
       var hasNone = inStr(actions, TOUCH_ACTION_NONE);
       var hasPanY = inStr(actions, TOUCH_ACTION_PAN_Y);
       var hasPanX = inStr(actions, TOUCH_ACTION_PAN_X);
       if (hasNone ||
           (hasPanY && direction & DIRECTION_HORIZONTAL) ||
           (hasPanX && direction & DIRECTION_VERTICAL)) {
           return this.preventSrc(srcEvent);
       }
   },
   /**
    * call preventDefault to prevent the browser's default behavior (scrolling in most cases)
    * @param {Object} srcEvent
    */
   preventSrc: function(srcEvent) {
       this.manager.session.prevented = true;
       srcEvent.preventDefault();
   }

};

/**

* when the touchActions are collected they are not a valid value, so we need to clean things up. *
* @param {String} actions
* @returns {*}
*/

function cleanTouchActions(actions) {

   // none
   if (inStr(actions, TOUCH_ACTION_NONE)) {
       return TOUCH_ACTION_NONE;
   }
   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 (hasPanX && hasPanY) {
       return TOUCH_ACTION_PAN_X + ' ' + TOUCH_ACTION_PAN_Y;
   }
   // pan-x OR pan-y
   if (hasPanX || hasPanY) {
       return hasPanX ? TOUCH_ACTION_PAN_X : TOUCH_ACTION_PAN_Y;
   }
   // manipulation
   if (inStr(actions, TOUCH_ACTION_MANIPULATION)) {
       return TOUCH_ACTION_MANIPULATION;
   }
   return TOUCH_ACTION_AUTO;

}

/**

* Recognizer flow explained; *
* All recognizers have the initial state of POSSIBLE when a input session starts.
* The definition of a input session is from the first input until the last input, with all it's movement in it. *
* Example session for mouse-input: mousedown -> mousemove -> mouseup
*
* On each recognizing cycle (see Manager.recognize) the .recognize() method is executed
* which determines with state it should be.
*
* If the recognizer has the state FAILED, CANCELLED or RECOGNIZED (equals ENDED), it is reset to
* POSSIBLE to give it another change on the next cycle.
*
*               Possible
*                  |
*            +-----+---------------+
*            |                     |
*      +-----+-----+               |
*      |           |               |
*   Failed      Cancelled          |
*                          +-------+------+
*                          |              |
*                      Recognized       Began
*                                         |
*                                      Changed
*                                         |
*                                  Ended/Recognized
*/

var STATE_POSSIBLE = 1; var STATE_BEGAN = 2; var STATE_CHANGED = 4; var STATE_ENDED = 8; var STATE_RECOGNIZED = STATE_ENDED; var STATE_CANCELLED = 16; var STATE_FAILED = 32;

/**

* Recognizer
* Every recognizer needs to extend from this class.
* @constructor
* @param {Object} options
*/

function Recognizer(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);
   this.state = STATE_POSSIBLE;
   this.simultaneous = {};
   this.requireFail = [];

}

Recognizer.prototype = {

   /**
    * @virtual
    * @type {Object}
    */
   defaults: {},
   /**
    * set options
    * @param {Object} options
    * @return {Recognizer}
    */
   set: function(options) {
       extend(this.options, options);
       // also update the touchAction, in case something changed about the directions/enabled state
       this.manager && this.manager.touchAction.update();
       return this;
   },
   /**
    * recognize simultaneous with an other recognizer.
    * @param {Recognizer} otherRecognizer
    * @returns {Recognizer} this
    */
   recognizeWith: function(otherRecognizer) {
       if (invokeArrayArg(otherRecognizer, 'recognizeWith', this)) {
           return this;
       }
       var simultaneous = this.simultaneous;
       otherRecognizer = getRecognizerByNameIfManager(otherRecognizer, this);
       if (!simultaneous[otherRecognizer.id]) {
           simultaneous[otherRecognizer.id] = otherRecognizer;
           otherRecognizer.recognizeWith(this);
       }
       return this;
   },
   /**
    * drop the simultaneous link. it doesnt remove the link on the other recognizer.
    * @param {Recognizer} otherRecognizer
    * @returns {Recognizer} this
    */
   dropRecognizeWith: function(otherRecognizer) {
       if (invokeArrayArg(otherRecognizer, 'dropRecognizeWith', this)) {
           return this;
       }
       otherRecognizer = getRecognizerByNameIfManager(otherRecognizer, this);
       delete this.simultaneous[otherRecognizer.id];
       return this;
   },
   /**
    * recognizer can only run when an other is failing
    * @param {Recognizer} otherRecognizer
    * @returns {Recognizer} this
    */
   requireFailure: function(otherRecognizer) {
       if (invokeArrayArg(otherRecognizer, 'requireFailure', this)) {
           return this;
       }
       var requireFail = this.requireFail;
       otherRecognizer = getRecognizerByNameIfManager(otherRecognizer, this);
       if (inArray(requireFail, otherRecognizer) === -1) {
           requireFail.push(otherRecognizer);
           otherRecognizer.requireFailure(this);
       }
       return this;
   },
   /**
    * drop the requireFailure link. it does not remove the link on the other recognizer.
    * @param {Recognizer} otherRecognizer
    * @returns {Recognizer} this
    */
   dropRequireFailure: function(otherRecognizer) {
       if (invokeArrayArg(otherRecognizer, 'dropRequireFailure', this)) {
           return this;
       }
       otherRecognizer = getRecognizerByNameIfManager(otherRecognizer, this);
       var index = inArray(this.requireFail, otherRecognizer);
       if (index > -1) {
           this.requireFail.splice(index, 1);
       }
       return this;
   },
   /**
    * has require failures boolean
    * @returns {boolean}
    */
   hasRequireFailures: function() {
       return this.requireFail.length > 0;
   },
   /**
    * if the recognizer can recognize simultaneous with an other recognizer
    * @param {Recognizer} otherRecognizer
    * @returns {Boolean}
    */
   canRecognizeWith: function(otherRecognizer) {
       return !!this.simultaneous[otherRecognizer.id];
   },
   /**
    * You should use `tryEmit` instead of `emit` directly to check
    * that all the needed recognizers has failed before emitting.
    * @param {Object} input
    */
   emit: function(input) {
       var self = this;
       var state = this.state;
       function emit(withState) {
           self.manager.emit(self.options.event + (withState ? stateStr(state) : ), input);
       }
       // 'panstart' and 'panmove'
       if (state < STATE_ENDED) {
           emit(true);
       }
       emit(); // simple 'eventName' events
       // panend and pancancel
       if (state >= STATE_ENDED) {
           emit(true);
       }
   },
   /**
    * Check that all the require failure recognizers has failed,
    * if true, it emits a gesture event,
    * otherwise, setup the state to FAILED.
    * @param {Object} input
    */
   tryEmit: function(input) {
       if (this.canEmit()) {
           return this.emit(input);
       }
       // it's failing anyway
       this.state = STATE_FAILED;
   },
   /**
    * can we emit?
    * @returns {boolean}
    */
   canEmit: function() {
       var i = 0;
       while (i < this.requireFail.length) {
           if (!(this.requireFail[i].state & (STATE_FAILED | STATE_POSSIBLE))) {
               return false;
           }
           i++;
       }
       return true;
   },
   /**
    * update the recognizer
    * @param {Object} inputData
    */
   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);
       // is is enabled and allow recognizing?
       if (!boolOrFn(this.options.enable, [this, inputDataClone])) {
           this.reset();
           this.state = STATE_FAILED;
           return;
       }
       // reset when we've reached the end
       if (this.state & (STATE_RECOGNIZED | STATE_CANCELLED | STATE_FAILED)) {
           this.state = STATE_POSSIBLE;
       }
       this.state = this.process(inputDataClone);
       // the recognizer has recognized a gesture
       // so trigger an event
       if (this.state & (STATE_BEGAN | STATE_CHANGED | STATE_ENDED | STATE_CANCELLED)) {
           this.tryEmit(inputDataClone);
       }
   },
   /**
    * return the state of the recognizer
    * the actual recognizing happens in this method
    * @virtual
    * @param {Object} inputData
    * @returns {Const} STATE
    */
   process: function(inputData) { }, // jshint ignore:line
   /**
    * return the preferred touch-action
    * @virtual
    * @returns {Array}
    */
   getTouchAction: function() { },
   /**
    * called when the gesture isn't allowed to recognize
    * like when another is being recognized or it is disabled
    * @virtual
    */
   reset: function() { }

};

/**

* get a usable string, used as event postfix
* @param {Const} state
* @returns {String} state
*/

function stateStr(state) {

   if (state & STATE_CANCELLED) {
       return 'cancel';
   } else if (state & STATE_ENDED) {
       return 'end';
   } else if (state & STATE_CHANGED) {
       return 'move';
   } else if (state & STATE_BEGAN) {
       return 'start';
   }
   return ;

}

/**

* direction cons to string
* @param {Const} direction
* @returns {String}
*/

function directionStr(direction) {

   if (direction == DIRECTION_DOWN) {
       return 'down';
   } else if (direction == DIRECTION_UP) {
       return 'up';
   } else if (direction == DIRECTION_LEFT) {
       return 'left';
   } else if (direction == DIRECTION_RIGHT) {
       return 'right';
   }
   return ;

}

/**

* get a recognizer by name if it is bound to a manager
* @param {Recognizer|String} otherRecognizer
* @param {Recognizer} recognizer
* @returns {Recognizer}
*/

function getRecognizerByNameIfManager(otherRecognizer, recognizer) {

   var manager = recognizer.manager;
   if (manager) {
       return manager.get(otherRecognizer);
   }
   return otherRecognizer;

}

/**

* This recognizer is just used as a base for the simple attribute recognizers.
* @constructor
* @extends Recognizer
*/

function AttrRecognizer() {

   Recognizer.apply(this, arguments);

}

inherit(AttrRecognizer, Recognizer, {

   /**
    * @namespace
    * @memberof AttrRecognizer
    */
   defaults: {
       /**
        * @type {Number}
        * @default 1
        */
       pointers: 1
   },
   /**
    * Used to check if it the recognizer receives valid input, like input.distance > 10.
    * @memberof AttrRecognizer
    * @param {Object} input
    * @returns {Boolean} recognized
    */
   attrTest: function(input) {
       var optionPointers = this.options.pointers;
       return optionPointers === 0 || input.pointers.length === optionPointers;
   },
   /**
    * Process the input and return the state for the recognizer
    * @memberof AttrRecognizer
    * @param {Object} input
    * @returns {*} State
    */
   process: function(input) {
       var state = this.state;
       var eventType = input.eventType;
       var isRecognized = state & (STATE_BEGAN | STATE_CHANGED);
       var isValid = this.attrTest(input);
       // on cancel input and we've recognized before, return STATE_CANCELLED
       if (isRecognized && (eventType & INPUT_CANCEL || !isValid)) {
           return state | STATE_CANCELLED;
       } else if (isRecognized || isValid) {
           if (eventType & INPUT_END) {
               return state | STATE_ENDED;
           } else if (!(state & STATE_BEGAN)) {
               return STATE_BEGAN;
           }
           return state | STATE_CHANGED;
       }
       return STATE_FAILED;
   }

});

/**

* Pan
* Recognized when the pointer is down and moved in the allowed direction.
* @constructor
* @extends AttrRecognizer
*/

function PanRecognizer() {

   AttrRecognizer.apply(this, arguments);
   this.pX = null;
   this.pY = null;

}

inherit(PanRecognizer, AttrRecognizer, {

   /**
    * @namespace
    * @memberof PanRecognizer
    */
   defaults: {
       event: 'pan',
       threshold: 10,
       pointers: 1,
       direction: DIRECTION_ALL
   },
   getTouchAction: function() {
       var direction = this.options.direction;
       var actions = [];
       if (direction & DIRECTION_HORIZONTAL) {
           actions.push(TOUCH_ACTION_PAN_Y);
       }
       if (direction & DIRECTION_VERTICAL) {
           actions.push(TOUCH_ACTION_PAN_X);
       }
       return actions;
   },
   directionTest: function(input) {
       var options = this.options;
       var hasMoved = true;
       var distance = input.distance;
       var direction = input.direction;
       var x = input.deltaX;
       var y = input.deltaY;
       // lock to axis?
       if (!(direction & options.direction)) {
           if (options.direction & DIRECTION_HORIZONTAL) {
               direction = (x === 0) ? DIRECTION_NONE : (x < 0) ? DIRECTION_LEFT : DIRECTION_RIGHT;
               hasMoved = x != this.pX;
               distance = Math.abs(input.deltaX);
           } else {
               direction = (y === 0) ? DIRECTION_NONE : (y < 0) ? DIRECTION_UP : DIRECTION_DOWN;
               hasMoved = y != this.pY;
               distance = Math.abs(input.deltaY);
           }
       }
       input.direction = direction;
       return hasMoved && distance > options.threshold && direction & options.direction;
   },
   attrTest: function(input) {
       return AttrRecognizer.prototype.attrTest.call(this, input) &&
           (this.state & STATE_BEGAN || (!(this.state & STATE_BEGAN) && this.directionTest(input)));
   },
   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);
       }
       this._super.emit.call(this, input);
   }

});

/**

* Pinch
* Recognized when two or more pointers are moving toward (zoom-in) or away from each other (zoom-out).
* @constructor
* @extends AttrRecognizer
*/

function PinchRecognizer() {

   AttrRecognizer.apply(this, arguments);

}

inherit(PinchRecognizer, AttrRecognizer, {

   /**
    * @namespace
    * @memberof PinchRecognizer
    */
   defaults: {
       event: 'pinch',
       threshold: 0,
       pointers: 2
   },
   getTouchAction: function() {
       return [TOUCH_ACTION_NONE];
   },
   attrTest: function(input) {
       return this._super.attrTest.call(this, input) &&
           (Math.abs(input.scale - 1) > this.options.threshold || this.state & STATE_BEGAN);
   },
   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);
       }
   }

});

/**

* Press
* Recognized when the pointer is down for x ms without any movement.
* @constructor
* @extends Recognizer
*/

function PressRecognizer() {

   Recognizer.apply(this, arguments);
   this._timer = null;
   this._input = null;

}

inherit(PressRecognizer, Recognizer, {

   /**
    * @namespace
    * @memberof PressRecognizer
    */
   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
   },
   getTouchAction: function() {
       return [TOUCH_ACTION_AUTO];
   },
   process: function(input) {
       var options = this.options;
       var validPointers = input.pointers.length === options.pointers;
       var validMovement = input.distance < options.threshold;
       var validTime = input.deltaTime > options.time;
       this._input = input;
       // we only allow little movement
       // and we've reached an end event, so a tap is possible
       if (!validMovement || !validPointers || (input.eventType & (INPUT_END | INPUT_CANCEL) && !validTime)) {
           this.reset();
       } else if (input.eventType & INPUT_START) {
           this.reset();
           this._timer = setTimeoutContext(function() {
               this.state = STATE_RECOGNIZED;
               this.tryEmit();
           }, options.time, this);
       } else if (input.eventType & INPUT_END) {
           return STATE_RECOGNIZED;
       }
       return STATE_FAILED;
   },
   reset: function() {
       clearTimeout(this._timer);
   },
   emit: function(input) {
       if (this.state !== STATE_RECOGNIZED) {
           return;
       }
       if (input && (input.eventType & INPUT_END)) {
           this.manager.emit(this.options.event + 'up', input);
       } else {
           this._input.timeStamp = now();
           this.manager.emit(this.options.event, this._input);
       }
   }

});

/**

* Rotate
* Recognized when two or more pointer are moving in a circular motion.
* @constructor
* @extends AttrRecognizer
*/

function RotateRecognizer() {

   AttrRecognizer.apply(this, arguments);

}

inherit(RotateRecognizer, AttrRecognizer, {

   /**
    * @namespace
    * @memberof RotateRecognizer
    */
   defaults: {
       event: 'rotate',
       threshold: 0,
       pointers: 2
   },
   getTouchAction: function() {
       return [TOUCH_ACTION_NONE];
   },
   attrTest: function(input) {
       return this._super.attrTest.call(this, input) &&
           (Math.abs(input.rotation) > this.options.threshold || this.state & STATE_BEGAN);
   }

});

/**

* Swipe
* Recognized when the pointer is moving fast (velocity), with enough distance in the allowed direction.
* @constructor
* @extends AttrRecognizer
*/

function SwipeRecognizer() {

   AttrRecognizer.apply(this, arguments);

}

inherit(SwipeRecognizer, AttrRecognizer, {

   /**
    * @namespace
    * @memberof SwipeRecognizer
    */
   defaults: {
       event: 'swipe',
       threshold: 10,
       velocity: 0.65,
       direction: DIRECTION_HORIZONTAL | DIRECTION_VERTICAL,
       pointers: 1
   },
   getTouchAction: function() {
       return PanRecognizer.prototype.getTouchAction.call(this);
   },
   attrTest: function(input) {
       var direction = this.options.direction;
       var velocity;
       if (direction & (DIRECTION_HORIZONTAL | DIRECTION_VERTICAL)) {
           velocity = input.velocity;
       } else if (direction & DIRECTION_HORIZONTAL) {
           velocity = input.velocityX;
       } else if (direction & DIRECTION_VERTICAL) {
           velocity = input.velocityY;
       }
       return this._super.attrTest.call(this, input) &&
           direction & input.direction &&
           input.distance > this.options.threshold &&
           abs(velocity) > this.options.velocity && input.eventType & INPUT_END;
   },
   emit: function(input) {
       var direction = directionStr(input.direction);
       if (direction) {
           this.manager.emit(this.options.event + direction, input);
       }
       this.manager.emit(this.options.event, input);
   }

});

/**

* A tap is ecognized when the pointer is doing a small tap/click. Multiple taps are recognized if they occur
* between the given interval and position. The delay option can be used to recognize multi-taps without firing
* a single tap.
*
* The eventData from the emitted event contains the property `tapCount`, which contains the amount of
* multi-taps being recognized.
* @constructor
* @extends Recognizer
*/

function TapRecognizer() {

   Recognizer.apply(this, arguments);
   // previous time and center,
   // used for tap counting
   this.pTime = false;
   this.pCenter = false;
   this._timer = null;
   this._input = null;
   this.count = 0;

}

inherit(TapRecognizer, Recognizer, {

   /**
    * @namespace
    * @memberof PinchRecognizer
    */
   defaults: {
       event: 'tap',
       pointers: 1,
       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
       posThreshold: 10 // a multi-tap can be a bit off the initial position
   },
   getTouchAction: function() {
       return [TOUCH_ACTION_MANIPULATION];
   },
   process: function(input) {
       var options = this.options;
       var validPointers = input.pointers.length === options.pointers;
       var validMovement = input.distance < options.threshold;
       var validTouchTime = input.deltaTime < options.time;
       this.reset();
       if ((input.eventType & INPUT_START) && (this.count === 0)) {
           return this.failTimeout();
       }
       // we only allow little movement
       // and we've reached an end event, so a tap is possible
       if (validMovement && validTouchTime && validPointers) {
           if (input.eventType != INPUT_END) {
               return this.failTimeout();
           }
           var validInterval = this.pTime ? (input.timeStamp - this.pTime < options.interval) : true;
           var validMultiTap = !this.pCenter || getDistance(this.pCenter, input.center) < options.posThreshold;
           this.pTime = input.timeStamp;
           this.pCenter = input.center;
           if (!validMultiTap || !validInterval) {
               this.count = 1;
           } else {
               this.count += 1;
           }
           this._input = input;
           // if tap count matches we have recognized it,
           // else it has began recognizing...
           var tapCount = this.count % options.taps;
           if (tapCount === 0) {
               // no failing requirements, immediately trigger the tap event
               // or wait as long as the multitap interval to trigger
               if (!this.hasRequireFailures()) {
                   return STATE_RECOGNIZED;
               } else {
                   this._timer = setTimeoutContext(function() {
                       this.state = STATE_RECOGNIZED;
                       this.tryEmit();
                   }, options.interval, this);
                   return STATE_BEGAN;
               }
           }
       }
       return STATE_FAILED;
   },
   failTimeout: function() {
       this._timer = setTimeoutContext(function() {
           this.state = STATE_FAILED;
       }, this.options.interval, this);
       return STATE_FAILED;
   },
   reset: function() {
       clearTimeout(this._timer);
   },
   emit: function() {
       if (this.state == STATE_RECOGNIZED ) {
           this._input.tapCount = this.count;
           this.manager.emit(this.options.event, this._input);
       }
   }

});

/**

* Simple way to create an manager with a default set of recognizers.
* @param {HTMLElement} element
* @param {Object} [options]
* @constructor
*/

function Hammer(element, options) {

   options = options || {};
   options.recognizers = ifUndefined(options.recognizers, Hammer.defaults.preset);
   return new Manager(element, options);

}

/**

* @const {string}
*/

Hammer.VERSION = '2.0.4';

/**

* default settings
* @namespace
*/

Hammer.defaults = {

   /**
    * set if DOM events are being triggered.
    * But this is slower and unused by simple implementations, so disabled by default.
    * @type {Boolean}
    * @default false
    */
   domEvents: false,
   /**
    * The value for the touchAction property/fallback.
    * When set to `compute` it will magically set the correct value based on the added recognizers.
    * @type {String}
    * @default compute
    */
   touchAction: TOUCH_ACTION_COMPUTE,
   /**
    * @type {Boolean}
    * @default true
    */
   enable: true,
   /**
    * EXPERIMENTAL FEATURE -- can be removed/changed
    * Change the parent input target element.
    * If Null, then it is being set the to main element.
    * @type {Null|EventTarget}
    * @default null
    */
   inputTarget: null,
   /**
    * force an input class
    * @type {Null|Function}
    * @default null
    */
   inputClass: null,
   /**
    * Default recognizer setup when calling `Hammer()`
    * When creating a new Manager these will be skipped.
    * @type {Array}
    */
   preset: [
       // RecognizerClass, options, [recognizeWith, ...], [requireFailure, ...]
       [RotateRecognizer, { enable: false }],
       [PinchRecognizer, { enable: false }, ['rotate']],
       [SwipeRecognizer,{ direction: DIRECTION_HORIZONTAL }],
       [PanRecognizer, { direction: DIRECTION_HORIZONTAL }, ['swipe']],
       [TapRecognizer],
       [TapRecognizer, { event: 'doubletap', taps: 2 }, ['tap']],
       [PressRecognizer]
   ],
   /**
    * Some CSS properties can be used to improve the working of Hammer.
    * Add them to this method and they will be set when creating a new Manager.
    * @namespace
    */
   cssProps: {
       /**
        * Disables text selection to improve the dragging gesture. Mainly for desktop browsers.
        * @type {String}
        * @default 'none'
        */
       userSelect: 'none',
       /**
        * Disable the Windows Phone grippers when pressing an element.
        * @type {String}
        * @default 'none'
        */
       touchSelect: 'none',
       /**
        * Disables the default callout shown when you touch and hold a touch target.
        * On iOS, when you touch and hold a touch target such as a link, Safari displays
        * a callout containing information about the link. This property allows you to disable that callout.
        * @type {String}
        * @default 'none'
        */
       touchCallout: 'none',
       /**
        * Specifies whether zooming is enabled. Used by IE10>
        * @type {String}
        * @default 'none'
        */
       contentZooming: 'none',
       /**
        * Specifies that an entire element should be draggable instead of its contents. Mainly for desktop browsers.
        * @type {String}
        * @default 'none'
        */
       userDrag: 'none',
       /**
        * Overrides the highlight color shown when the user taps a link or a JavaScript
        * clickable element in iOS. This property obeys the alpha value, if specified.
        * @type {String}
        * @default 'rgba(0,0,0,0)'
        */
       tapHighlightColor: 'rgba(0,0,0,0)'
   }

};

var STOP = 1; var FORCED_STOP = 2;

/**

* Manager
* @param {HTMLElement} element
* @param {Object} [options]
* @constructor
*/

function Manager(element, options) {

   options = options || {};
   this.options = merge(options, Hammer.defaults);
   this.options.inputTarget = this.options.inputTarget || element;
   this.handlers = {};
   this.session = {};
   this.recognizers = [];
   this.element = element;
   this.input = createInputInstance(this);
   this.touchAction = new TouchAction(this, this.options.touchAction);
   toggleCssProps(this, true);
   each(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]);
   }, this);

}

Manager.prototype = {

   /**
    * set options
    * @param {Object} options
    * @returns {Manager}
    */
   set: function(options) {
       extend(this.options, options);
       // Options that need a little more setup
       if (options.touchAction) {
           this.touchAction.update();
       }
       if (options.inputTarget) {
           // Clean up existing event listeners and reinitialize
           this.input.destroy();
           this.input.target = options.inputTarget;
           this.input.init();
       }
       return this;
   },
   /**
    * stop recognizing for this session.
    * This session will be discarded, when a new [input]start event is fired.
    * When forced, the recognizer cycle is stopped immediately.
    * @param {Boolean} [force]
    */
   stop: function(force) {
       this.session.stopped = force ? FORCED_STOP : STOP;
   },
   /**
    * run the recognizers!
    * called by the inputHandler function on every movement of the pointers (touches)
    * it walks through all the recognizers and tries to detect the gesture that is being made
    * @param {Object} inputData
    */
   recognize: function(inputData) {
       var session = this.session;
       if (session.stopped) {
           return;
       }
       // run the touch-action polyfill
       this.touchAction.preventDefaults(inputData);
       var recognizer;
       var recognizers = this.recognizers;
       // this holds the recognizer that is being recognized.
       // so the recognizer's state needs to be BEGAN, CHANGED, ENDED or RECOGNIZED
       // if no recognizer is detecting a thing, it is set to `null`
       var curRecognizer = session.curRecognizer;
       // reset when the last recognizer is recognized
       // or when we're in a new session
       if (!curRecognizer || (curRecognizer && curRecognizer.state & STATE_RECOGNIZED)) {
           curRecognizer = session.curRecognizer = null;
       }
       var i = 0;
       while (i < recognizers.length) {
           recognizer = recognizers[i];
           // find out if we are allowed try to recognize the input for this one.
           // 1.   allow if the session is NOT forced stopped (see the .stop() method)
           // 2.   allow if we still haven't recognized a gesture in this session, or the this recognizer is the one
           //      that is being recognized.
           // 3.   allow if the recognizer is allowed to run simultaneous with the current recognized recognizer.
           //      this can be setup with the `recognizeWith()` method on the recognizer.
           if (session.stopped !== FORCED_STOP && ( // 1
                   !curRecognizer || recognizer == curRecognizer || // 2
                   recognizer.canRecognizeWith(curRecognizer))) { // 3
               recognizer.recognize(inputData);
           } else {
               recognizer.reset();
           }
           // if the recognizer has been recognizing the input as a valid gesture, we want to store this one as the
           // current active recognizer. but only if we don't already have an active recognizer
           if (!curRecognizer && recognizer.state & (STATE_BEGAN | STATE_CHANGED | STATE_ENDED)) {
               curRecognizer = session.curRecognizer = recognizer;
           }
           i++;
       }
   },
   /**
    * get a recognizer by its event name.
    * @param {Recognizer|String} recognizer
    * @returns {Recognizer|Null}
    */
   get: function(recognizer) {
       if (recognizer instanceof Recognizer) {
           return recognizer;
       }
       var recognizers = this.recognizers;
       for (var i = 0; i < recognizers.length; i++) {
           if (recognizers[i].options.event == recognizer) {
               return recognizers[i];
           }
       }
       return null;
   },
   /**
    * add a recognizer to the manager
    * existing recognizers with the same event name will be removed
    * @param {Recognizer} recognizer
    * @returns {Recognizer|Manager}
    */
   add: function(recognizer) {
       if (invokeArrayArg(recognizer, 'add', this)) {
           return this;
       }
       // remove existing
       var existing = this.get(recognizer.options.event);
       if (existing) {
           this.remove(existing);
       }
       this.recognizers.push(recognizer);
       recognizer.manager = this;
       this.touchAction.update();
       return recognizer;
   },
   /**
    * remove a recognizer by name or instance
    * @param {Recognizer|String} recognizer
    * @returns {Manager}
    */
   remove: function(recognizer) {
       if (invokeArrayArg(recognizer, 'remove', this)) {
           return this;
       }
       var recognizers = this.recognizers;
       recognizer = this.get(recognizer);
       recognizers.splice(inArray(recognizers, recognizer), 1);
       this.touchAction.update();
       return this;
   },
   /**
    * bind event
    * @param {String} events
    * @param {Function} handler
    * @returns {EventEmitter} this
    */
   on: function(events, handler) {
       var handlers = this.handlers;
       each(splitStr(events), function(event) {
           handlers[event] = handlers[event] || [];
           handlers[event].push(handler);
       });
       return this;
   },
   /**
    * unbind event, leave emit blank to remove all handlers
    * @param {String} events
    * @param {Function} [handler]
    * @returns {EventEmitter} this
    */
   off: function(events, handler) {
       var handlers = this.handlers;
       each(splitStr(events), function(event) {
           if (!handler) {
               delete handlers[event];
           } else {
               handlers[event].splice(inArray(handlers[event], handler), 1);
           }
       });
       return this;
   },
   /**
    * emit event to the listeners
    * @param {String} event
    * @param {Object} data
    */
   emit: function(event, data) {
       // we also want to trigger dom events
       if (this.options.domEvents) {
           triggerDomEvent(event, data);
       }
       // no handlers, so skip it all
       var handlers = this.handlers[event] && this.handlers[event].slice();
       if (!handlers || !handlers.length) {
           return;
       }
       data.type = event;
       data.preventDefault = function() {
           data.srcEvent.preventDefault();
       };
       var i = 0;
       while (i < handlers.length) {
           handlers[i](data);
           i++;
       }
   },
   /**
    * destroy the manager and unbinds all events
    * it doesn't unbind dom events, that is the user own responsibility
    */
   destroy: function() {
       this.element && toggleCssProps(this, false);
       this.handlers = {};
       this.session = {};
       this.input.destroy();
       this.element = null;
   }

};

/**

* add/remove the css properties as defined in manager.options.cssProps
* @param {Manager} manager
* @param {Boolean} add
*/

function toggleCssProps(manager, add) {

   var element = manager.element;
   each(manager.options.cssProps, function(value, name) {
       element.style[prefixed(element.style, name)] = add ? value : ;
   });

}

/**

* trigger dom event
* @param {String} event
* @param {Object} data
*/

function triggerDomEvent(event, data) {

   var gestureEvent = document.createEvent('Event');
   gestureEvent.initEvent(event, true, true);
   gestureEvent.gesture = data;
   data.target.dispatchEvent(gestureEvent);

}

extend(Hammer, {

   INPUT_START: INPUT_START,
   INPUT_MOVE: INPUT_MOVE,
   INPUT_END: INPUT_END,
   INPUT_CANCEL: INPUT_CANCEL,
   STATE_POSSIBLE: STATE_POSSIBLE,
   STATE_BEGAN: STATE_BEGAN,
   STATE_CHANGED: STATE_CHANGED,
   STATE_ENDED: STATE_ENDED,
   STATE_RECOGNIZED: STATE_RECOGNIZED,
   STATE_CANCELLED: STATE_CANCELLED,
   STATE_FAILED: STATE_FAILED,
   DIRECTION_NONE: DIRECTION_NONE,
   DIRECTION_LEFT: DIRECTION_LEFT,
   DIRECTION_RIGHT: DIRECTION_RIGHT,
   DIRECTION_UP: DIRECTION_UP,
   DIRECTION_DOWN: DIRECTION_DOWN,
   DIRECTION_HORIZONTAL: DIRECTION_HORIZONTAL,
   DIRECTION_VERTICAL: DIRECTION_VERTICAL,
   DIRECTION_ALL: DIRECTION_ALL,
   Manager: Manager,
   Input: Input,
   TouchAction: TouchAction,
   TouchInput: TouchInput,
   MouseInput: MouseInput,
   PointerEventInput: PointerEventInput,
   TouchMouseInput: TouchMouseInput,
   SingleTouchInput: SingleTouchInput,
   Recognizer: Recognizer,
   AttrRecognizer: AttrRecognizer,
   Tap: TapRecognizer,
   Pan: PanRecognizer,
   Swipe: SwipeRecognizer,
   Pinch: PinchRecognizer,
   Rotate: RotateRecognizer,
   Press: PressRecognizer,
   on: addEventListeners,
   off: removeEventListeners,
   each: each,
   merge: merge,
   extend: extend,
   inherit: inherit,
   bindFn: bindFn,
   prefixed: prefixed

});

if (typeof module != 'undefined' && module.exports) {

   module.exports = Hammer;

} else {

   window[exportName] = Hammer;

}

})(window, document, 'Hammer');