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

 
(7 intermediate revisions by the same user not shown)
Line 1: Line 1:
<html>
 
<body>
 
<script>
 
/*
 
*
 
* jqTransform
 
* by mathieu vilaplana mvilaplana@dfc-e.com
 
* Designer ghyslain armand garmand@dfc-e.com
 
*
 
*
 
* Version 1.0 25.09.08
 
* Version 1.1 06.08.09
 
* Add event click on Checkbox and Radio
 
* Auto calculate the size of a select element
 
* Can now, disabled the elements
 
* Correct bug in ff if click on select (overflow=hidden)
 
* No need any more preloading !!
 
*
 
******************************************** */
 
 
function($){
 
var defaultOptions = {preloadImg:true};
 
var jqTransformImgPreloaded = false;
 
 
var jqTransformPreloadHoverFocusImg = function(strImgUrl) {
 
//guillemets to remove for ie
 
strImgUrl = strImgUrl.replace(/^url\((.*)\)/,'$1').replace(/^\"(.*)\"$/,'$1');
 
var imgHover = new Image();
 
imgHover.src = strImgUrl.replace(/\.([a-zA-Z]*)$/,'-hover.$1');
 
var imgFocus = new Image();
 
imgFocus.src = strImgUrl.replace(/\.([a-zA-Z]*)$/,'-focus.$1');
 
};
 
 
 
/***************************
 
  Labels
 
***************************/
 
var jqTransformGetLabel = function(objfield){
 
var selfForm = $(objfield.get(0).form);
 
var oLabel = objfield.next();
 
if(!oLabel.is('label')) {
 
oLabel = objfield.prev();
 
if(oLabel.is('label')){
 
var inputname = objfield.attr('id');
 
if(inputname){
 
oLabel = selfForm.find('label[for="'+inputname+'"]');
 
}
 
}
 
}
 
if(oLabel.is('label')){return oLabel.css('cursor','pointer');}
 
return false;
 
};
 
 
/* Hide all open selects */
 
var jqTransformHideSelect = function(oTarget){
 
var ulVisible = $('.jqTransformSelectWrapper ul:visible');
 
ulVisible.each(function(){
 
var oSelect = $(this).parents(".jqTransformSelectWrapper:first").find("select").get(0);
 
//do not hide if click on the label object associated to the select
 
if( !(oTarget && oSelect.oLabel && oSelect.oLabel.get(0) == oTarget.get(0)) ){$(this).hide();}
 
});
 
};
 
/* Check for an external click */
 
var jqTransformCheckExternalClick = function(event) {
 
if ($(event.target).parents('.jqTransformSelectWrapper').length === 0) { jqTransformHideSelect($(event.target)); }
 
};
 
 
/* Apply document listener */
 
var jqTransformAddDocumentListener = function (){
 
$(document).mousedown(jqTransformCheckExternalClick);
 
};
 
 
/* Add a new handler for the reset action */
 
var jqTransformReset = function(f){
 
var sel;
 
$('.jqTransformSelectWrapper select', f).each(function(){sel = (this.selectedIndex<0) ? 0 : this.selectedIndex; $('ul', $(this).parent()).each(function(){$('a:eq('+ sel +')', this).click();});});
 
$('a.jqTransformCheckbox, a.jqTransformRadio', f).removeClass('jqTransformChecked');
 
$('input:checkbox, input:radio', f).each(function(){if(this.checked){$('a', $(this).parent()).addClass('jqTransformChecked');}});
 
};
 
 
/***************************
 
  Buttons
 
***************************/
 
$.fn.jqTransInputButton = function(){
 
return this.each(function(){
 
var newBtn = $('<button id="'+ this.id +'" name="'+ this.name +'" type="'+ this.type +'" class="'+ this.className +' jqTransformButton"><span><span>'+ $(this).attr('value') +'</span></span>')
 
.hover(function(){newBtn.addClass('jqTransformButton_hover');},function(){newBtn.removeClass('jqTransformButton_hover')})
 
.mousedown(function(){newBtn.addClass('jqTransformButton_click')})
 
.mouseup(function(){newBtn.removeClass('jqTransformButton_click')})
 
;
 
$(this).replaceWith(newBtn);
 
});
 
};
 
 
/***************************
 
  Text Fields
 
***************************/
 
$.fn.jqTransInputText = function(){
 
return this.each(function(){
 
var $input = $(this);
 
 
if($input.hasClass('jqtranformdone') || !$input.is('input')) {return;}
 
$input.addClass('jqtranformdone');
 
 
var oLabel = jqTransformGetLabel($(this));
 
oLabel && oLabel.bind('click',function(){$input.focus();});
 
 
var inputSize=$input.width();
 
if($input.attr('size')){
 
inputSize = $input.attr('size')*10;
 
$input.css('width',inputSize);
 
}
 
 
$input.addClass("jqTransformInput").wrap('<div class="jqTransformInputWrapper"><div class="jqTransformInputInner"><div></div></div></div>');
 
var $wrapper = $input.parent().parent().parent();
 
$wrapper.css("width", inputSize+10);
 
$input
 
.focus(function(){$wrapper.addClass("jqTransformInputWrapper_focus");})
 
.blur(function(){$wrapper.removeClass("jqTransformInputWrapper_focus");})
 
.hover(function(){$wrapper.addClass("jqTransformInputWrapper_hover");},function(){$wrapper.removeClass("jqTransformInputWrapper_hover");})
 
;
 
 
/* If this is safari we need to add an extra class */
 
$.browser.safari && $wrapper.addClass('jqTransformSafari');
 
$.browser.safari && $input.css('width',$wrapper.width()+16);
 
this.wrapper = $wrapper;
 
 
});
 
};
 
 
/***************************
 
  Check Boxes
 
***************************/
 
$.fn.jqTransCheckBox = function(){
 
return this.each(function(){
 
if($(this).hasClass('jqTransformHidden')) {return;}
 
 
var $input = $(this);
 
var inputSelf = this;
 
 
//set the click on the label
 
var oLabel=jqTransformGetLabel($input);
 
oLabel && oLabel.click(function(){aLink.trigger('click');});
 
 
var aLink = $('<a href="#" class="jqTransformCheckbox"></a>');
 
//wrap and add the link
 
$input.addClass('jqTransformHidden').wrap('<span class="jqTransformCheckboxWrapper"></span>').parent().prepend(aLink);
 
//on change, change the class of the link
 
$input.change(function(){
 
this.checked && aLink.addClass('jqTransformChecked') || aLink.removeClass('jqTransformChecked');
 
return true;
 
});
 
// Click Handler, trigger the click and change event on the input
 
aLink.click(function(){
 
//do nothing if the original input is disabled
 
if($input.attr('disabled')){return false;}
 
//trigger the envents on the input object
 
$input.trigger('click').trigger("change");
 
return false;
 
});
 
 
// set the default state
 
this.checked && aLink.addClass('jqTransformChecked');
 
});
 
};
 
/***************************
 
  Radio Buttons
 
***************************/
 
$.fn.jqTransRadio = function(){
 
return this.each(function(){
 
if($(this).hasClass('jqTransformHidden')) {return;}
 
 
var $input = $(this);
 
var inputSelf = this;
 
 
oLabel = jqTransformGetLabel($input);
 
oLabel && oLabel.click(function(){aLink.trigger('click');});
 
 
var aLink = $('<a href="#" class="jqTransformRadio" rel="'+ this.name +'"></a>');
 
$input.addClass('jqTransformHidden').wrap('<span class="jqTransformRadioWrapper"></span>').parent().prepend(aLink);
 
 
$input.change(function(){
 
inputSelf.checked && aLink.addClass('jqTransformChecked') || aLink.removeClass('jqTransformChecked');
 
return true;
 
});
 
// Click Handler
 
aLink.click(function(){
 
if($input.attr('disabled')){return false;}
 
$input.trigger('click').trigger('change');
 
 
// uncheck all others of same name input radio elements
 
$('input[name="'+$input.attr('name')+'"]',inputSelf.form).not($input).each(function(){
 
$(this).attr('type')=='radio' && $(this).trigger('change');
 
});
 
 
return false;
 
});
 
// set the default state
 
inputSelf.checked && aLink.addClass('jqTransformChecked');
 
});
 
};
 
 
/***************************
 
  TextArea
 
***************************/
 
$.fn.jqTransTextarea = function(){
 
return this.each(function(){
 
var textarea = $(this);
 
 
if(textarea.hasClass('jqtransformdone')) {return;}
 
textarea.addClass('jqtransformdone');
 
 
oLabel = jqTransformGetLabel(textarea);
 
oLabel && oLabel.click(function(){textarea.focus();});
 
 
var strTable = '<table cellspacing="0" cellpadding="0" border="0" class="jqTransformTextarea">';
 
strTable +='<tr><td id="jqTransformTextarea-tl"></td><td id="jqTransformTextarea-tm"></td><td id="jqTransformTextarea-tr"></td></tr>';
 
strTable +='<tr><td id="jqTransformTextarea-ml">&nbsp;</td><td id="jqTransformTextarea-mm"><div></div></td><td id="jqTransformTextarea-mr">&nbsp;</td></tr>';
 
strTable +='<tr><td id="jqTransformTextarea-bl"></td><td id="jqTransformTextarea-bm"></td><td id="jqTransformTextarea-br"></td></tr>';
 
strTable +='</table>';
 
var oTable = $(strTable)
 
.insertAfter(textarea)
 
.hover(function(){
 
!oTable.hasClass('jqTransformTextarea-focus') && oTable.addClass('jqTransformTextarea-hover');
 
},function(){
 
oTable.removeClass('jqTransformTextarea-hover');
 
})
 
;
 
 
textarea
 
.focus(function(){oTable.removeClass('jqTransformTextarea-hover').addClass('jqTransformTextarea-focus');})
 
.blur(function(){oTable.removeClass('jqTransformTextarea-focus');})
 
.appendTo($('#jqTransformTextarea-mm div',oTable))
 
;
 
this.oTable = oTable;
 
if($.browser.safari){
 
$('#jqTransformTextarea-mm',oTable)
 
.addClass('jqTransformSafariTextarea')
 
.find('div')
 
.css('height',textarea.height())
 
.css('width',textarea.width())
 
;
 
}
 
});
 
};
 
 
/***************************
 
  Select
 
***************************/
 
$.fn.jqTransSelect = function(){
 
return this.each(function(index){
 
var $select = $(this);
 
 
if($select.hasClass('jqTransformHidden')) {return;}
 
if($select.attr('multiple')) {return;}
 
 
var oLabel  =  jqTransformGetLabel($select);
 
/* First thing we do is Wrap it */
 
var $wrapper = $select
 
.addClass('jqTransformHidden')
 
.wrap('<div class="jqTransformSelectWrapper"></div>')
 
.parent()
 
.css({zIndex: 10-index})
 
;
 
 
/* Now add the html for the select */
 
$wrapper.prepend('<div><span></span><a href="#" class="jqTransformSelectOpen"></a></div><ul></ul>');
 
var $ul = $('ul', $wrapper).css('width',$select.width()).hide();
 
/* Now we add the options */
 
$('option', this).each(function(i){
 
var oLi = $('<li><a href="#" index="'+ i +'">'+ $(this).html() +'</a></li>');
 
$ul.append(oLi);
 
});
 
 
/* Add click handler to the a */
 
$ul.find('a').click(function(){
 
$('a.selected', $wrapper).removeClass('selected');
 
$(this).addClass('selected');
 
/* Fire the onchange event */
 
if ($select[0].selectedIndex != $(this).attr('index') && $select[0].onchange) { $select[0].selectedIndex = $(this).attr('index'); $select[0].onchange(); }
 
$select[0].selectedIndex = $(this).attr('index');
 
$('span:eq(0)', $wrapper).html($(this).html());
 
$ul.hide();
 
return false;
 
});
 
/* Set the default */
 
$('a:eq('+ this.selectedIndex +')', $ul).click();
 
$('span:first', $wrapper).click(function(){$("a.jqTransformSelectOpen",$wrapper).trigger('click');});
 
oLabel && oLabel.click(function(){$("a.jqTransformSelectOpen",$wrapper).trigger('click');});
 
this.oLabel = oLabel;
 
 
/* Apply the click handler to the Open */
 
var oLinkOpen = $('a.jqTransformSelectOpen', $wrapper)
 
.click(function(){
 
//Check if box is already open to still allow toggle, but close all other selects
 
if( $ul.css('display') == 'none' ) {jqTransformHideSelect();}
 
if($select.attr('disabled')){return false;}
 
 
$ul.slideToggle('fast', function(){
 
var offSet = ($('a.selected', $ul).offset().top - $ul.offset().top);
 
$ul.animate({scrollTop: offSet});
 
});
 
return false;
 
})
 
;
 
 
// Set the new width
 
var iSelectWidth = $select.outerWidth();
 
var oSpan = $('span:first',$wrapper);
 
var newWidth = (iSelectWidth > oSpan.innerWidth())?iSelectWidth+oLinkOpen.outerWidth():$wrapper.width();
 
$wrapper.css('width',newWidth);
 
$ul.css('width',newWidth-2);
 
oSpan.css({width:iSelectWidth});
 
 
// Calculate the height if necessary, less elements that the default height
 
//show the ul to calculate the block, if ul is not displayed li height value is 0
 
$ul.css({display:'block',visibility:'hidden'});
 
var iSelectHeight = ($('li',$ul).length)*($('li:first',$ul).height());//+1 else bug ff
 
(iSelectHeight < $ul.height()) && $ul.css({height:iSelectHeight,'overflow':'hidden'});//hidden else bug with ff
 
$ul.css({display:'none',visibility:'visible'});
 
 
});
 
};
 
$.fn.jqTransform = function(options){
 
var opt = $.extend({},defaultOptions,options);
 
 
/* each form */
 
return this.each(function(){
 
var selfForm = $(this);
 
if(selfForm.hasClass('jqtransformdone')) {return;}
 
selfForm.addClass('jqtransformdone');
 
 
$('input:submit, input:reset, input[type="button"]', this).jqTransInputButton();
 
$('input:text, input:password', this).jqTransInputText();
 
$('input:checkbox', this).jqTransCheckBox();
 
$('input:radio', this).jqTransRadio();
 
$('textarea', this).jqTransTextarea();
 
 
if( $('select', this).jqTransSelect().length > 0 ){jqTransformAddDocumentListener();}
 
selfForm.bind('reset',function(){var action = function(){jqTransformReset(this);}; window.setTimeout(action, 10);});
 
 
//preloading dont needed anymore since normal, focus and hover image are the same one
 
/*if(opt.preloadImg && !jqTransformImgPreloaded){
 
jqTransformImgPreloaded = true;
 
var oInputText = $('input:text:first', selfForm);
 
if(oInputText.length > 0){
 
//pour ie on eleve les ""
 
var strWrapperImgUrl = oInputText.get(0).wrapper.css('background-image');
 
jqTransformPreloadHoverFocusImg(strWrapperImgUrl);
 
var strInnerImgUrl = $('div.jqTransformInputInner',$(oInputText.get(0).wrapper)).css('background-image');
 
jqTransformPreloadHoverFocusImg(strInnerImgUrl);
 
}
 
 
var oTextarea = $('textarea',selfForm);
 
if(oTextarea.length > 0){
 
var oTable = oTextarea.get(0).oTable;
 
$('td',oTable).each(function(){
 
var strImgBack = $(this).css('background-image');
 
jqTransformPreloadHoverFocusImg(strImgBack);
 
});
 
}
 
}*/
 
 
 
}); /* End Form each */
 
 
};/* End the Plugin */
 
 
}
 
 
/***********************************
 
      jQuery
 
************************************/
 
  
 +
/****************************
 +
    custom js
 +
*****************************/
 
jQuery(function($) {
 
jQuery(function($) {
  
Line 553: Line 183:
 
});
 
});
  
 
+
/***********************************
 +
    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;
 +
}
  
</script>
+
})(window, document, 'Hammer');
</body>
+
</html>
+

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');