diff --git a/.babelrc b/.babelrc new file mode 100644 index 000000000..1d9cdd528 --- /dev/null +++ b/.babelrc @@ -0,0 +1,3 @@ +{ + "presets": ["es2015-rollup"] +} diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 000000000..eba5d9c20 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,20 @@ +# EditorConfig helps developers define and maintain consistent +# coding styles between different editors and IDEs +# editorconfig.org + +root = true + +[*] +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true +indent_style = space +indent_size = 2 + +[*.js] +indent_style = space +indent_size = 2 + +[*.{diff,md}] +trim_trailing_whitespace = false diff --git a/.jscsrc b/.jscsrc index 168677b82..bdfe07c49 100644 --- a/.jscsrc +++ b/.jscsrc @@ -1,95 +1,11 @@ { + "preset": "ember-suave", "excludeFiles": [ "*.js", "tests/**/assets", "node_modules/**" ], - "requireCurlyBraces": [ - "if", - "else", - "for", - "while", - "do", - "try", - "catch" - ], - "requireOperatorBeforeLineBreak": true, - "requireCamelCaseOrUpperCaseIdentifiers": true, - "maximumLineLength": { - "value": 120, - "allowComments": true, - "allowRegex": true - }, - "validateIndentation": 4, - "validateQuoteMarks": "'", - "disallowMultipleLineStrings": true, - "disallowMixedSpacesAndTabs": true, - "disallowTrailingWhitespace": true, - "disallowSpaceAfterPrefixUnaryOperators": true, - "requireSpaceAfterKeywords": [ - "if", - "else", - "for", - "while", - "do", - "switch", - "return", - "try", - "catch" - ], - "requireSpaceBeforeBinaryOperators": [ - "=", - "+=", - "-=", - "*=", - "/=", - "%=", - "<<=", - ">>=", - ">>>=", - "&=", - "|=", - "^=", - "+=", - "+", - "-", - "*", - "/", - "%", - "<<", - ">>", - ">>>", - "&", - "|", - "^", - "&&", - "||", - "===", - "==", - ">=", - "<=", - "<", - ">", - "!=", - "!==" - ], - "requireSpaceAfterBinaryOperators": true, - "requireSpacesInConditionalExpression": true, - "requireSpaceBeforeBlockStatements": true, - "requireLineFeedAtFileEnd": true, - "requireSpacesInFunctionExpression": { - "beforeOpeningCurlyBrace": true - }, - "disallowSpacesInAnonymousFunctionExpression": { - "beforeOpeningRoundBrace": true - }, - "disallowSpacesInsideObjectBrackets": "all", - "disallowSpacesInsideArrayBrackets": "all", - "disallowSpacesInsideParentheses": true, - "validateJSDoc": { - "checkParamNames": true, - "requireParamTypes": true - }, - "disallowMultipleLineBreaks": true, - "disallowNewlineBeforeBlockStatements": true -} \ No newline at end of file + "disallowConstOutsideModuleScope": false, + "requireArrowFunctions": true, + "disallowEmptyBlocks": false +} diff --git a/.jshintrc b/.jshintrc index 925e528b5..7a0378b38 100644 --- a/.jshintrc +++ b/.jshintrc @@ -1,22 +1,41 @@ { - "browser": true, - "curly": true, - "eqnull": true, - "expr": true, - "maxerr": 100, - "freeze": true, - "newcap": true, - "node": true, - "quotmark": "single", - "strict": true, - "sub": true, - "trailing": true, - "undef": true, - "unused": true, - "camelcase": true, - "indent": 4, - "validthis": true, - "globals": { - "define": false - } + "maxerr": 100, + "freeze": true, + "node": false, + "indent": 2, + "predef": [ + "document", + "window" + ], + "browser": true, + "boss": true, + "curly": true, + "debug": false, + "devel": true, + "eqeqeq": true, + "expr": true, + "validthis": true, + "evil": true, + "forin": false, + "immed": false, + "laxbreak": false, + "newcap": true, + "noarg": true, + "noempty": false, + "nonew": false, + "nomen": false, + "onevar": false, + "plusplus": false, + "regexp": false, + "undef": true, + "sub": true, + "strict": false, + "white": false, + "eqnull": true, + "esversion": 6, + "unused": true, + "-W116": true, + "-W080": true, + "-W038": true, + "proto": true } diff --git a/.travis.yml b/.travis.yml index 308d9a6e8..b686d4bf4 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,6 @@ language: node_js node_js: - - "0.10" + - "0.12" sudo: false @@ -8,4 +8,4 @@ before_script: - npm install -g grunt-cli script: - - grunt test-travis \ No newline at end of file + - grunt test-travis diff --git a/Gruntfile.coffee b/Gruntfile.coffee deleted file mode 100644 index 739af0c56..000000000 --- a/Gruntfile.coffee +++ /dev/null @@ -1,124 +0,0 @@ -module.exports = (grunt) -> - grunt.initConfig - pkg: grunt.file.readJSON 'package.json' - - usebanner: - taskName: - options: - position: 'top' - banner: ' -/*! <%= pkg.title || pkg.name %> - v<%= pkg.version %> - <%= grunt.template.today("yyyy-mm-dd") %>\n - * <%= pkg.homepage %>\n - *\n - * Copyright (c) <%= grunt.template.today("yyyy") %> <%= pkg.author.name %>;\n - * Licensed under the <%= pkg.license %> license */' - linebreak: true - files: - src: ['./hammer.js','./hammer.min.js'] - - concat: - build: - src: [ - 'src/hammer.prefix.js' - 'src/utils.js' - 'src/input.js' - 'src/input/*.js' - 'src/touchaction.js' - 'src/recognizer.js' - 'src/recognizers/*.js' - 'src/hammer.js' - 'src/manager.js' - 'src/expose.js' - 'src/hammer.suffix.js'] - dest: 'hammer.js' - - uglify: - min: - options: - report: 'gzip' - sourceMap: 'hammer.min.map' - files: - 'hammer.min.js': ['hammer.js'] - # special test build that exposes everything so it's testable - test: - options: - wrap: "$H" - comments: 'all' - exportAll: true - mangle: false - beautify: true - compress: - global_defs: - exportName: 'Hammer' - files: - 'tests/build.js': [ - 'src/utils.js' - 'src/input.js' - 'src/input/*.js' - 'src/touchaction.js' - 'src/recognizer.js' - 'src/recognizers/*.js' - 'src/hammer.js' - 'src/manager.js' - 'src/expose.js'] - - 'string-replace': - version: - files: - 'hammer.js': 'hammer.js' - options: - replacements: [ - pattern: '{{PKG_VERSION}}' - replacement: '<%= pkg.version %>' - ] - - jshint: - options: - jshintrc: true - build: - src: ['hammer.js'] - - jscs: - src: [ - 'src/**/*.js', - '!src/hammer.prefix.js', - '!src/hammer.suffix.js' - ] - options: - config: "./.jscsrc" - force: true - - watch: - scripts: - files: ['src/**/*.js'] - tasks: ['concat','string-replace','uglify','jshint','jscs'] - options: - interrupt: true - - connect: - server: - options: - hostname: "0.0.0.0" - port: 8000 - - qunit: - all: ['tests/unit/index.html'] - - - # Load tasks - grunt.loadNpmTasks 'grunt-contrib-concat' - grunt.loadNpmTasks 'grunt-contrib-uglify' - grunt.loadNpmTasks 'grunt-contrib-qunit' - grunt.loadNpmTasks 'grunt-contrib-watch' - grunt.loadNpmTasks 'grunt-contrib-jshint' - grunt.loadNpmTasks 'grunt-contrib-connect' - grunt.loadNpmTasks 'grunt-string-replace' - grunt.loadNpmTasks 'grunt-banner' - grunt.loadNpmTasks 'grunt-jscs' - - # Default task(s) - grunt.registerTask 'default', ['connect', 'watch'] - grunt.registerTask 'default-test', ['connect', 'uglify:test', 'watch'] - grunt.registerTask 'build', ['concat', 'string-replace', 'uglify:min', 'usebanner', 'test'] - grunt.registerTask 'test', ['jshint', 'jscs', 'uglify:test', 'qunit'] - grunt.registerTask 'test-travis', ['build'] diff --git a/Gruntfile.js b/Gruntfile.js new file mode 100644 index 000000000..f9a98433f --- /dev/null +++ b/Gruntfile.js @@ -0,0 +1,110 @@ +var babel = require('rollup-plugin-babel'); +var nodeResolve = require('rollup-plugin-node-resolve'); + +module.exports = function(grunt) { + grunt.initConfig({ + pkg: grunt.file.readJSON('package.json'), + usebanner: { + taskName: { + options: { + position: 'top', + banner: '/*! <%= pkg.title || pkg.name %> - v<%= pkg.version %> - <%= grunt.template.today("yyyy-mm-dd") %>\n * <%= pkg.homepage %>\n *\n * Copyright (c) <%= grunt.template.today("yyyy") %> <%= pkg.author.name %>;\n * Licensed under the <%= pkg.license %> license */', + linebreak: true + }, + files: { + src: ['./hammer.js', './hammer.min.js'] + } + } + }, + rollup: { + options: { + format: 'es6', + plugins: [ + nodeResolve(), + babel() + ], + intro: " (function(window, document, exportName, undefined) { \n'use strict';", + outro: "})(window, document, 'Hammer');" + }, + files: { + dest: 'hammer.js', + src: 'src/main.js' + } + }, + uglify: { + min: { + options: { + report: 'gzip', + sourceMap: 'hammer.min.map' + }, + files: { + 'hammer.min.js': ['hammer.js'] + } + } + }, + 'string-replace': { + version: { + files: { + 'hammer.js': 'hammer.js' + }, + options: { + replacements: [ + { + pattern: '{{PKG_VERSION}}', + replacement: '<%= pkg.version %>' + } + ] + } + } + }, + jshint: { + options: { + jshintrc: true + }, + build: { + src: ['hammer.js'] + } + }, + jscs: { + src: ['src/**/*.js'], + options: { + config: "./.jscsrc", + force: true + } + }, + watch: { + scripts: { + files: ['src/**/*.js'], + tasks: ['rollup', 'string-replace', 'uglify', 'jshint', 'jscs'], + options: { + interrupt: true + } + } + }, + connect: { + server: { + options: { + hostname: "0.0.0.0", + port: 8000 + } + } + }, + qunit: { + all: ['tests/unit/index.html'] + } + }); + grunt.loadNpmTasks('grunt-rollup'); + grunt.loadNpmTasks('grunt-contrib-uglify'); + grunt.loadNpmTasks('grunt-contrib-qunit'); + grunt.loadNpmTasks('grunt-contrib-watch'); + grunt.loadNpmTasks('grunt-contrib-jshint'); + grunt.loadNpmTasks('grunt-contrib-connect'); + grunt.loadNpmTasks('grunt-string-replace'); + grunt.loadNpmTasks('grunt-banner'); + grunt.loadNpmTasks('grunt-jscs'); + grunt.registerTask('default', ['connect', 'watch']); + grunt.registerTask('default-test', ['connect', 'uglify:test', 'watch']); + grunt.registerTask('build', ['rollup', 'string-replace', 'uglify:min', 'usebanner', 'test']); + grunt.registerTask('test', ['jshint', 'jscs', 'qunit']); + grunt.registerTask('test-travis', ['build']); +}; diff --git a/README.md b/README.md index e7bf8b8f0..9d857b572 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Hammer.js 2.0.6 +# Hammer.js 3.0.0 [![Build Status](https://travis-ci.org/hammerjs/hammer.js.svg)](https://travis-ci.org/hammerjs/hammer.js) @@ -47,5 +47,5 @@ For PRs. ## Building -You can get the pre-build versions from the Hammer.js website, or do this by yourself running +You can get the pre-build versions from the Hammer.js website, or do this by yourself running `npm install -g grunt-cli && npm install && grunt build` diff --git a/hammer.js b/hammer.js index 00d2965eb..53f5f9045 100644 --- a/hammer.js +++ b/hammer.js @@ -1,2643 +1,1413 @@ -/*! Hammer.JS - v2.0.7 - 2016-04-22 +/*! Hammer.JS - v2.0.8 - 2016-07-12 * http://hammerjs.github.io/ * * Copyright (c) 2016 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); -} +(function(window, document, exportName, undefined) { +'use strict'; +var SMALL_ARRAY_LENGTH = 200; +var UNDEFINED_KEY = Object.create(null); + +var classCallCheck = function (instance, Constructor) { + if (!(instance instanceof Constructor)) { + throw new TypeError("Cannot call a class as a function"); + } +}; -/** - * 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; +var createClass = function () { + function defineProperties(target, props) { + for (var i = 0; i < props.length; i++) { + var descriptor = props[i]; + descriptor.enumerable = descriptor.enumerable || false; + descriptor.configurable = true; + if ("value" in descriptor) descriptor.writable = true; + Object.defineProperty(target, descriptor.key, descriptor); } - return false; -} + } -/** - * walk objects and arrays - * @param {Object} obj - * @param {Function} iterator - * @param {Object} context - */ -function each(obj, iterator, context) { - var i; + return function (Constructor, protoProps, staticProps) { + if (protoProps) defineProperties(Constructor.prototype, protoProps); + if (staticProps) defineProperties(Constructor, staticProps); + return Constructor; + }; +}(); - if (!obj) { - return; - } +var get = function get(object, property, receiver) { + if (object === null) object = Function.prototype; + var desc = Object.getOwnPropertyDescriptor(object, property); - 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++; - } + if (desc === undefined) { + var parent = Object.getPrototypeOf(object); + + if (parent === null) { + return undefined; } else { - for (i in obj) { - obj.hasOwnProperty(i) && iterator.call(context, obj[i], i, obj); - } + return get(parent, property, receiver); } -} + } else if ("value" in desc) { + return desc.value; + } else { + var getter = desc.get; -/** - * wrap a method with a deprecation warning and stack trace - * @param {Function} method - * @param {String} name - * @param {String} message - * @returns {Function} A new function wrapping the supplied method. - */ -function deprecate(method, name, message) { - var deprecationMessage = 'DEPRECATED METHOD: ' + name + '\n' + message + ' AT \n'; - return function() { - var e = new Error('get-stack-trace'); - var stack = e && e.stack ? e.stack.replace(/^[^\(]+?[\n$]/gm, '') - .replace(/^\s+at\s+/gm, '') - .replace(/^Object.\s*\(/gm, '{anonymous}()@') : 'Unknown Stack Trace'; - - var log = window.console && (window.console.warn || window.console.log); - if (log) { - log.call(window.console, deprecationMessage, stack); - } - return method.apply(this, arguments); - }; -} - -/** - * extend object. - * means that properties in dest will be overwritten by the ones in src. - * @param {Object} target - * @param {...Object} objects_to_assign - * @returns {Object} target - */ -var assign; -if (typeof Object.assign !== 'function') { - assign = function assign(target) { - if (target === undefined || target === null) { - throw new TypeError('Cannot convert undefined or null to object'); - } + if (getter === undefined) { + return undefined; + } - var output = Object(target); - for (var index = 1; index < arguments.length; index++) { - var source = arguments[index]; - if (source !== undefined && source !== null) { - for (var nextKey in source) { - if (source.hasOwnProperty(nextKey)) { - output[nextKey] = source[nextKey]; - } - } - } - } - return output; - }; -} else { - assign = Object.assign; -} + return getter.call(receiver); + } +}; -/** - * extend object. - * means that properties in dest will be overwritten by the ones in src. - * @param {Object} dest - * @param {Object} src - * @param {Boolean} [merge=false] - * @returns {Object} dest - */ -var extend = deprecate(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++; +var inherits = function (subClass, superClass) { + if (typeof superClass !== "function" && superClass !== null) { + throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); + } + + subClass.prototype = Object.create(superClass && superClass.prototype, { + constructor: { + value: subClass, + enumerable: false, + writable: true, + configurable: true } - return dest; -}, 'extend', 'Use `assign`.'); - -/** - * 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 - */ -var merge = deprecate(function merge(dest, src) { - return extend(dest, src, true); -}, 'merge', 'Use `assign`.'); + }); + if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; +}; -/** - * simple class inheritance - * @param {Function} child - * @param {Function} base - * @param {Object} [properties] - */ -function inherit(child, base, properties) { - var baseP = base.prototype, - childP; +var possibleConstructorReturn = function (self, call) { + if (!self) { + throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); + } - childP = child.prototype = Object.create(baseP); - childP.constructor = child; - childP._super = baseP; + return call && (typeof call === "object" || typeof call === "function") ? call : self; +}; - if (properties) { - assign(childP, properties); +var FastArray = function () { + function FastArray() { + var length = arguments.length <= 0 || arguments[0] === undefined ? SMALL_ARRAY_LENGTH : arguments[0]; + var name = arguments.length <= 1 || arguments[1] === undefined ? 'Unknown Pool' : arguments[1]; + classCallCheck(this, FastArray); + + this.init(length, name); + } + + createClass(FastArray, [{ + key: 'init', + value: function init() { + var length = arguments.length <= 0 || arguments[0] === undefined ? SMALL_ARRAY_LENGTH : arguments[0]; + var name = arguments.length <= 1 || arguments[1] === undefined ? 'Unknown Pool' : arguments[1]; + + this.name = name; + this.length = 0; + this._length = length; + this._data = new Array(length); } -} - -/** - * 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); + }, { + key: 'get', + value: function get(index) { + if (index >= 0 && index < this.length) { + return this._data[index]; + } + + return undefined; } - 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); - }); -} + }, { + key: 'set', + value: function set(index, value) { + if (index > this.length) { + throw new Error("Index is out of array bounds."); + } + + if (index === this.length) { + this.length++; + } + + this._data[index] = value; + } + }, { + key: 'forEach', + value: function forEach(cb) { + for (var i = 0; i < this.length; i++) { + cb(this._data[i], i); + } + } + }, { + key: 'emptyEach', + value: function emptyEach(cb) { + for (var i = 0; i < this.length; i++) { + cb(this._data[i], i); + this._data[i] = undefined; + } + + this.length = 0; + } + }, { + key: 'mapInPlace', + value: function mapInPlace(cb) { + for (var i = 0; i < this.length; i++) { + this._data[i] = cb(this._data[i], i); + } + } + }, { + key: 'map', + value: function map(cb) { + var arr = new FastArray(this._length, this.name); -/** - * 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); - }); -} + for (var i = 0; i < this.length; i++) { + arr._data[i] = cb(this._data[i], i); + } -/** - * 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 arr; } - return false; -} + }, { + key: 'push', + value: function push(item) { + var index = this.length++; -/** - * small indexOf wrapper - * @param {String} str - * @param {String} find - * @returns {Boolean} found - */ -function inStr(str, find) { - return str.indexOf(find) > -1; -} + if (index === this._length) { + this._length *= 2; + this._data.length = this._length; + } -/** - * 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; + this._data[index] = item; } -} + }, { + key: 'pop', + value: function pop() { + var index = --this.length; -/** - * convert array-like objects to real arrays - * @param {Object} obj - * @returns {Array} - */ -function toArray(obj) { - return Array.prototype.slice.call(obj, 0); -} + if (index < 0) { + this.length = 0; + return undefined; + } -/** - * 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++; + return this._data[index]; } - - if (sort) { - if (!key) { - results = results.sort(); - } else { - results = results.sort(function sortUniqueArray(a, b) { - return a[key] > b[key]; - }); - } + }]); + return FastArray; +}(); + +var STREAM_EVENT_POOL = new FastArray(undefined, 'StreamEvent Pool'); + +var StreamEvent = function () { + function StreamEvent(name, info, prev) { + classCallCheck(this, StreamEvent); + + this.init(name, info, prev); + } + + createClass(StreamEvent, [{ + key: 'init', + value: function init(name, info, prev) { + this.name = name; + this.element = info.event.target; + this._isImportantEvent = name === 'end' || name === 'start' || prev && prev.name === 'start'; + this._source = this._isImportantEvent ? info.event : undefined; + this.silenced = false; + this.prev = prev; + this.pointerId = info.pointerId; + + // time + this.time = performance.now(); + this.dT = prev ? this.time - prev.time : 0; + + // current position (clientX/Y) + this.x = info.x; + this.y = info.y; + + // deltas off of origin event + this.originX = info.originX; + this.originY = info.originY; + this.totalX = info.x - this.originX; + this.totalY = info.y - this.originY; + + // deltas off the segment + this.segmentOriginX = info.segmentOriginX; + this.segmentOriginY = info.segmentOriginY; + this.segmentX = info.x - this.segmentOriginX; + this.segmentY = info.y - this.segmentOriginY; + + // deltas off of last event + this.dX = prev ? info.x - prev.x : 0; + this.dY = prev ? info.y - prev.y : 0; + + // prediction values + this.acceleration = 0; + this.aX = 0; + this.aY = 0; + + this.velocity = 0; + this.vX = 0; + this.vY = 0; + + this.nextX = 0; + this.nextY = 0; } + }, { + key: 'getAccelerationX', + value: function getAccelerationX() { + var dT = this.dT; + var prev = this.prev; - 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 vX = this.getVelocityX(); + var _vX = prev.vX; - 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 this.aX = (vX - _vX) / dT; } - 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 || element; - return (doc.defaultView || doc.parentWindow || window); -} - -var MOBILE_REGEX = /mobile|tablet|ip(ad|hone|od)|android/i; + }, { + key: 'getAccelerationY', + value: function getAccelerationY() { + var dT = this.dT; + var prev = this.prev; -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 vY = this.getVelocityY(); + var _vY = prev.vY; -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; + return this.aY = (vY - _vY) / dT; + } + }, { + key: 'getAcceleration', + value: function getAcceleration() { + var aX = this.getAccelerationX(); + var aY = this.getAccelerationY(); + var acceleration = this.acceleration = Math.sqrt(aX * aX + aY * aY); + + return { aX: aX, aY: aY, acceleration: acceleration }; + } + }, { + key: 'getVelocityX', + value: function getVelocityX() { + var dX = this.dX; + var dT = this.dT; -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; + return this.vX = dX / dT; + } + }, { + key: 'getVelocityY', + value: function getVelocityY() { + var dY = this.dY; + var dT = this.dT; -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); - } - }; + return this.vY = dY / dT; + } + }, { + key: 'getVelocity', + value: function getVelocity() { + var vX = this.getVelocityX(); + var vY = this.getVelocityY(); + var velocity = this.velocity = Math.sqrt(vX * vX + vY * vY); + + return { vX: vX, vY: vY, velocity: velocity }; + } + }, { + key: 'predictX', + value: function predictX() { + var aX = this.getAccelerationX(); + var x = this.x; + var dX = this.dX; + var vX = this.vX; + var dT = this.dT; + var totalX = this.totalX; + + // distance = initial distance + velocity * time + 1/2 acceleration * time^2 + + var nextDeltaX = Math.round(vX * dT + 0.5 * aX * dT * dT); + var nextdX = dX + nextDeltaX; + var nextX = x + nextDeltaX; + var nextTotalX = totalX + nextDeltaX; + + return this.nextX = { x: nextX, dX: nextdX, totalX: nextTotalX }; + } + }, { + key: 'predictY', + value: function predictY() { + var aY = this.getAccelerationY(); + var y = this.y; + var dY = this.dY; + var vY = this.vY; + var dT = this.dT; + var totalY = this.totalY; + + // distance = initial distance + velocity * time + 1/2 acceleration * time^2 + + var nextDeltaY = Math.round(vY * dT + 0.5 * aY * dT * dT); + var nextdY = dY + nextDeltaY; + var nextY = y + nextDeltaY; + var nextTotalY = totalY + nextDeltaY; + + return this.nextY = { y: nextY, dY: nextdY, totalY: nextTotalY }; + } + }, { + key: 'predict', + value: function predict() { + var nextX = this.predictX(); + var nextY = this.predictY(); - this.init(); + return { x: nextX, y: nextY }; + } -} + // cancel any default behaviors from this event -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); + }, { + key: 'silence', + value: function silence() { + if (this._source && this._source.cancelable) { + this._source.preventDefault(); + this._source.stopPropagation(); + this.silenced = true; + } } -}; - -/** - * 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; + }, { + key: 'destroy', + value: function destroy() { + this._source = undefined; + this.prev = undefined; + this.element = undefined; + + STREAM_EVENT_POOL.push(this); } - 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)); + }], [{ + key: 'create', + value: function create(name, info, prev) { + var event = STREAM_EVENT_POOL.pop(); - input.isFirst = !!isFirst; - input.isFinal = !!isFinal; + if (event) { + event.init(name, info, prev); + return event; + } - if (isFirst) { - manager.session = {}; + return new StreamEvent(name, info, prev); } + }]); + return StreamEvent; +}(); - // source event is the normalized value of the domEvents - // like 'touchstart, mouseup, pointerdown' - input.eventType = eventType; +var STREAM_SERIES_POOL = new FastArray(10, 'StreamSeries Pool'); - // compute scale, rotation etc - computeInputData(manager, input); +var StreamSeries = function (_FastArray) { + inherits(StreamSeries, _FastArray); - // emit secret event - manager.emit('hammer.input', input); + function StreamSeries(values) { + var number = arguments.length <= 1 || arguments[1] === undefined ? SMALL_ARRAY_LENGTH : arguments[1]; + var name = arguments.length <= 2 || arguments[2] === undefined ? 'StreamEvent to List' : arguments[2]; + classCallCheck(this, StreamSeries); - manager.recognize(input); - manager.session.prevInput = input; -} + var _this = possibleConstructorReturn(this, Object.getPrototypeOf(StreamSeries).call(this, number, name)); -/** - * 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; + _this.init(values, number, name); + _this._isDestroyed = false; + return _this; + } - // store the first input to calculate the distance and direction - if (!session.firstInput) { - session.firstInput = simpleCloneInputData(input); - } + createClass(StreamSeries, [{ + key: 'init', + value: function init(_ref, length, name) { + var originX = _ref.originX; + var originY = _ref.originY; - // 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; + get(Object.getPrototypeOf(StreamSeries.prototype), 'init', this).call(this, length, name); + this.originX = originX; + this.originY = originY; } + }, { + key: 'destroy', + value: function destroy() { + if (!this._isDestroyed) { + this._isDestroyed = true; - 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); + for (var j = 0; j < this.length; j++) { + this._data[j].destroy(); + this._data[j] = undefined; + } - var overallVelocity = getVelocity(input.deltaTime, input.deltaX, input.deltaY); - input.overallVelocityX = overallVelocity.x; - input.overallVelocityY = overallVelocity.y; - input.overallVelocity = (abs(overallVelocity.x) > abs(overallVelocity.y)) ? overallVelocity.x : overallVelocity.y; + STREAM_SERIES_POOL.push(this); + } + } + }], [{ + key: 'create', + value: function create(values) { + var number = arguments.length <= 1 || arguments[1] === undefined ? SMALL_ARRAY_LENGTH : arguments[1]; + var name = arguments.length <= 2 || arguments[2] === undefined ? 'StreamEvent to List' : arguments[2]; - input.scale = firstMultiple ? getScale(firstMultiple.pointers, pointers) : 1; - input.rotation = firstMultiple ? getRotation(firstMultiple.pointers, pointers) : 0; + var series = STREAM_SERIES_POOL.pop(); - input.maxPointers = !session.prevInput ? input.pointers.length : ((input.pointers.length > - session.prevInput.maxPointers) ? input.pointers.length : session.prevInput.maxPointers); + if (series) { + series.init(values, number, name); - computeIntervalInputData(session, input); + return series; + } - // find the correct target - var target = manager.element; - if (hasParent(input.srcEvent.target, target)) { - target = input.srcEvent.target; + return new StreamSeries(values, number, name); } - input.target = target; -} + }]); + return StreamSeries; +}(FastArray); -function computeDeltaXY(session, input) { - var center = input.center; - var offset = session.offsetDelta || {}; - var prevDelta = session.prevDelta || {}; - var prevInput = session.prevInput || {}; +var MacroTask = function MacroTask(job) { + classCallCheck(this, MacroTask); - if (input.eventType === INPUT_START || prevInput.eventType === INPUT_END) { - prevDelta = session.prevDelta = { - x: prevInput.deltaX || 0, - y: prevInput.deltaY || 0 - }; + setTimeout(job, 0); +}; - offset = session.offsetDelta = { - x: center.x, - y: center.y - }; +var STREAM_POOL = new FastArray(5, 'Stream Pool'); + +var Stream = function () { + function Stream(values) { + classCallCheck(this, Stream); + + this.init(values); + } + + createClass(Stream, [{ + key: 'init', + value: function init(_ref) { + var pointerId = _ref.pointerId; + var originX = _ref.originX; + var originY = _ref.originY; + + this.segments = new FastArray(5, 'Segments'); + this.series = undefined; + this._isDestroyed = false; + this._isDestroying = false; + this.active = false; + this.pointerId = pointerId; + this.originX = originX; + this.originY = originY; } + }, { + key: 'open', + value: function open(info) { + this.active = true; + this.series = StreamSeries.create({ originX: info.x, originY: info.y }); + this.segments.push(this.series); - input.deltaX = prevDelta.x + (center.x - offset.x); - input.deltaY = prevDelta.y + (center.y - offset.y); -} + var streamEvent = StreamEvent.create('start', this._addContextToInfo(info)); -/** - * 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 = input.deltaX - last.deltaX; - var deltaY = input.deltaY - last.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; + this.series.push(streamEvent); + return streamEvent; + } + }, { + key: 'push', + value: function push(info) { + var lastEvent = this.series.get(this.series.length - 1); + var streamEvent = StreamEvent.create('move', this._addContextToInfo(info), lastEvent); + + this.series.push(streamEvent); + return streamEvent; } + }, { + key: 'close', + value: function close(info) { + var _this = this; - input.velocity = velocity; - input.velocityX = velocityX; - input.velocityY = velocityY; - input.direction = direction; -} + this.active = false; + var lastEvent = this.series.get(this.series.length - 1); + var streamEvent = StreamEvent.create('end', this._addContextToInfo(info), lastEvent); -/** - * 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 - }; -} + this.series.push(streamEvent); -/** - * 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; + this._isDestroying = true; + new MacroTask(function () { + _this.destroy(); + }); - // no need to loop when only one touch - if (pointersLength === 1) { - return { - x: round(pointers[0].clientX), - y: round(pointers[0].clientY) - }; + return streamEvent; } - - var x = 0, y = 0, i = 0; - while (i < pointersLength) { - x += pointers[i].clientX; - y += pointers[i].clientY; - i++; + }, { + key: 'silence', + value: function silence() { + var series = this.segments.get(0); + var down = series.get(0); + var initial = series.get(1); + + down.silence(); + initial.silence(); } - - 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; + }, { + key: 'split', + value: function split() { + var lastEvent = this.series.get(this.series.length - 1); + this.series = StreamSeries.create({ originX: lastEvent.x, originY: lastEvent.y }); + this.segments.push(this.series); } + }, { + key: 'destroy', + value: function destroy() { + if (!this._isDestroyed) { + this._isDestroyed = true; + this.series = undefined; + + this.segments.forEach(function (series) { + series.destroy(); + }); + this.segments = undefined; - if (abs(x) >= abs(y)) { - return x < 0 ? DIRECTION_LEFT : DIRECTION_RIGHT; + STREAM_POOL.push(this); + } } - 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; + }, { + key: '_addContextToInfo', + value: function _addContextToInfo(info) { + info.originX = this.originX; + info.originY = this.originY; + info.segmentOriginX = this.series.originX; + info.segmentOriginY = this.series.originY; + + return info; } - var x = p2[props[0]] - p1[props[0]], - y = p2[props[1]] - p1[props[1]]; + }], [{ + key: 'create', + value: function create(values) { + var stream = STREAM_POOL.pop(); - return Math.sqrt((x * x) + (y * y)); -} + if (stream) { + stream.init(values); + return stream; + } -/** - * 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; + return new Stream(values); } - 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'; + }]); + return Stream; +}(); + +// All Credit for this goes to the Ember.js Core Team + +// This exists because `Object.create(null)` is absurdly slow compared +// to `new EmptyObject()`. In either case, you want a null prototype +// when you're treating the object instances as arbitrary dictionaries +// and don't want your keys colliding with build-in methods on the +// default object prototype. + +var proto = Object.create(null, { + // without this, we will always still end up with (new + // EmptyObject()).constructor === Object + constructor: { + value: undefined, + enumerable: false, + writable: true + } +}); -/** - * Mouse events input - * @constructor - * @extends Input - */ -function MouseInput() { - this.evEl = MOUSE_ELEMENT_EVENTS; - this.evWin = MOUSE_WINDOW_EVENTS; +function EmptyObject() {} +EmptyObject.prototype = proto; - this.pressed = false; // mousedown state +var HashMap = function () { + function HashMap(entries) { + classCallCheck(this, HashMap); - Input.apply(this, arguments); -} + this._data = new EmptyObject(); -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; - } + if (entries) { + for (var i = 0; i < entries.length; i++) { + this.data[entries[i][0]] = entries[i][1]; + } + } + } - // mouse must be down - if (!this.pressed) { - return; + createClass(HashMap, [{ + key: 'forEach', + value: function forEach(cb) { + for (var key in this._data) { + // skip undefined + if (this._data[key] !== UNDEFINED_KEY) { + cb(this._data[key], key); } + } - if (eventType & INPUT_END) { - this.pressed = false; - } + return this; + } + }, { + key: 'get', + value: function get(key) { + var val = this._data[key]; - this.callback(this.manager, eventType, { - pointers: [ev], - changedPointers: [ev], - pointerType: INPUT_TYPE_MOUSE, - srcEvent: ev - }); + return val === UNDEFINED_KEY ? undefined : val; } -}); + }, { + key: 'set', + value: function set(key, value) { + this._data[key] = value; -var POINTER_INPUT_MAP = { - pointerdown: INPUT_START, - pointermove: INPUT_MOVE, - pointerup: INPUT_END, - pointercancel: INPUT_CANCEL, - pointerout: INPUT_CANCEL -}; + return this; + } + }, { + key: 'delete', + value: function _delete(key) { + this._data[key] = UNDEFINED_KEY; -// 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 -}; + return true; + } + }]); + return HashMap; +}(); -var POINTER_ELEMENT_EVENTS = 'pointerdown'; -var POINTER_WINDOW_EVENTS = 'pointermove pointerup pointercancel'; +var Input = function () { + function Input(element, manager) { + classCallCheck(this, Input); -// IE10 has prefixed support, and case-sensitive -if (window.MSPointerEvent && !window.PointerEvent) { - POINTER_ELEMENT_EVENTS = 'MSPointerDown'; - POINTER_WINDOW_EVENTS = 'MSPointerMove MSPointerUp MSPointerCancel'; -} + this.element = element; + this.handler = null; + this.handlerStack = []; -/** - * Pointer events input - * @constructor - * @extends Input - */ -function PointerEventInput() { - this.evEl = POINTER_ELEMENT_EVENTS; - this.evWin = POINTER_WINDOW_EVENTS; + this.attached = false; + this.streaming = false; + this.hasMoved = false; - Input.apply(this, arguments); + this.openStreams = 0; + this.streams = new HashMap(); + this._nextEvents = new HashMap(); - this.store = (this.manager.session.pointerEvents = []); -} + this._handlers = { start: null, update: null, end: null, interrupt: null }; + this.manager = manager; -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; - } + this.attach(); + } - // it not found, so the pointer hasn't been down (so it's probably a hover) - if (storeIndex < 0) { - return; - } + createClass(Input, [{ + key: '_bind', + value: function _bind(name) { + var _name; - // update the event in the store - store[storeIndex] = ev; + var _handlers = this._handlers; - this.callback(this.manager, eventType, { - pointers: store, - changedPointers: [ev], - pointerType: pointerType, - srcEvent: ev - }); + for (var _len = arguments.length, args = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) { + args[_key - 1] = arguments[_key]; + } - if (removePointer) { - // remove from the store - store.splice(storeIndex, 1); - } + return _handlers[name] = (_name = this[name]).bind.apply(_name, [this].concat(args)); } -}); - -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'; + }, { + key: 'extractThen', + value: function extractThen(name, event) { + this[name](this.extract(event)); + } + }, { + key: 'extractManyThen', + value: function extractManyThen(name, event) { + this.extractMany(event).forEach(this[name].bind(this)); + } + }, { + key: 'start', + value: function start(eventInfo) { + var stream = Stream.create({ + pointerId: eventInfo.pointerId, + originX: eventInfo.x, + originY: eventInfo.y + }); -/** - * Touch events input - * @constructor - * @extends Input - */ -function SingleTouchInput() { - this.evTarget = SINGLE_TOUCH_TARGET_EVENTS; - this.evWin = SINGLE_TOUCH_WINDOW_EVENTS; - this.started = false; + var streams = this.streams; - Input.apply(this, arguments); -} -inherit(SingleTouchInput, Input, { - handler: function TEhandler(ev) { - var type = SINGLE_TOUCH_INPUT_MAP[ev.type]; + streams.forEach(function (stream) { + return stream.split(); + }); - // should we handle the touch events? - if (type === INPUT_START) { - this.started = true; - } + this.streaming = true; - if (!this.started) { - return; - } + this.openStreams++; + streams.set(stream.pointerId, stream); + // console.log('opening new stream'); + var streamEvent = stream.open(eventInfo); - var touches = normalizeSingleTouches.call(this, ev, type); + if (this.handler) { + this.handlerStack.push(this.handler); + this.handler = null; + } - // when done, reset the started state - if (type & (INPUT_END | INPUT_CANCEL) && touches[0].length - touches[1].length === 0) { - this.started = false; - } + this.manager.recognize(this, streams, stream, streamEvent); - this.callback(this.manager, type, { - pointers: touches[0], - changedPointers: touches[1], - pointerType: INPUT_TYPE_TOUCH, - srcEvent: ev - }); + this._poll(); } -}); - -/** - * @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); + }, { + key: 'trigger', + value: function trigger(stream, streamEvent) { + if (this.handler) { + this.handler.recognize(this, this.streams, stream, streamEvent); + } else { + this.manager.recognize(this, this.streams, stream, streamEvent); + } } + }, { + key: '_update', + value: function _update(eventInfo) { + // console.log('updating'); + var streams = this.streams; - return [all, changed]; -} + var stream = streams.get(eventInfo.pointerId); + var streamEvent = void 0; -var TOUCH_INPUT_MAP = { - touchstart: INPUT_START, - touchmove: INPUT_MOVE, - touchend: INPUT_END, - touchcancel: INPUT_CANCEL -}; + if (!this.streaming) { + if (!this.handler) {} + // console.log('closing stream'); + streamEvent = stream.close(eventInfo); -var TOUCH_TARGET_EVENTS = 'touchstart touchmove touchend touchcancel'; + this.hasMoved = false; + this.trigger(stream, streamEvent); -/** - * Multi-user touch events input - * @constructor - * @extends Input - */ -function TouchInput() { - this.evTarget = TOUCH_TARGET_EVENTS; - this.targetIds = {}; + var wasRecognizing = this.handler; - Input.apply(this, arguments); -} + this.handler = null; -inherit(TouchInput, Input, { - handler: function MTEhandler(ev) { - var type = TOUCH_INPUT_MAP[ev.type]; - var touches = getTouches.call(this, ev, type); - if (!touches) { - return; + // vacate this stream + // console.log('removing stream'); + streams.delete(stream.pointerId); + this.openStreams--; + + if (wasRecognizing && this.openStreams === 0) { + this.manager.endInputRecognition(); } + } else { + streamEvent = stream.push(eventInfo); - this.callback(this.manager, type, { - pointers: touches[0], - changedPointers: touches[1], - pointerType: INPUT_TYPE_TOUCH, - srcEvent: ev - }); + this.trigger(stream, streamEvent); + } } -}); + }, { + key: '_poll', + value: function _poll() { + var _this = this; + + return void requestAnimationFrame(function () { + _this._nextEvents.forEach(function (event, key) { + _this._update(event); + _this._nextEvents.delete(key); + }); -/** - * @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++; + if (_this.streaming) { + _this._poll(); } + }); } + }, { + key: 'update', + value: function update(eventInfo) { + if (!this.streaming) { + return; + } - // 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]); - } + this._nextEvents.set(eventInfo.pointerId, eventInfo); - // cleanup removed touches - if (type & (INPUT_END | INPUT_CANCEL)) { - delete targetIds[changedTouches[i].identifier]; - } - i++; + if (!this.hasMoved) { + this.hasMoved = true; + this._update(eventInfo); + } } - - if (!changedTargetTouches.length) { - return; + }, { + key: '_close', + value: function _close(event) { + if (this.streaming) { + // console.log('received close event'); + this.streaming = false; + this._nextEvents.set(event.pointerId, event); + } } - - 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 - */ - -var DEDUP_TIMEOUT = 2500; -var DEDUP_DISTANCE = 25; - -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); - - this.primaryTouch = null; - this.lastTouches = []; -} - -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); - - if (isMouse && inputData.sourceCapabilities && inputData.sourceCapabilities.firesTouchEvents) { - return; - } - - // when we're in a touch event, record touches to de-dupe synthetic mouse event - if (isTouch) { - recordTouches.call(this, inputEvent, inputData); - } else if (isMouse && isSyntheticEvent.call(this, inputData)) { - return; - } - - this.callback(manager, inputEvent, inputData); - }, - - /** - * remove the event listeners - */ - destroy: function destroy() { - this.touch.destroy(); - this.mouse.destroy(); + }, { + key: 'end', + value: function end(event) { + if (this.streaming) { + this._close(event); + } } -}); - -function recordTouches(eventType, eventData) { - if (eventType & INPUT_START) { - this.primaryTouch = eventData.changedPointers[0].identifier; - setLastTouch.call(this, eventData); - } else if (eventType & (INPUT_END | INPUT_CANCEL)) { - setLastTouch.call(this, eventData); + }, { + key: 'interrupt', + value: function interrupt(event) { + if (this.streaming) { + this._close(event); + } } -} - -function setLastTouch(eventData) { - var touch = eventData.changedPointers[0]; - - if (touch.identifier === this.primaryTouch) { - var lastTouch = {x: touch.clientX, y: touch.clientY}; - this.lastTouches.push(lastTouch); - var lts = this.lastTouches; - var removeLastTouch = function() { - var i = lts.indexOf(lastTouch); - if (i > -1) { - lts.splice(i, 1); - } - }; - setTimeout(removeLastTouch, DEDUP_TIMEOUT); + }, { + key: 'extract', + value: function extract() { + throw new Error('Interface Method Not Implemented'); } -} - -function isSyntheticEvent(eventData) { - var x = eventData.srcEvent.clientX, y = eventData.srcEvent.clientY; - for (var i = 0; i < this.lastTouches.length; i++) { - var t = this.lastTouches[i]; - var dx = Math.abs(x - t.x), dy = Math.abs(y - t.y); - if (dx <= DEDUP_DISTANCE && dy <= DEDUP_DISTANCE) { - return true; - } + }, { + key: 'extractMany', + value: function extractMany() { + throw new Error('Interface Method Not Implemented'); } - return false; -} - -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'; -var TOUCH_ACTION_MAP = getTouchActionProps(); - -/** - * 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 && TOUCH_ACTION_MAP[value]) { - 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) { - 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; - } + }, { + key: 'attach', + value: function attach() { + throw new Error('Interface Method Not Implemented'); + } + }, { + key: 'deattach', + value: function deattach() { + throw new Error('Interface Method Not Implemented'); + } + }, { + key: 'destroy', + value: function destroy() { + this.deattach(); + this.manager = null; + this.element = null; + this.streams = null; + this.handler = null; + } + }]); + return Input; +}(); - var actions = this.actions; - var hasNone = inStr(actions, TOUCH_ACTION_NONE) && !TOUCH_ACTION_MAP[TOUCH_ACTION_NONE]; - var hasPanY = inStr(actions, TOUCH_ACTION_PAN_Y) && !TOUCH_ACTION_MAP[TOUCH_ACTION_PAN_Y]; - var hasPanX = inStr(actions, TOUCH_ACTION_PAN_X) && !TOUCH_ACTION_MAP[TOUCH_ACTION_PAN_X]; +var Layer = function () { + function Layer(element) { + classCallCheck(this, Layer); - if (hasNone) { - //do not prevent defaults if this is a tap gesture + this.element = element; + this.recognizers = []; + this._handlers = {}; + } - var isTapPointer = input.pointers.length === 1; - var isTapMovement = input.distance < 2; - var isTapTouchTime = input.deltaTime < 250; + createClass(Layer, [{ + key: 'recognize', + value: function recognize(input, streams, stream, streamEvent) { + var recognizers = this.recognizers; - if (isTapPointer && isTapMovement && isTapTouchTime) { - return; - } - } - if (hasPanX && hasPanY) { - // `pan-x pan-y` means browser handles all scrolling/panning, do not prevent - return; - } + for (var i = 0; i < recognizers.length; i++) { + var recognizer = recognizers[i]; - if (hasNone || - (hasPanY && direction & DIRECTION_HORIZONTAL) || - (hasPanX && direction & DIRECTION_VERTICAL)) { - return this.preventSrc(srcEvent); + if (recognizer.recognize(input, streams, stream, streamEvent)) { + input.handler = recognizer; + return true; } - }, + } - /** - * 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(); + return false; } -}; - -/** - * 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; + }, { + key: 'addRecognizer', + value: function addRecognizer(recognizerInstance) { + recognizerInstance.layer = this; + this.recognizers.push(recognizerInstance); } + }, { + key: 'emit', + value: function emit(e) { + var name = e.name; - var hasPanX = inStr(actions, TOUCH_ACTION_PAN_X); - var hasPanY = inStr(actions, TOUCH_ACTION_PAN_Y); + var handlers = (this._handlers['*'] || []).concat(this._handlers[name] || []); - // if both pan-x and pan-y are set (different recognizers - // for different directions, e.g. horizontal pan but vertical swipe?) - // we need none (as otherwise with pan-x pan-y combined none of these - // recognizers will work, since the browser would handle all panning - if (hasPanX && hasPanY) { - return TOUCH_ACTION_NONE; + for (var i = 0; i < handlers.length; i++) { + handlers[i].call(null, e); + } } - - // pan-x OR pan-y - if (hasPanX || hasPanY) { - return hasPanX ? TOUCH_ACTION_PAN_X : TOUCH_ACTION_PAN_Y; + }, { + key: 'on', + value: function on(event, handler) { + this._handlers[event] = this._handlers[event] || []; + this._handlers[event].push(handler); } - - // manipulation - if (inStr(actions, TOUCH_ACTION_MANIPULATION)) { - return TOUCH_ACTION_MANIPULATION; + }, { + key: 'off', + value: function off() {} + }]); + return Layer; +}(); + +var TouchInput = function (_Input) { + inherits(TouchInput, _Input); + + function TouchInput() { + classCallCheck(this, TouchInput); + return possibleConstructorReturn(this, Object.getPrototypeOf(TouchInput).apply(this, arguments)); + } + + createClass(TouchInput, [{ + key: 'extract', + value: function extract(event) { + return extractTouch(event.changedTouches[0], event); } - - return TOUCH_ACTION_AUTO; -} - -function getTouchActionProps() { - if (!NATIVE_TOUCH_ACTION) { - return false; + }, { + key: 'extractMany', + value: function extractMany(event) { + return Array.prototype.slice.call(event.changedTouches).map(function (touch) { + return extractTouch(touch, event); + }); } - var touchMap = {}; - var cssSupports = window.CSS && window.CSS.supports; - ['auto', 'manipulation', 'pan-y', 'pan-x', 'pan-x pan-y', 'none'].forEach(function(val) { + }, { + key: 'attach', + value: function attach() { + if (this.attached) { + return; + } + var element = this.element; - // If css.supports is not supported but there is native touch-action assume it supports - // all values. This is the case for IE 10 and 11. - touchMap[val] = cssSupports ? window.CSS.supports('touch-action', val) : true; - }); - return touchMap; -} -/** - * 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; + element.addEventListener('touchstart', this._bind('extractThen', 'start'), true); + element.addEventListener('touchend', this._bind('extractThen', 'end'), true); + element.addEventListener('touchcancel', this._bind('extractThen', 'interrupt'), true); + element.addEventListener('touchmove', this._bind('extractManyThen', 'update'), true); -/** - * Recognizer - * Every recognizer needs to extend from this class. - * @constructor - * @param {Object} options - */ -function Recognizer(options) { - this.options = assign({}, this.defaults, options || {}); + this.attached = true; + } + }, { + key: 'deattach', + value: function deattach() { + if (!this.attached) { + return; + } + var element = this.element; + var _handlers = this._handlers; - this.id = uniqueId(); - this.manager = null; + element.removeEventListener('touchstart', _handlers.start, true); + element.removeEventListener('touchend', _handlers.end, true); + element.removeEventListener('touchcancel', _handlers.interrupt, true); + element.removeEventListener('touchmove', _handlers.update, true); + } + }]); + return TouchInput; +}(Input); - // default is enable true - this.options.enable = ifUndefined(this.options.enable, true); +function extractTouch(touch, event) { + return { + pointerId: touch.identifier, + x: touch.clientX, + y: touch.clientY, + event: event + }; +} - this.state = STATE_POSSIBLE; +var supportsPassive = false; - this.simultaneous = {}; - this.requireFail = []; -} +try { + var opts = Object.defineProperty({}, 'passive', { + get: function get() { + supportsPassive = true; + } + }); + + window.addEventListener('test', null, opts); +} catch (e) {} + +var SUPPORTS_PASSIVE = supportsPassive; + +var MouseInput = function (_Input) { + inherits(MouseInput, _Input); + + function MouseInput() { + classCallCheck(this, MouseInput); + return possibleConstructorReturn(this, Object.getPrototypeOf(MouseInput).apply(this, arguments)); + } + + createClass(MouseInput, [{ + key: 'extract', + value: function extract(event) { + return { + pointerId: 'MOUSE_POINTER', + x: event.clientX, + y: event.clientY, + event: event + }; + } + }, { + key: 'attach', + value: function attach() { + if (this.attached) { + return; + } + var element = this.element; -Recognizer.prototype = { - /** - * @virtual - * @type {Object} - */ - defaults: {}, - - /** - * set options - * @param {Object} options - * @return {Recognizer} - */ - set: function(options) { - assign(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; - } + var opts = SUPPORTS_PASSIVE ? { capture: true, passive: true } : true; - 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; - } + element.addEventListener('mousedown', this._bind('extractThen', 'start'), opts); + element.addEventListener('mouseup', this._bind('extractThen', 'end'), opts); + element.addEventListener('mouseexit', this._bind('extractThen', 'interrupt'), opts); + element.addEventListener('mousemove', this._bind('extractThen', 'update'), opts); - 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; - } + this.attached = true; + } + }, { + key: 'deattach', + value: function deattach() { + if (this.attached) { + return; + } + var element = this.element; + var _handlers = this._handlers; - 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(event) { - self.manager.emit(event, input); - } - // 'panstart' and 'panmove' - if (state < STATE_ENDED) { - emit(self.options.event + stateStr(state)); - } + var opts = SUPPORTS_PASSIVE ? { capture: true, passive: true } : true; - emit(self.options.event); // simple 'eventName' events + element.removeEventListener('mousedown', _handlers.start, opts); + element.removeEventListener('mouseup', _handlers.end, opts); + element.removeEventListener('mouseexit', _handlers.interrupt, opts); + element.removeEventListener('mousemove', _handlers.update, opts); + } + }]); + return MouseInput; +}(Input); - if (input.additionalEvent) { // additional event(panleft, panright, pinchin, pinchout...) - emit(input.additionalEvent); - } +// import PointerInput from './inputs/pointer'; - // panend and pancancel - if (state >= STATE_ENDED) { - emit(self.options.event + stateStr(state)); - } - }, - - /** - * 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 = assign({}, inputData); - - // is is enabled and allow recognizing? - if (!boolOrFn(this.options.enable, [this, inputDataClone])) { - this.reset(); - this.state = STATE_FAILED; - return; - } +var MAY_SUPPORT_TOUCH = 'ontouchstart' in window || // html5 browsers +navigator.maxTouchPoints > 0 || // future IE +navigator.msMaxTouchPoints > 0; // current IE10 - // reset when we've reached the end - if (this.state & (STATE_RECOGNIZED | STATE_CANCELLED | STATE_FAILED)) { - this.state = STATE_POSSIBLE; - } +var MAY_SUPPORT_MOUSE = true; - this.state = this.process(inputDataClone); +// const SUPPORT_POINTER_EVENTS = prefixed(window, 'PointerEvent') !== undefined; - // 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() { } -}; +function availableInputs() { + var inputs = {}; -/** - * 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 ''; -} + if (MAY_SUPPORT_MOUSE) { + inputs.mouse = MouseInput; + } -/** - * 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 ''; -} + if (MAY_SUPPORT_TOUCH) { + inputs.touch = TouchInput; + } -/** - * 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; + return inputs; } /** - * This recognizer is just used as a base for the simple attribute recognizers. - * @constructor - * @extends Recognizer + * @private + * extend object. + * means that properties in dest will be overwritten by the ones in src. + * @param {Object} target + * @param {...Object} objects_to_assign + * @returns {Object} target */ -function AttrRecognizer() { - Recognizer.apply(this, arguments); -} +var assign = void 0; +if (typeof Object.assign !== 'function') { + assign = function assign(target) { + if (target === undefined || target === null) { + throw new TypeError('Cannot convert undefined or null to object'); + } -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; + var output = Object(target); + for (var index = 1; index < arguments.length; index++) { + var source = arguments[index]; + if (source !== undefined && source !== null) { + for (var nextKey in source) { + if (source.hasOwnProperty(nextKey)) { + output[nextKey] = source[nextKey]; + } } - return 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; + return output; + }; +} else { + assign = Object.assign; } -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; - }, +var assign$1 = assign; - attrTest: function(input) { - return AttrRecognizer.prototype.attrTest.call(this, input) && - (this.state & STATE_BEGAN || (!(this.state & STATE_BEGAN) && this.directionTest(input))); - }, +var DEFAULT_OPTIONS = { + inputs: availableInputs() +}; - emit: function(input) { +var Manager = function () { + function Manager(rootElement, options) { + classCallCheck(this, Manager); - this.pX = input.deltaX; - this.pY = input.deltaY; + this.rootElement = rootElement || window; + this.layers = new WeakMap(); + this._recognizedInputs = 0; + this.isRecognizing = false; - var direction = directionStr(input.direction); + this.inputs = {}; + this.options = assign$1({}, DEFAULT_OPTIONS, options || {}); - if (direction) { - input.additionalEvent = this.options.event + direction; - } - this._super.emit.call(this, input); - } -}); + if (this.options.inputs) { + var inputs = Object.keys(this.options.inputs); -/** - * 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); -} + for (var i = 0; i < inputs.length; i++) { + var name = inputs[i]; + var InputClass = this.options.inputs[name]; -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) { - if (input.scale !== 1) { - var inOut = input.scale < 1 ? 'in' : 'out'; - input.additionalEvent = this.options.event + inOut; - } - this._super.emit.call(this, input); + this.registerInput(name, InputClass); + } } -}); + } -/** - * 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: 251, // minimal time of the pointer to be pressed - threshold: 9 // 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); - } + createClass(Manager, [{ + key: 'registerInput', + value: function registerInput(name, InputClass) { + this.inputs[name] = new InputClass(this.rootElement, this); } -}); - -/** - * 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); + }, { + key: 'recognize', + value: function recognize(input, streams, stream, streamEvent) { + var layer = this._findParentLayer(streamEvent.element); + + while (layer) { + if (layer.recognize(input, streams, stream, streamEvent)) { + this.startInputRecognition(); + break; + } + layer = layer.parent; + } + + if (this.isRecognizing && streamEvent.name === 'end') { + this.endInputRecognition(); + } } -}); + }, { + key: 'startInputRecognition', + value: function startInputRecognition() { + this._recognizedInputs++; + if (this._recognizedInputs === 1) { + this.isRecognizing = true; + document.body.setAttribute('gesture-no-touch', 'true'); + } + } + }, { + key: 'endInputRecognition', + value: function endInputRecognition() { + this._recognizedInputs--; + if (this._recognizedInputs === 0) { + this.isRecognizing = false; + document.body.removeAttribute('gesture-no-touch'); + } + } + }, { + key: 'unregisterInput', + value: function unregisterInput(name) { + var input = this.inputs[name]; + + if (input) { + this.inputs[name] = null; + input.destroy(); + } + } + }, { + key: 'registerLayer', + value: function registerLayer(layer) { + layer.element.setAttribute('gesture-layer', true); + this.layers.set(layer.element, layer); + + layer.parent = this._findParentLayer(layer.element.parentNode); + + // insert into linked layer list + if (layer.parent) { + layer.child = layer.parent.child; + layer.parent.child = layer; + } + } + }, { + key: 'forgetLayer', + value: function forgetLayer(layer) { + this.layers.delete(layer.element); -/** - * 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); -} + // join parent/child + if (layer.parent && layer.child) { + layer.parent.child = layer.child; -inherit(SwipeRecognizer, AttrRecognizer, { - /** - * @namespace - * @memberof SwipeRecognizer - */ - defaults: { - event: 'swipe', - threshold: 10, - velocity: 0.3, - 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.overallVelocity; - } else if (direction & DIRECTION_HORIZONTAL) { - velocity = input.overallVelocityX; - } else if (direction & DIRECTION_VERTICAL) { - velocity = input.overallVelocityY; + // unlink parent/child + } else { + if (layer.parent) { + layer.parent.child = null; } - - return this._super.attrTest.call(this, input) && - direction & input.offsetDirection && - input.distance > this.options.threshold && - input.maxPointers == this.options.pointers && - abs(velocity) > this.options.velocity && input.eventType & INPUT_END; - }, - - emit: function(input) { - var direction = directionStr(input.offsetDirection); - if (direction) { - this.manager.emit(this.options.event + direction, input); + if (layer.child) { + layer.child.parent = null; } - - 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; -} + }, { + key: '_findParentLayer', + value: function _findParentLayer(element) { + do { + if (element && element.hasAttribute('gesture-layer')) { + var layer = this.layers.get(element); -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: 9, // 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(); + if (layer) { + return layer; + } } + } while (element && element !== document.body && (element = element.parentNode)); - // 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); - } + return null; } -}); + }, { + key: '_teardown', + value: function _teardown() { + this.streams.touch.destroy(); + this.streams.mouse.destroy(); -/** - * Simple way to create a 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); -} + this.layers.forEach(function (layer) { + layer.destroy(); + }); -/** - * @const {string} - */ -Hammer.VERSION = '2.0.7'; - -/** - * 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)' + this.layers = null; } -}; - -var STOP = 1; -var FORCED_STOP = 2; - -/** - * Manager - * @param {HTMLElement} element - * @param {Object} [options] - * @constructor - */ -function Manager(element, options) { - this.options = assign({}, Hammer.defaults, options || {}); - - this.options.inputTarget = this.options.inputTarget || element; - - this.handlers = {}; - this.session = {}; - this.recognizers = []; - this.oldCssProps = {}; - - this.element = element; - this.input = createInputInstance(this); - this.touchAction = new TouchAction(this, this.options.touchAction); - - toggleCssProps(this, true); - - each(this.options.recognizers, function(item) { - var recognizer = this.add(new (item[0])(item[1])); - item[2] && recognizer.recognizeWith(item[2]); - item[3] && recognizer.requireFailure(item[3]); - }, this); -} - -Manager.prototype = { - /** - * set options - * @param {Object} options - * @returns {Manager} - */ - set: function(options) { - assign(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; - } + }, { + key: 'destroy', + value: function destroy() { + this._teardown(); + } + }], [{ + key: 'create', + value: function create() { + return new Manager(); + } + }]); + return Manager; +}(); - // run the touch-action polyfill - this.touchAction.preventDefaults(inputData); +var Recognizer = function Recognizer(name) { + classCallCheck(this, Recognizer); - var recognizer; - var recognizers = this.recognizers; + this.name = name; +}; - // 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; +var HorizontalPan = function () { + function HorizontalPan(options) { + classCallCheck(this, HorizontalPan); - // 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; - } + this.name = 'horizontal-pan'; + this.options = options; + this.layer = undefined; + this.stream = undefined; - 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; - } + this.isRecognizing = false; + } - 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; - } + createClass(HorizontalPan, [{ + key: 'beginRecognizing', + value: function beginRecognizing(input, stream) { + var _this = this; - // remove existing - var existing = this.get(recognizer.options.event); - if (existing) { - this.remove(existing); - } + this.isRecognizing = true; - 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; - } - - recognizer = this.get(recognizer); + this.stream = stream; + var series = this.stream.series; - // let's make sure this recognizer exists - if (recognizer) { - var recognizers = this.recognizers; - var index = inArray(recognizers, recognizer); - if (index !== -1) { - recognizers.splice(index, 1); - this.touchAction.update(); - } - } + series.forEach(function (event) { + _this.relay(event); + }); + } + }, { + key: 'relay', + value: function relay(event) { + if (event.name === 'start') { + this.layer.emit({ name: 'panStart', event: event }); + } else if (event.name === 'end') { + this.isRecognizing = false; + this.layer.emit({ name: 'panEnd', event: event }); + this.stream = undefined; + } else if (event.totalX < 0 || event.prev.totalX < 0) { + this.layer.emit({ name: 'panLeft', event: event }); + } else { + this.layer.emit({ name: 'panRight', event: event }); + } + } + }, { + key: 'emit', + value: function emit(name, event) { + this.layer.emit({ name: name, event: event }); + } + }, { + key: 'recognize', + value: function recognize(input, streams, stream, streamEvent) { + if (this.isRecognizing) { + this.relay(streamEvent); + } else if (input.openStreams === 1 && streamEvent.totalY === 0 && streamEvent.totalX !== 0) { + this.beginRecognizing(input, stream); + } + + return this.isRecognizing; + } + }]); + return HorizontalPan; +}(); - return this; - }, - - /** - * bind event - * @param {String} events - * @param {Function} handler - * @returns {EventEmitter} this - */ - on: function(events, handler) { - if (events === undefined) { - return; - } - if (handler === undefined) { - return; - } +var VerticalPan = function () { + function VerticalPan(options) { + classCallCheck(this, VerticalPan); - 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) { - if (events === undefined) { - return; - } + this.name = 'vertical-pan'; + this.options = options; + this.layer = undefined; + this.stream = undefined; - var handlers = this.handlers; - each(splitStr(events), function(event) { - if (!handler) { - delete handlers[event]; - } else { - handlers[event] && 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); - } + this.isRecognizing = false; + } - // no handlers, so skip it all - var handlers = this.handlers[event] && this.handlers[event].slice(); - if (!handlers || !handlers.length) { - return; - } + createClass(VerticalPan, [{ + key: 'beginRecognizing', + value: function beginRecognizing(input, streams) { + var _this = this; - data.type = event; - data.preventDefault = function() { - data.srcEvent.preventDefault(); - }; + this.isRecognizing = true; - var i = 0; - while (i < handlers.length) { - handlers[i](data); - i++; - } - }, + this.stream = streams[streams.length - 1]; + var series = this.stream.series; - /** - * 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; + series.forEach(function (event) { + _this.relay(event); + }); } -}; - -/** - * 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; - if (!element.style) { - return; + }, { + key: 'relay', + value: function relay(event) { + if (event.name === 'start') { + this.layer.emit({ name: 'panStart', event: event }); + } else if (event.name === 'end') { + this.isRecognizing = false; + this.layer.emit({ name: 'panEnd', event: event }); + this.stream = undefined; + } else if (event.totalY < 0 || event.prev.totalY < 0) { + this.layer.emit({ name: 'panUp', event: event }); + } else { + this.layer.emit({ name: 'panDown', event: event }); + } } - var prop; - each(manager.options.cssProps, function(value, name) { - prop = prefixed(element.style, name); - if (add) { - manager.oldCssProps[prop] = element.style[prop]; - element.style[prop] = value; - } else { - element.style[prop] = manager.oldCssProps[prop] || ''; - } - }); - if (!add) { - manager.oldCssProps = {}; + }, { + key: 'emit', + value: function emit(name, event) { + this.layer.emit({ name: name, event: event }); } -} + }, { + key: 'recognize', + value: function recognize(input, streams, streamEvent) { + if (this.isRecognizing) { + this.relay(streamEvent); + } else if (streamEvent.totalX === 0 && streamEvent.totalY !== 0) { + this.beginRecognizing(input, streams); + } + + return this.isRecognizing; + } + }]); + return VerticalPan; +}(); -/** - * 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); -} +// this prevents errors when Hammer is loaded in the presence of an AMD +// style loader but by script tag, not by the loader. -assign(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, - assign: assign, - inherit: inherit, - bindFn: bindFn, - prefixed: prefixed -}); +var Hammer = { // jshint ignore:line + Input: Input, + Layer: Layer, + Manager: Manager, + Recognizer: Recognizer, -// this prevents errors when Hammer is loaded in the presence of an AMD -// style loader but by script tag, not by the loader. -var freeGlobal = (typeof window !== 'undefined' ? window : (typeof self !== 'undefined' ? self : {})); // jshint ignore:line -freeGlobal.Hammer = Hammer; + MouseInput: MouseInput, + TouchInput: TouchInput, + HorizontalPanRecognizer: HorizontalPan, + VerticalPanRecognizer: VerticalPan +}; + +/* jshint ignore:start */ if (typeof define === 'function' && define.amd) { - define(function() { - return Hammer; - }); -} else if (typeof module != 'undefined' && module.exports) { - module.exports = Hammer; + define(function () { + return Hammer; + }); +} else if (typeof module !== 'undefined' && module.exports) { + module.exports = Hammer; } else { - window[exportName] = Hammer; + window[exportName] = Hammer; } - -})(window, document, 'Hammer'); +/* jshint ignore:end */ +})(window, document, 'Hammer'); \ No newline at end of file diff --git a/hammer.min.js b/hammer.min.js index 34a8c86fb..8a3643ae9 100644 --- a/hammer.min.js +++ b/hammer.min.js @@ -1,7 +1,7 @@ -/*! Hammer.JS - v2.0.7 - 2016-04-22 +/*! Hammer.JS - v2.0.8 - 2016-07-12 * http://hammerjs.github.io/ * * Copyright (c) 2016 Jorik Tangelder; * Licensed under the MIT license */ -!function(a,b,c,d){"use strict";function e(a,b,c){return setTimeout(j(a,c),b)}function f(a,b,c){return Array.isArray(a)?(g(a,c[b],c),!0):!1}function g(a,b,c){var e;if(a)if(a.forEach)a.forEach(b,c);else if(a.length!==d)for(e=0;e\s*\(/gm,"{anonymous}()@"):"Unknown Stack Trace",f=a.console&&(a.console.warn||a.console.log);return f&&f.call(a.console,e,d),b.apply(this,arguments)}}function i(a,b,c){var d,e=b.prototype;d=a.prototype=Object.create(e),d.constructor=a,d._super=e,c&&la(d,c)}function j(a,b){return function(){return a.apply(b,arguments)}}function k(a,b){return typeof a==oa?a.apply(b?b[0]||d:d,b):a}function l(a,b){return a===d?b:a}function m(a,b,c){g(q(b),function(b){a.addEventListener(b,c,!1)})}function n(a,b,c){g(q(b),function(b){a.removeEventListener(b,c,!1)})}function o(a,b){for(;a;){if(a==b)return!0;a=a.parentNode}return!1}function p(a,b){return a.indexOf(b)>-1}function q(a){return a.trim().split(/\s+/g)}function r(a,b,c){if(a.indexOf&&!c)return a.indexOf(b);for(var d=0;dc[b]}):d.sort()),d}function u(a,b){for(var c,e,f=b[0].toUpperCase()+b.slice(1),g=0;g1&&!c.firstMultiple?c.firstMultiple=D(b):1===e&&(c.firstMultiple=!1);var f=c.firstInput,g=c.firstMultiple,h=g?g.center:f.center,i=b.center=E(d);b.timeStamp=ra(),b.deltaTime=b.timeStamp-f.timeStamp,b.angle=I(h,i),b.distance=H(h,i),B(c,b),b.offsetDirection=G(b.deltaX,b.deltaY);var j=F(b.deltaTime,b.deltaX,b.deltaY);b.overallVelocityX=j.x,b.overallVelocityY=j.y,b.overallVelocity=qa(j.x)>qa(j.y)?j.x:j.y,b.scale=g?K(g.pointers,d):1,b.rotation=g?J(g.pointers,d):0,b.maxPointers=c.prevInput?b.pointers.length>c.prevInput.maxPointers?b.pointers.length:c.prevInput.maxPointers:b.pointers.length,C(c,b);var k=a.element;o(b.srcEvent.target,k)&&(k=b.srcEvent.target),b.target=k}function B(a,b){var c=b.center,d=a.offsetDelta||{},e=a.prevDelta||{},f=a.prevInput||{};b.eventType!==Ea&&f.eventType!==Ga||(e=a.prevDelta={x:f.deltaX||0,y:f.deltaY||0},d=a.offsetDelta={x:c.x,y:c.y}),b.deltaX=e.x+(c.x-d.x),b.deltaY=e.y+(c.y-d.y)}function C(a,b){var c,e,f,g,h=a.lastInterval||b,i=b.timeStamp-h.timeStamp;if(b.eventType!=Ha&&(i>Da||h.velocity===d)){var j=b.deltaX-h.deltaX,k=b.deltaY-h.deltaY,l=F(i,j,k);e=l.x,f=l.y,c=qa(l.x)>qa(l.y)?l.x:l.y,g=G(j,k),a.lastInterval=b}else c=h.velocity,e=h.velocityX,f=h.velocityY,g=h.direction;b.velocity=c,b.velocityX=e,b.velocityY=f,b.direction=g}function D(a){for(var b=[],c=0;ce;)c+=a[e].clientX,d+=a[e].clientY,e++;return{x:pa(c/b),y:pa(d/b)}}function F(a,b,c){return{x:b/a||0,y:c/a||0}}function G(a,b){return a===b?Ia:qa(a)>=qa(b)?0>a?Ja:Ka:0>b?La:Ma}function H(a,b,c){c||(c=Qa);var d=b[c[0]]-a[c[0]],e=b[c[1]]-a[c[1]];return Math.sqrt(d*d+e*e)}function I(a,b,c){c||(c=Qa);var d=b[c[0]]-a[c[0]],e=b[c[1]]-a[c[1]];return 180*Math.atan2(e,d)/Math.PI}function J(a,b){return I(b[1],b[0],Ra)+I(a[1],a[0],Ra)}function K(a,b){return H(b[0],b[1],Ra)/H(a[0],a[1],Ra)}function L(){this.evEl=Ta,this.evWin=Ua,this.pressed=!1,x.apply(this,arguments)}function M(){this.evEl=Xa,this.evWin=Ya,x.apply(this,arguments),this.store=this.manager.session.pointerEvents=[]}function N(){this.evTarget=$a,this.evWin=_a,this.started=!1,x.apply(this,arguments)}function O(a,b){var c=s(a.touches),d=s(a.changedTouches);return b&(Ga|Ha)&&(c=t(c.concat(d),"identifier",!0)),[c,d]}function P(){this.evTarget=bb,this.targetIds={},x.apply(this,arguments)}function Q(a,b){var c=s(a.touches),d=this.targetIds;if(b&(Ea|Fa)&&1===c.length)return d[c[0].identifier]=!0,[c,c];var e,f,g=s(a.changedTouches),h=[],i=this.target;if(f=c.filter(function(a){return o(a.target,i)}),b===Ea)for(e=0;e-1&&d.splice(a,1)};setTimeout(e,cb)}}function U(a){for(var b=a.srcEvent.clientX,c=a.srcEvent.clientY,d=0;d=f&&db>=g)return!0}return!1}function V(a,b){this.manager=a,this.set(b)}function W(a){if(p(a,jb))return jb;var b=p(a,kb),c=p(a,lb);return b&&c?jb:b||c?b?kb:lb:p(a,ib)?ib:hb}function X(){if(!fb)return!1;var b={},c=a.CSS&&a.CSS.supports;return["auto","manipulation","pan-y","pan-x","pan-x pan-y","none"].forEach(function(d){b[d]=c?a.CSS.supports("touch-action",d):!0}),b}function Y(a){this.options=la({},this.defaults,a||{}),this.id=v(),this.manager=null,this.options.enable=l(this.options.enable,!0),this.state=nb,this.simultaneous={},this.requireFail=[]}function Z(a){return a&sb?"cancel":a&qb?"end":a&pb?"move":a&ob?"start":""}function $(a){return a==Ma?"down":a==La?"up":a==Ja?"left":a==Ka?"right":""}function _(a,b){var c=b.manager;return c?c.get(a):a}function aa(){Y.apply(this,arguments)}function ba(){aa.apply(this,arguments),this.pX=null,this.pY=null}function ca(){aa.apply(this,arguments)}function da(){Y.apply(this,arguments),this._timer=null,this._input=null}function ea(){aa.apply(this,arguments)}function fa(){aa.apply(this,arguments)}function ga(){Y.apply(this,arguments),this.pTime=!1,this.pCenter=!1,this._timer=null,this._input=null,this.count=0}function ha(a,b){return b=b||{},b.recognizers=l(b.recognizers,ha.defaults.preset),new ia(a,b)}function ia(a,b){this.options=la({},ha.defaults,b||{}),this.options.inputTarget=this.options.inputTarget||a,this.handlers={},this.session={},this.recognizers=[],this.oldCssProps={},this.element=a,this.input=y(this),this.touchAction=new V(this,this.options.touchAction),ja(this,!0),g(this.options.recognizers,function(a){var b=this.add(new a[0](a[1]));a[2]&&b.recognizeWith(a[2]),a[3]&&b.requireFailure(a[3])},this)}function ja(a,b){var c=a.element;if(c.style){var d;g(a.options.cssProps,function(e,f){d=u(c.style,f),b?(a.oldCssProps[d]=c.style[d],c.style[d]=e):c.style[d]=a.oldCssProps[d]||""}),b||(a.oldCssProps={})}}function ka(a,c){var d=b.createEvent("Event");d.initEvent(a,!0,!0),d.gesture=c,c.target.dispatchEvent(d)}var la,ma=["","webkit","Moz","MS","ms","o"],na=b.createElement("div"),oa="function",pa=Math.round,qa=Math.abs,ra=Date.now;la="function"!=typeof Object.assign?function(a){if(a===d||null===a)throw new TypeError("Cannot convert undefined or null to object");for(var b=Object(a),c=1;ch&&(b.push(a),h=b.length-1):e&(Ga|Ha)&&(c=!0),0>h||(b[h]=a,this.callback(this.manager,e,{pointers:b,changedPointers:[a],pointerType:f,srcEvent:a}),c&&b.splice(h,1))}});var Za={touchstart:Ea,touchmove:Fa,touchend:Ga,touchcancel:Ha},$a="touchstart",_a="touchstart touchmove touchend touchcancel";i(N,x,{handler:function(a){var b=Za[a.type];if(b===Ea&&(this.started=!0),this.started){var c=O.call(this,a,b);b&(Ga|Ha)&&c[0].length-c[1].length===0&&(this.started=!1),this.callback(this.manager,b,{pointers:c[0],changedPointers:c[1],pointerType:za,srcEvent:a})}}});var ab={touchstart:Ea,touchmove:Fa,touchend:Ga,touchcancel:Ha},bb="touchstart touchmove touchend touchcancel";i(P,x,{handler:function(a){var b=ab[a.type],c=Q.call(this,a,b);c&&this.callback(this.manager,b,{pointers:c[0],changedPointers:c[1],pointerType:za,srcEvent:a})}});var cb=2500,db=25;i(R,x,{handler:function(a,b,c){var d=c.pointerType==za,e=c.pointerType==Ba;if(!(e&&c.sourceCapabilities&&c.sourceCapabilities.firesTouchEvents)){if(d)S.call(this,b,c);else if(e&&U.call(this,c))return;this.callback(a,b,c)}},destroy:function(){this.touch.destroy(),this.mouse.destroy()}});var eb=u(na.style,"touchAction"),fb=eb!==d,gb="compute",hb="auto",ib="manipulation",jb="none",kb="pan-x",lb="pan-y",mb=X();V.prototype={set:function(a){a==gb&&(a=this.compute()),fb&&this.manager.element.style&&mb[a]&&(this.manager.element.style[eb]=a),this.actions=a.toLowerCase().trim()},update:function(){this.set(this.manager.options.touchAction)},compute:function(){var a=[];return g(this.manager.recognizers,function(b){k(b.options.enable,[b])&&(a=a.concat(b.getTouchAction()))}),W(a.join(" "))},preventDefaults:function(a){var b=a.srcEvent,c=a.offsetDirection;if(this.manager.session.prevented)return void b.preventDefault();var d=this.actions,e=p(d,jb)&&!mb[jb],f=p(d,lb)&&!mb[lb],g=p(d,kb)&&!mb[kb];if(e){var h=1===a.pointers.length,i=a.distance<2,j=a.deltaTime<250;if(h&&i&&j)return}return g&&f?void 0:e||f&&c&Na||g&&c&Oa?this.preventSrc(b):void 0},preventSrc:function(a){this.manager.session.prevented=!0,a.preventDefault()}};var nb=1,ob=2,pb=4,qb=8,rb=qb,sb=16,tb=32;Y.prototype={defaults:{},set:function(a){return la(this.options,a),this.manager&&this.manager.touchAction.update(),this},recognizeWith:function(a){if(f(a,"recognizeWith",this))return this;var b=this.simultaneous;return a=_(a,this),b[a.id]||(b[a.id]=a,a.recognizeWith(this)),this},dropRecognizeWith:function(a){return f(a,"dropRecognizeWith",this)?this:(a=_(a,this),delete this.simultaneous[a.id],this)},requireFailure:function(a){if(f(a,"requireFailure",this))return this;var b=this.requireFail;return a=_(a,this),-1===r(b,a)&&(b.push(a),a.requireFailure(this)),this},dropRequireFailure:function(a){if(f(a,"dropRequireFailure",this))return this;a=_(a,this);var b=r(this.requireFail,a);return b>-1&&this.requireFail.splice(b,1),this},hasRequireFailures:function(){return this.requireFail.length>0},canRecognizeWith:function(a){return!!this.simultaneous[a.id]},emit:function(a){function b(b){c.manager.emit(b,a)}var c=this,d=this.state;qb>d&&b(c.options.event+Z(d)),b(c.options.event),a.additionalEvent&&b(a.additionalEvent),d>=qb&&b(c.options.event+Z(d))},tryEmit:function(a){return this.canEmit()?this.emit(a):void(this.state=tb)},canEmit:function(){for(var a=0;af?Ja:Ka,c=f!=this.pX,d=Math.abs(a.deltaX)):(e=0===g?Ia:0>g?La:Ma,c=g!=this.pY,d=Math.abs(a.deltaY))),a.direction=e,c&&d>b.threshold&&e&b.direction},attrTest:function(a){return aa.prototype.attrTest.call(this,a)&&(this.state&ob||!(this.state&ob)&&this.directionTest(a))},emit:function(a){this.pX=a.deltaX,this.pY=a.deltaY;var b=$(a.direction);b&&(a.additionalEvent=this.options.event+b),this._super.emit.call(this,a)}}),i(ca,aa,{defaults:{event:"pinch",threshold:0,pointers:2},getTouchAction:function(){return[jb]},attrTest:function(a){return this._super.attrTest.call(this,a)&&(Math.abs(a.scale-1)>this.options.threshold||this.state&ob)},emit:function(a){if(1!==a.scale){var b=a.scale<1?"in":"out";a.additionalEvent=this.options.event+b}this._super.emit.call(this,a)}}),i(da,Y,{defaults:{event:"press",pointers:1,time:251,threshold:9},getTouchAction:function(){return[hb]},process:function(a){var b=this.options,c=a.pointers.length===b.pointers,d=a.distanceb.time;if(this._input=a,!d||!c||a.eventType&(Ga|Ha)&&!f)this.reset();else if(a.eventType&Ea)this.reset(),this._timer=e(function(){this.state=rb,this.tryEmit()},b.time,this);else if(a.eventType&Ga)return rb;return tb},reset:function(){clearTimeout(this._timer)},emit:function(a){this.state===rb&&(a&&a.eventType&Ga?this.manager.emit(this.options.event+"up",a):(this._input.timeStamp=ra(),this.manager.emit(this.options.event,this._input)))}}),i(ea,aa,{defaults:{event:"rotate",threshold:0,pointers:2},getTouchAction:function(){return[jb]},attrTest:function(a){return this._super.attrTest.call(this,a)&&(Math.abs(a.rotation)>this.options.threshold||this.state&ob)}}),i(fa,aa,{defaults:{event:"swipe",threshold:10,velocity:.3,direction:Na|Oa,pointers:1},getTouchAction:function(){return ba.prototype.getTouchAction.call(this)},attrTest:function(a){var b,c=this.options.direction;return c&(Na|Oa)?b=a.overallVelocity:c&Na?b=a.overallVelocityX:c&Oa&&(b=a.overallVelocityY),this._super.attrTest.call(this,a)&&c&a.offsetDirection&&a.distance>this.options.threshold&&a.maxPointers==this.options.pointers&&qa(b)>this.options.velocity&&a.eventType&Ga},emit:function(a){var b=$(a.offsetDirection);b&&this.manager.emit(this.options.event+b,a),this.manager.emit(this.options.event,a)}}),i(ga,Y,{defaults:{event:"tap",pointers:1,taps:1,interval:300,time:250,threshold:9,posThreshold:10},getTouchAction:function(){return[ib]},process:function(a){var b=this.options,c=a.pointers.length===b.pointers,d=a.distance=0&&athis.length)throw new Error("Index is out of array bounds.");a===this.length&&this.length++,this._data[a]=b}},{key:"forEach",value:function(a){for(var b=0;b1?d-1:0),f=1;f0||navigator.msMaxTouchPoints>0,H=!0,I=void 0;I="function"!=typeof Object.assign?function(a){if(a===d||null===a)throw new TypeError("Cannot convert undefined or null to object");for(var b=Object(a),c=1;c - * @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)' - } -}; diff --git a/src/hammer.prefix.js b/src/hammer.prefix.js deleted file mode 100644 index debc7fff2..000000000 --- a/src/hammer.prefix.js +++ /dev/null @@ -1,2 +0,0 @@ -(function(window, document, exportName, undefined) { - 'use strict'; diff --git a/src/hammer.suffix.js b/src/hammer.suffix.js deleted file mode 100644 index 41bfecfc2..000000000 --- a/src/hammer.suffix.js +++ /dev/null @@ -1 +0,0 @@ -})(window, document, 'Hammer'); diff --git a/src/input.js b/src/input.js deleted file mode 100644 index e9601ca68..000000000 --- a/src/input.js +++ /dev/null @@ -1,394 +0,0 @@ -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); - - var overallVelocity = getVelocity(input.deltaTime, input.deltaX, input.deltaY); - input.overallVelocityX = overallVelocity.x; - input.overallVelocityY = overallVelocity.y; - input.overallVelocity = (abs(overallVelocity.x) > abs(overallVelocity.y)) ? overallVelocity.x : overallVelocity.y; - - input.scale = firstMultiple ? getScale(firstMultiple.pointers, pointers) : 1; - input.rotation = firstMultiple ? getRotation(firstMultiple.pointers, pointers) : 0; - - input.maxPointers = !session.prevInput ? input.pointers.length : ((input.pointers.length > - session.prevInput.maxPointers) ? input.pointers.length : session.prevInput.maxPointers); - - computeIntervalInputData(session, input); - - // find the correct target - 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 = input.deltaX - last.deltaX; - var deltaY = input.deltaY - last.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); -} diff --git a/src/input/mouse.js b/src/input/mouse.js deleted file mode 100644 index 6e27ef5ff..000000000 --- a/src/input/mouse.js +++ /dev/null @@ -1,57 +0,0 @@ -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.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 - if (!this.pressed) { - return; - } - - if (eventType & INPUT_END) { - this.pressed = false; - } - - this.callback(this.manager, eventType, { - pointers: [ev], - changedPointers: [ev], - pointerType: INPUT_TYPE_MOUSE, - srcEvent: ev - }); - } -}); diff --git a/src/input/pointerevent.js b/src/input/pointerevent.js deleted file mode 100644 index ec810b5fb..000000000 --- a/src/input/pointerevent.js +++ /dev/null @@ -1,88 +0,0 @@ -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 && !window.PointerEvent) { - 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); - } - } -}); diff --git a/src/input/singletouch.js b/src/input/singletouch.js deleted file mode 100644 index cbe9ec086..000000000 --- a/src/input/singletouch.js +++ /dev/null @@ -1,68 +0,0 @@ -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]; -} diff --git a/src/input/touch.js b/src/input/touch.js deleted file mode 100644 index 4ac2dd598..000000000 --- a/src/input/touch.js +++ /dev/null @@ -1,98 +0,0 @@ -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 - ]; -} diff --git a/src/input/touchmouse.js b/src/input/touchmouse.js deleted file mode 100644 index b110d2397..000000000 --- a/src/input/touchmouse.js +++ /dev/null @@ -1,95 +0,0 @@ -/** - * 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 - */ - -var DEDUP_TIMEOUT = 2500; -var DEDUP_DISTANCE = 25; - -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); - - this.primaryTouch = null; - this.lastTouches = []; -} - -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); - - if (isMouse && inputData.sourceCapabilities && inputData.sourceCapabilities.firesTouchEvents) { - return; - } - - // when we're in a touch event, record touches to de-dupe synthetic mouse event - if (isTouch) { - recordTouches.call(this, inputEvent, inputData); - } else if (isMouse && isSyntheticEvent.call(this, inputData)) { - return; - } - - this.callback(manager, inputEvent, inputData); - }, - - /** - * remove the event listeners - */ - destroy: function destroy() { - this.touch.destroy(); - this.mouse.destroy(); - } -}); - -function recordTouches(eventType, eventData) { - if (eventType & INPUT_START) { - this.primaryTouch = eventData.changedPointers[0].identifier; - setLastTouch.call(this, eventData); - } else if (eventType & (INPUT_END | INPUT_CANCEL)) { - setLastTouch.call(this, eventData); - } -} - -function setLastTouch(eventData) { - var touch = eventData.changedPointers[0]; - - if (touch.identifier === this.primaryTouch) { - var lastTouch = {x: touch.clientX, y: touch.clientY}; - this.lastTouches.push(lastTouch); - var lts = this.lastTouches; - var removeLastTouch = function() { - var i = lts.indexOf(lastTouch); - if (i > -1) { - lts.splice(i, 1); - } - }; - setTimeout(removeLastTouch, DEDUP_TIMEOUT); - } -} - -function isSyntheticEvent(eventData) { - var x = eventData.srcEvent.clientX, y = eventData.srcEvent.clientY; - for (var i = 0; i < this.lastTouches.length; i++) { - var t = this.lastTouches[i]; - var dx = Math.abs(x - t.x), dy = Math.abs(y - t.y); - if (dx <= DEDUP_DISTANCE && dy <= DEDUP_DISTANCE) { - return true; - } - } - return false; -} diff --git a/src/inputs/input.js b/src/inputs/input.js new file mode 100644 index 000000000..332bd1aaa --- /dev/null +++ b/src/inputs/input.js @@ -0,0 +1,183 @@ +import Stream from '../streams/stream'; +import StreamEvent from '../streams/stream-event'; +import HashMap from 'perf-primitives/addon/hash-map'; + +export default class Input { + + constructor(element, manager) { + this.element = element; + this.handler = null; + this.handlerStack = []; + + this.attached = false; + this.streaming = false; + this.hasMoved = false; + + this.openStreams = 0; + this.streams = new HashMap(); + this._nextEvents = new HashMap(); + + this._handlers = { start: null, update: null, end: null, interrupt: null }; + this.manager = manager; + + this.attach(); + } + + _bind(name, ...args) { + const { _handlers } = this; + + return _handlers[name] = this[name].bind(this, ...args); + } + + extractThen(name, event) { + this[name](this.extract(event)); + } + + extractManyThen(name, event) { + this.extractMany(event).forEach(this[name].bind(this)); + } + + start(eventInfo) { + let stream = Stream.create({ + pointerId: eventInfo.pointerId, + originX: eventInfo.x, + originY: eventInfo.y + }); + + const { streams } = this; + + streams.forEach((stream) => stream.split()); + + this.streaming = true; + + this.openStreams++; + streams.set(stream.pointerId, stream); + // console.log('opening new stream'); + let streamEvent = stream.open(eventInfo); + + if (this.handler) { + this.handlerStack.push(this.handler); + this.handler = null; + } + + this.manager.recognize(this, streams, stream, streamEvent); + + this._poll(); + } + + trigger(stream, streamEvent) { + if (this.handler) { + this.handler.recognize(this, this.streams, stream, streamEvent); + } else { + this.manager.recognize(this, this.streams, stream, streamEvent); + } + } + + _update(eventInfo) { + // console.log('updating'); + let { streams } = this; + let stream = streams.get(eventInfo.pointerId); + let streamEvent; + + if (!this.streaming) { + if (!this.handler) { + + } + // console.log('closing stream'); + streamEvent = stream.close(eventInfo); + + this.hasMoved = false; + this.trigger(stream, streamEvent); + + let wasRecognizing = this.handler; + + this.handler = null; + + // vacate this stream + // console.log('removing stream'); + streams.delete(stream.pointerId); + this.openStreams--; + + if (wasRecognizing && this.openStreams === 0) { + this.manager.endInputRecognition(); + } + + } else { + streamEvent = stream.push(eventInfo); + + this.trigger(stream, streamEvent); + } + + } + + _poll() { + return void requestAnimationFrame(() => { + this._nextEvents.forEach((event, key) => { + this._update(event); + this._nextEvents.delete(key); + }); + + if (this.streaming) { + this._poll(); + } + }); + } + + update(eventInfo) { + if (!this.streaming) { + return; + } + + this._nextEvents.set(eventInfo.pointerId, eventInfo); + + if (!this.hasMoved) { + this.hasMoved = true; + this._update(eventInfo); + } + } + + _close(event) { + if (this.streaming) { + // console.log('received close event'); + this.streaming = false; + this._nextEvents.set(event.pointerId, event); + } + } + + end(event) { + if (this.streaming) { + this._close(event); + } + } + + interrupt(event) { + if (this.streaming) { + this._close(event); + } + } + + extract() { + throw new Error('Interface Method Not Implemented'); + } + + extractMany() { + throw new Error('Interface Method Not Implemented'); + } + + attach() { + throw new Error('Interface Method Not Implemented'); + } + + deattach() { + throw new Error('Interface Method Not Implemented'); + } + + destroy() { + this.deattach(); + this.manager = null; + this.element = null; + this.streams = null; + this.handler = null; + } + +} diff --git a/src/inputs/mouse.js b/src/inputs/mouse.js new file mode 100644 index 000000000..53b820595 --- /dev/null +++ b/src/inputs/mouse.js @@ -0,0 +1,45 @@ +import Input from './input'; +import SUPPORTS_PASSIVE from '../utils/supports-passive'; + +export default class MouseInput extends Input { + + extract(event) { + return { + pointerId: 'MOUSE_POINTER', + x: event.clientX, + y: event.clientY, + event + }; + } + + attach() { + if (this.attached) { + return; + } + const { element } = this; + + let opts = SUPPORTS_PASSIVE ? { capture: true, passive: true } : true; + + element.addEventListener('mousedown', this._bind('extractThen', 'start'), opts); + element.addEventListener('mouseup', this._bind('extractThen', 'end'), opts); + element.addEventListener('mouseexit', this._bind('extractThen', 'interrupt'), opts); + element.addEventListener('mousemove', this._bind('extractThen', 'update'), opts); + + this.attached = true; + } + + deattach() { + if (this.attached) { + return; + } + const { element, _handlers } = this; + + let opts = SUPPORTS_PASSIVE ? { capture: true, passive: true } : true; + + element.removeEventListener('mousedown', _handlers.start, opts); + element.removeEventListener('mouseup', _handlers.end, opts); + element.removeEventListener('mouseexit', _handlers.interrupt, opts); + element.removeEventListener('mousemove', _handlers.update, opts); + } + +} diff --git a/src/inputs/touch.js b/src/inputs/touch.js new file mode 100644 index 000000000..c47b9fc59 --- /dev/null +++ b/src/inputs/touch.js @@ -0,0 +1,48 @@ +import Input from './input'; + +export default class TouchInput extends Input { + + extract(event) { + return extractTouch(event.changedTouches[0], event); + } + + extractMany(event) { + return Array.prototype.slice.call(event.changedTouches).map((touch) => extractTouch(touch, event)); + } + + attach() { + if (this.attached) { + return; + } + const { element } = this; + + element.addEventListener('touchstart', this._bind('extractThen', 'start') , true); + element.addEventListener('touchend', this._bind('extractThen', 'end') , true); + element.addEventListener('touchcancel', this._bind('extractThen', 'interrupt') , true); + element.addEventListener('touchmove', this._bind('extractManyThen', 'update') , true); + + this.attached = true; + } + + deattach() { + if (!this.attached) { + return; + } + const { element, _handlers } = this; + + element.removeEventListener('touchstart', _handlers.start , true); + element.removeEventListener('touchend', _handlers.end , true); + element.removeEventListener('touchcancel', _handlers.interrupt , true); + element.removeEventListener('touchmove', _handlers.update , true); + } + +} + +function extractTouch(touch, event) { + return { + pointerId: touch.identifier, + x: touch.clientX, + y: touch.clientY, + event + }; +} diff --git a/src/layer.js b/src/layer.js new file mode 100644 index 000000000..c0067558c --- /dev/null +++ b/src/layer.js @@ -0,0 +1,46 @@ + +export default class Layer { + + constructor(element) { + this.element = element; + this.recognizers = []; + this._handlers = {}; + } + + recognize(input, streams, stream, streamEvent) { + let { recognizers } = this; + + for (let i = 0; i < recognizers.length; i++) { + let recognizer = recognizers[i]; + + if (recognizer.recognize(input, streams, stream, streamEvent)) { + input.handler = recognizer; + return true; + } + } + + return false; + } + + addRecognizer(recognizerInstance) { + recognizerInstance.layer = this; + this.recognizers.push(recognizerInstance); + } + + emit(e) { + let { name } = e; + let handlers = (this._handlers['*'] || []).concat(this._handlers[name] || []); + + for (let i = 0; i < handlers.length; i++) { + handlers[i].call(null, e); + } + } + + on(event, handler) { + this._handlers[event] = this._handlers[event] || []; + this._handlers[event].push(handler); + } + + off() {} + +} diff --git a/src/main.js b/src/main.js new file mode 100644 index 000000000..22be5ef21 --- /dev/null +++ b/src/main.js @@ -0,0 +1,41 @@ +import Input from './inputs/input'; +import Layer from './layer'; +import Manager from './manager'; +import Recognizer from './recognizer'; + +import MouseInput from './inputs/mouse'; +import TouchInput from './inputs/touch'; + +import HorizontalPanRecognizer from './recognizers/horizontal-pan'; +import VerticalPanRecognizer from './recognizers/vertical-pan'; + +// this prevents errors when Hammer is loaded in the presence of an AMD +// style loader but by script tag, not by the loader. + +let Hammer = { // jshint ignore:line + Input, + Layer, + Manager, + Recognizer, + + MouseInput, + TouchInput, + + HorizontalPanRecognizer, + VerticalPanRecognizer +}; + +let freeGlobal = (typeof window !== 'undefined' ? window : (typeof self !== 'undefined' ? self : {})); // jshint ignore:line +freeGlobal.Hammer = Hammer; + +/* jshint ignore:start */ +if (typeof define === 'function' && define.amd) { + define(() => { + return Hammer; + }); +} else if (typeof module !== 'undefined' && module.exports) { + module.exports = Hammer; +} else { + window[exportName] = Hammer; +} +/* jshint ignore:end */ diff --git a/src/manager.js b/src/manager.js index 5a5e97e4e..af57823f6 100644 --- a/src/manager.js +++ b/src/manager.js @@ -1,312 +1,141 @@ -var STOP = 1; -var FORCED_STOP = 2; +import { availableInputs } from './utils/detection'; +import assign from './utils/assign'; -/** - * Manager - * @param {HTMLElement} element - * @param {Object} [options] - * @constructor - */ -function Manager(element, options) { - this.options = assign({}, Hammer.defaults, options || {}); - - this.options.inputTarget = this.options.inputTarget || element; - - this.handlers = {}; - this.session = {}; - this.recognizers = []; - this.oldCssProps = {}; - - this.element = element; - this.input = createInputInstance(this); - this.touchAction = new TouchAction(this, this.options.touchAction); - - toggleCssProps(this, true); - - each(this.options.recognizers, function(item) { - var recognizer = this.add(new (item[0])(item[1])); - item[2] && recognizer.recognizeWith(item[2]); - item[3] && recognizer.requireFailure(item[3]); - }, this); -} - -Manager.prototype = { - /** - * set options - * @param {Object} options - * @returns {Manager} - */ - set: function(options) { - assign(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; +const DEFAULT_OPTIONS = { + inputs: availableInputs() +}; - // 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; +export default class Manager { - // 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; - } + constructor(rootElement, options) { + this.rootElement = rootElement || window; + this.layers = new WeakMap(); + this._recognizedInputs = 0; + this.isRecognizing = false; - var i = 0; - while (i < recognizers.length) { - recognizer = recognizers[i]; + this.inputs = {}; + this.options = assign({}, DEFAULT_OPTIONS, options || {}); - // 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 (this.options.inputs) { + let inputs = Object.keys(this.options.inputs); - // 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++; - } - }, + for (let i = 0; i < inputs.length; i++) { + let name = inputs[i]; + let InputClass = this.options.inputs[name]; - /** - * 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; - }, + this.registerInput(name, InputClass); + } + } - /** - * 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); - } + registerInput(name, InputClass) { + this.inputs[name] = new InputClass(this.rootElement, this); + } - this.recognizers.push(recognizer); - recognizer.manager = this; + recognize(input, streams, stream, streamEvent) { + let layer = this._findParentLayer(streamEvent.element); - this.touchAction.update(); - return recognizer; - }, + while (layer) { + if (layer.recognize(input, streams, stream, streamEvent)) { + this.startInputRecognition(); + break; + } + layer = layer.parent; + } - /** - * remove a recognizer by name or instance - * @param {Recognizer|String} recognizer - * @returns {Manager} - */ - remove: function(recognizer) { - if (invokeArrayArg(recognizer, 'remove', this)) { - return this; - } + if (this.isRecognizing && streamEvent.name === 'end') { + this.endInputRecognition(); + } + } - recognizer = this.get(recognizer); + startInputRecognition() { + this._recognizedInputs++; + if (this._recognizedInputs === 1) { + this.isRecognizing = true; + document.body.setAttribute('gesture-no-touch', 'true'); + } + } - // let's make sure this recognizer exists - if (recognizer) { - var recognizers = this.recognizers; - var index = inArray(recognizers, recognizer); + endInputRecognition() { + this._recognizedInputs--; + if (this._recognizedInputs === 0) { + this.isRecognizing = false; + document.body.removeAttribute('gesture-no-touch'); + } + } - if (index !== -1) { - recognizers.splice(index, 1); - this.touchAction.update(); - } - } + unregisterInput(name) { + let input = this.inputs[name]; - return this; - }, + if (input) { + this.inputs[name] = null; + input.destroy(); + } + } - /** - * bind event - * @param {String} events - * @param {Function} handler - * @returns {EventEmitter} this - */ - on: function(events, handler) { - if (events === undefined) { - return; - } - if (handler === undefined) { - return; - } + registerLayer(layer) { + layer.element.setAttribute('gesture-layer', true); + this.layers.set(layer.element, layer); - var handlers = this.handlers; - each(splitStr(events), function(event) { - handlers[event] = handlers[event] || []; - handlers[event].push(handler); - }); - return this; - }, + layer.parent = this._findParentLayer(layer.element.parentNode); - /** - * unbind event, leave emit blank to remove all handlers - * @param {String} events - * @param {Function} [handler] - * @returns {EventEmitter} this - */ - off: function(events, handler) { - if (events === undefined) { - return; - } + // insert into linked layer list + if (layer.parent) { + layer.child = layer.parent.child; + layer.parent.child = layer; + } + } + + forgetLayer(layer) { + this.layers.delete(layer.element); + + // join parent/child + if (layer.parent && layer.child) { + layer.parent.child = layer.child; + + // unlink parent/child + } else { + if (layer.parent) { + layer.parent.child = null; + } + if (layer.child) { + layer.child.parent = null; + } + } + } - var handlers = this.handlers; - each(splitStr(events), function(event) { - if (!handler) { - delete handlers[event]; - } else { - handlers[event] && handlers[event].splice(inArray(handlers[event], handler), 1); - } - }); - return this; - }, + _findParentLayer(element) { + do { + if (element && element.hasAttribute('gesture-layer')) { + let layer = this.layers.get(element); - /** - * 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); + if (layer) { + return layer; } + } + } while (element && element !== document.body && (element = element.parentNode)); - // no handlers, so skip it all - var handlers = this.handlers[event] && this.handlers[event].slice(); - if (!handlers || !handlers.length) { - return; - } + return null; + } - data.type = event; - data.preventDefault = function() { - data.srcEvent.preventDefault(); - }; + _teardown() { + this.streams.touch.destroy(); + this.streams.mouse.destroy(); - var i = 0; - while (i < handlers.length) { - handlers[i](data); - i++; - } - }, + this.layers.forEach((layer) => { + layer.destroy(); + }); - /** - * 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.layers = null; + } - this.handlers = {}; - this.session = {}; - this.input.destroy(); - this.element = null; - } -}; + static create() { + return new Manager(); + } -/** - * 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; - if (!element.style) { - return; - } - var prop; - each(manager.options.cssProps, function(value, name) { - prop = prefixed(element.style, name); - if (add) { - manager.oldCssProps[prop] = element.style[prop]; - element.style[prop] = value; - } else { - element.style[prop] = manager.oldCssProps[prop] || ''; - } - }); - if (!add) { - manager.oldCssProps = {}; - } -} + destroy() { + this._teardown(); + } -/** - * 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); } diff --git a/src/recognizer.js b/src/recognizer.js index b0dd9fa04..00cd86c94 100644 --- a/src/recognizer.js +++ b/src/recognizer.js @@ -1,327 +1,7 @@ -/** - * 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; +export default class Recognizer { -/** - * Recognizer - * Every recognizer needs to extend from this class. - * @constructor - * @param {Object} options - */ -function Recognizer(options) { - this.options = assign({}, this.defaults, options || {}); + constructor(name) { + this.name = name; + } - this.id = uniqueId(); - - this.manager = null; - - // 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) { - assign(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(event) { - self.manager.emit(event, input); - } - - // 'panstart' and 'panmove' - if (state < STATE_ENDED) { - emit(self.options.event + stateStr(state)); - } - - emit(self.options.event); // simple 'eventName' events - - if (input.additionalEvent) { // additional event(panleft, panright, pinchin, pinchout...) - emit(input.additionalEvent); - } - - // panend and pancancel - if (state >= STATE_ENDED) { - emit(self.options.event + stateStr(state)); - } - }, - - /** - * 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 = assign({}, 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; } diff --git a/src/recognizers/attribute.js b/src/recognizers/attribute.js deleted file mode 100644 index 0d7fff4c6..000000000 --- a/src/recognizers/attribute.js +++ /dev/null @@ -1,60 +0,0 @@ -/** - * 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; - } -}); diff --git a/src/recognizers/horizontal-pan.js b/src/recognizers/horizontal-pan.js new file mode 100644 index 000000000..7f9bee249 --- /dev/null +++ b/src/recognizers/horizontal-pan.js @@ -0,0 +1,53 @@ + +export default class HorizontalPan { + constructor(options) { + this.name = 'horizontal-pan'; + this.options = options; + this.layer = undefined; + this.stream = undefined; + + this.isRecognizing = false; + } + + beginRecognizing(input, stream) { + this.isRecognizing = true; + + this.stream = stream; + let { series } = this.stream; + + series.forEach((event) => { + this.relay(event); + }); + } + + relay(event) { + if (event.name === 'start') { + this.layer.emit({ name: 'panStart', event }); + + } else if (event.name === 'end') { + this.isRecognizing = false; + this.layer.emit({ name: 'panEnd', event }); + this.stream = undefined; + + } else if (event.totalX < 0 || event.prev.totalX < 0) { + this.layer.emit({ name: 'panLeft', event }); + + } else { + this.layer.emit({ name: 'panRight', event }); + } + } + + emit(name, event) { + this.layer.emit({ name, event }); + } + + recognize(input, streams, stream, streamEvent) { + if (this.isRecognizing) { + this.relay(streamEvent); + } else if (input.openStreams === 1 && streamEvent.totalY === 0 && streamEvent.totalX !== 0) { + this.beginRecognizing(input, stream); + } + + return this.isRecognizing; + } +} diff --git a/src/recognizers/pan.js b/src/recognizers/pan.js deleted file mode 100644 index 8cc3398e4..000000000 --- a/src/recognizers/pan.js +++ /dev/null @@ -1,79 +0,0 @@ -/** - * 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) { - input.additionalEvent = this.options.event + direction; - } - this._super.emit.call(this, input); - } -}); diff --git a/src/recognizers/pinch.js b/src/recognizers/pinch.js deleted file mode 100644 index cc19b3075..000000000 --- a/src/recognizers/pinch.js +++ /dev/null @@ -1,38 +0,0 @@ -/** - * 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) { - if (input.scale !== 1) { - var inOut = input.scale < 1 ? 'in' : 'out'; - input.additionalEvent = this.options.event + inOut; - } - this._super.emit.call(this, input); - } -}); diff --git a/src/recognizers/press.js b/src/recognizers/press.js deleted file mode 100644 index d52f830cf..000000000 --- a/src/recognizers/press.js +++ /dev/null @@ -1,70 +0,0 @@ -/** - * 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: 251, // minimal time of the pointer to be pressed - threshold: 9 // 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); - } - } -}); diff --git a/src/recognizers/rotate.js b/src/recognizers/rotate.js deleted file mode 100644 index 207982406..000000000 --- a/src/recognizers/rotate.js +++ /dev/null @@ -1,30 +0,0 @@ -/** - * 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); - } -}); diff --git a/src/recognizers/swipe.js b/src/recognizers/swipe.js deleted file mode 100644 index fb3d66c77..000000000 --- a/src/recognizers/swipe.js +++ /dev/null @@ -1,55 +0,0 @@ -/** - * 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.3, - 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.overallVelocity; - } else if (direction & DIRECTION_HORIZONTAL) { - velocity = input.overallVelocityX; - } else if (direction & DIRECTION_VERTICAL) { - velocity = input.overallVelocityY; - } - - return this._super.attrTest.call(this, input) && - direction & input.offsetDirection && - input.distance > this.options.threshold && - input.maxPointers == this.options.pointers && - abs(velocity) > this.options.velocity && input.eventType & INPUT_END; - }, - - emit: function(input) { - var direction = directionStr(input.offsetDirection); - if (direction) { - this.manager.emit(this.options.event + direction, input); - } - - this.manager.emit(this.options.event, input); - } -}); diff --git a/src/recognizers/tap.js b/src/recognizers/tap.js deleted file mode 100644 index 072d45c94..000000000 --- a/src/recognizers/tap.js +++ /dev/null @@ -1,114 +0,0 @@ -/** - * 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: 9, // 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); - } - } -}); diff --git a/src/recognizers/vertical-pan.js b/src/recognizers/vertical-pan.js new file mode 100644 index 000000000..ad7b7feec --- /dev/null +++ b/src/recognizers/vertical-pan.js @@ -0,0 +1,53 @@ + +export default class VerticalPan { + constructor(options) { + this.name = 'vertical-pan'; + this.options = options; + this.layer = undefined; + this.stream = undefined; + + this.isRecognizing = false; + } + + beginRecognizing(input, streams) { + this.isRecognizing = true; + + this.stream = streams[streams.length - 1]; + let { series } = this.stream; + + series.forEach((event) => { + this.relay(event); + }); + } + + relay(event) { + if (event.name === 'start') { + this.layer.emit({ name: 'panStart', event }); + + } else if (event.name === 'end') { + this.isRecognizing = false; + this.layer.emit({ name: 'panEnd', event }); + this.stream = undefined; + + } else if (event.totalY < 0 || event.prev.totalY < 0) { + this.layer.emit({ name: 'panUp', event }); + + } else { + this.layer.emit({ name: 'panDown', event }); + } + } + + emit(name, event) { + this.layer.emit({ name, event }); + } + + recognize(input, streams, streamEvent) { + if (this.isRecognizing) { + this.relay(streamEvent); + } else if (streamEvent.totalX === 0 && streamEvent.totalY !== 0) { + this.beginRecognizing(input, streams); + } + + return this.isRecognizing; + } +} diff --git a/src/streams/stream-event.js b/src/streams/stream-event.js new file mode 100644 index 000000000..ac3923fdd --- /dev/null +++ b/src/streams/stream-event.js @@ -0,0 +1,162 @@ +/* global Math, performance */ +import FastArray from 'perf-primitives/addon/fast-array'; +const STREAM_EVENT_POOL = new FastArray(undefined, 'StreamEvent Pool'); + +export default class StreamEvent { + + constructor(name, info, prev) { + this.init(name, info, prev); + } + + init(name, info, prev) { + this.name = name; + this.element = info.event.target; + this._isImportantEvent = (name === 'end' || name === 'start' || (prev && prev.name === 'start')); + this._source = this._isImportantEvent ? info.event : undefined; + this.silenced = false; + this.prev = prev; + this.pointerId = info.pointerId; + + // time + this.time = performance.now(); + this.dT = prev ? this.time - prev.time : 0; + + // current position (clientX/Y) + this.x = info.x; + this.y = info.y; + + // deltas off of origin event + this.originX = info.originX; + this.originY = info.originY; + this.totalX = info.x - this.originX; + this.totalY = info.y - this.originY; + + // deltas off the segment + this.segmentOriginX = info.segmentOriginX; + this.segmentOriginY = info.segmentOriginY; + this.segmentX = info.x - this.segmentOriginX; + this.segmentY = info.y - this.segmentOriginY; + + // deltas off of last event + this.dX = prev ? info.x - prev.x : 0; + this.dY = prev ? info.y - prev.y : 0; + + // prediction values + this.acceleration = 0; + this.aX = 0; + this.aY = 0; + + this.velocity = 0; + this.vX = 0; + this.vY = 0; + + this.nextX = 0; + this.nextY = 0; + } + + getAccelerationX() { + const { dT, prev } = this; + const vX = this.getVelocityX(); + const { vX: _vX } = prev; + + return this.aX = (vX - _vX) / dT; + } + + getAccelerationY() { + const { dT, prev } = this; + const vY = this.getVelocityY(); + const { vY: _vY } = prev; + + return this.aY = (vY - _vY) / dT; + } + + getAcceleration() { + const aX = this.getAccelerationX(); + const aY = this.getAccelerationY(); + let acceleration = this.acceleration = Math.sqrt(aX * aX + aY * aY); + + return { aX, aY, acceleration }; + } + + getVelocityX() { + const { dX, dT } = this; + + return this.vX = dX / dT; + } + + getVelocityY() { + const { dY, dT } = this; + + return this.vY = dY / dT; + } + + getVelocity() { + const vX = this.getVelocityX(); + const vY = this.getVelocityY(); + let velocity = this.velocity = Math.sqrt(vX * vX + vY * vY); + + return { vX, vY, velocity }; + } + + predictX() { + const aX = this.getAccelerationX(); + const { x, dX, vX, dT, totalX } = this; + + // distance = initial distance + velocity * time + 1/2 acceleration * time^2 + let nextDeltaX = Math.round((vX * dT) + (0.5 * aX * dT * dT)); + let nextdX = dX + nextDeltaX; + let nextX = x + nextDeltaX; + let nextTotalX = totalX + nextDeltaX; + + return this.nextX = { x: nextX, dX: nextdX, totalX: nextTotalX }; + } + + predictY() { + const aY = this.getAccelerationY(); + const { y, dY, vY, dT, totalY } = this; + + // distance = initial distance + velocity * time + 1/2 acceleration * time^2 + let nextDeltaY = Math.round((vY * dT) + (0.5 * aY * dT * dT)); + let nextdY = dY + nextDeltaY; + let nextY = y + nextDeltaY; + let nextTotalY = totalY + nextDeltaY; + + return this.nextY = { y: nextY, dY: nextdY, totalY: nextTotalY }; + } + + predict() { + const nextX = this.predictX(); + const nextY = this.predictY(); + + return { x: nextX, y: nextY }; + } + + // cancel any default behaviors from this event + silence() { + if (this._source && this._source.cancelable) { + this._source.preventDefault(); + this._source.stopPropagation(); + this.silenced = true; + } + } + + static create(name, info, prev) { + let event = STREAM_EVENT_POOL.pop(); + + if (event) { + event.init(name, info, prev); + return event; + } + + return new StreamEvent(name, info, prev); + } + + destroy() { + this._source = undefined; + this.prev = undefined; + this.element = undefined; + + STREAM_EVENT_POOL.push(this); + } + +} diff --git a/src/streams/stream-series.js b/src/streams/stream-series.js new file mode 100644 index 000000000..410081adc --- /dev/null +++ b/src/streams/stream-series.js @@ -0,0 +1,46 @@ +import FastArray from 'perf-primitives/addon/fast-array'; +import { SMALL_ARRAY_LENGTH } from 'perf-primitives/addon/-constants'; + +const STREAM_SERIES_POOL = new FastArray(10, 'StreamSeries Pool'); + +export default class StreamSeries extends FastArray { + + constructor(values, number = SMALL_ARRAY_LENGTH, name = 'StreamEvent to List') { + super(number, name); + + this.init(values, number, name); + this._isDestroyed = false; + } + + init({ originX, originY }, length, name) { + super.init(length, name); + this.originX = originX; + this.originY = originY; + } + + static create(values, number = SMALL_ARRAY_LENGTH, name = 'StreamEvent to List') { + let series = STREAM_SERIES_POOL.pop(); + + if (series) { + series.init(values, number, name); + + return series; + } + + return new StreamSeries(values, number, name); + } + + destroy() { + if (!this._isDestroyed) { + this._isDestroyed = true; + + for (let j = 0; j < this.length; j++) { + this._data[j].destroy(); + this._data[j] = undefined; + } + + STREAM_SERIES_POOL.push(this); + } + } + +}; diff --git a/src/streams/stream.js b/src/streams/stream.js new file mode 100644 index 000000000..584057b3d --- /dev/null +++ b/src/streams/stream.js @@ -0,0 +1,108 @@ +import StreamEvent from './stream-event'; +import StreamSeries from './stream-series'; +import MacroTask from '../utils/macro-task'; +import FastArray from 'perf-primitives/addon/fast-array'; + +const STREAM_POOL = new FastArray(5, 'Stream Pool'); + +export default class Stream { + + constructor(values) { + this.init(values); + } + + init({ pointerId, originX, originY }) { + this.segments = new FastArray(5, 'Segments'); + this.series = undefined; + this._isDestroyed = false; + this._isDestroying = false; + this.active = false; + this.pointerId = pointerId; + this.originX = originX; + this.originY = originY; + } + + open(info) { + this.active = true; + this.series = StreamSeries.create({ originX: info.x, originY: info.y }); + this.segments.push(this.series); + + let streamEvent = StreamEvent.create('start', this._addContextToInfo(info)); + + this.series.push(streamEvent); + return streamEvent; + } + + push(info) { + let lastEvent = this.series.get(this.series.length - 1); + let streamEvent = StreamEvent.create('move', this._addContextToInfo(info), lastEvent); + + this.series.push(streamEvent); + return streamEvent; + } + + close(info) { + this.active = false; + let lastEvent = this.series.get(this.series.length - 1); + let streamEvent = StreamEvent.create('end', this._addContextToInfo(info), lastEvent); + + this.series.push(streamEvent); + + this._isDestroying = true; + new MacroTask(() => { + this.destroy(); + }); + + return streamEvent; + } + + silence() { + let series = this.segments.get(0); + let down = series.get(0); + let initial = series.get(1); + + down.silence(); + initial.silence(); + } + + split() { + let lastEvent = this.series.get(this.series.length - 1); + this.series = StreamSeries.create({ originX: lastEvent.x, originY: lastEvent.y }); + this.segments.push(this.series); + } + + destroy() { + if (!this._isDestroyed) { + this._isDestroyed = true; + this.series = undefined; + + this.segments.forEach((series) => { + series.destroy(); + }); + this.segments = undefined; + + STREAM_POOL.push(this); + } + } + + _addContextToInfo(info) { + info.originX = this.originX; + info.originY = this.originY; + info.segmentOriginX = this.series.originX; + info.segmentOriginY = this.series.originY; + + return info; + } + + static create(values) { + let stream = STREAM_POOL.pop(); + + if (stream) { + stream.init(values); + return stream; + } + + return new Stream(values); + } + +} diff --git a/src/touchaction.js b/src/touchaction.js deleted file mode 100644 index 55609012b..000000000 --- a/src/touchaction.js +++ /dev/null @@ -1,164 +0,0 @@ -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'; -var TOUCH_ACTION_MAP = getTouchActionProps(); - -/** - * 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 && TOUCH_ACTION_MAP[value]) { - 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) { - 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) && !TOUCH_ACTION_MAP[TOUCH_ACTION_NONE]; - var hasPanY = inStr(actions, TOUCH_ACTION_PAN_Y) && !TOUCH_ACTION_MAP[TOUCH_ACTION_PAN_Y]; - var hasPanX = inStr(actions, TOUCH_ACTION_PAN_X) && !TOUCH_ACTION_MAP[TOUCH_ACTION_PAN_X]; - - if (hasNone) { - //do not prevent defaults if this is a tap gesture - - var isTapPointer = input.pointers.length === 1; - var isTapMovement = input.distance < 2; - var isTapTouchTime = input.deltaTime < 250; - - if (isTapPointer && isTapMovement && isTapTouchTime) { - return; - } - } - - if (hasPanX && hasPanY) { - // `pan-x pan-y` means browser handles all scrolling/panning, do not prevent - return; - } - - if (hasNone || - (hasPanY && direction & DIRECTION_HORIZONTAL) || - (hasPanX && direction & DIRECTION_VERTICAL)) { - 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); - - // if both pan-x and pan-y are set (different recognizers - // for different directions, e.g. horizontal pan but vertical swipe?) - // we need none (as otherwise with pan-x pan-y combined none of these - // recognizers will work, since the browser would handle all panning - if (hasPanX && hasPanY) { - return TOUCH_ACTION_NONE; - } - - // 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; -} - -function getTouchActionProps() { - if (!NATIVE_TOUCH_ACTION) { - return false; - } - var touchMap = {}; - var cssSupports = window.CSS && window.CSS.supports; - ['auto', 'manipulation', 'pan-y', 'pan-x', 'pan-x pan-y', 'none'].forEach(function(val) { - - // If css.supports is not supported but there is native touch-action assume it supports - // all values. This is the case for IE 10 and 11. - touchMap[val] = cssSupports ? window.CSS.supports('touch-action', val) : true; - }); - return touchMap; -} diff --git a/src/utils.js b/src/utils.js deleted file mode 100644 index c923ce533..000000000 --- a/src/utils.js +++ /dev/null @@ -1,371 +0,0 @@ -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); - } - } -} - -/** - * wrap a method with a deprecation warning and stack trace - * @param {Function} method - * @param {String} name - * @param {String} message - * @returns {Function} A new function wrapping the supplied method. - */ -function deprecate(method, name, message) { - var deprecationMessage = 'DEPRECATED METHOD: ' + name + '\n' + message + ' AT \n'; - return function() { - var e = new Error('get-stack-trace'); - var stack = e && e.stack ? e.stack.replace(/^[^\(]+?[\n$]/gm, '') - .replace(/^\s+at\s+/gm, '') - .replace(/^Object.\s*\(/gm, '{anonymous}()@') : 'Unknown Stack Trace'; - - var log = window.console && (window.console.warn || window.console.log); - if (log) { - log.call(window.console, deprecationMessage, stack); - } - return method.apply(this, arguments); - }; -} - -/** - * extend object. - * means that properties in dest will be overwritten by the ones in src. - * @param {Object} target - * @param {...Object} objects_to_assign - * @returns {Object} target - */ -var assign; -if (typeof Object.assign !== 'function') { - assign = function assign(target) { - if (target === undefined || target === null) { - throw new TypeError('Cannot convert undefined or null to object'); - } - - var output = Object(target); - for (var index = 1; index < arguments.length; index++) { - var source = arguments[index]; - if (source !== undefined && source !== null) { - for (var nextKey in source) { - if (source.hasOwnProperty(nextKey)) { - output[nextKey] = source[nextKey]; - } - } - } - } - return output; - }; -} else { - assign = Object.assign; -} - -/** - * extend object. - * means that properties in dest will be overwritten by the ones in src. - * @param {Object} dest - * @param {Object} src - * @param {Boolean} [merge=false] - * @returns {Object} dest - */ -var extend = deprecate(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; -}, 'extend', 'Use `assign`.'); - -/** - * 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 - */ -var merge = deprecate(function merge(dest, src) { - return extend(dest, src, true); -}, 'merge', 'Use `assign`.'); - -/** - * 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) { - assign(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 || element; - return (doc.defaultView || doc.parentWindow || window); -} diff --git a/src/utils/assign.js b/src/utils/assign.js new file mode 100644 index 000000000..8cc50220a --- /dev/null +++ b/src/utils/assign.js @@ -0,0 +1,33 @@ +/** + * @private + * extend object. + * means that properties in dest will be overwritten by the ones in src. + * @param {Object} target + * @param {...Object} objects_to_assign + * @returns {Object} target + */ +let assign; +if (typeof Object.assign !== 'function') { + assign = function assign(target) { + if (target === undefined || target === null) { + throw new TypeError('Cannot convert undefined or null to object'); + } + + let output = Object(target); + for (let index = 1; index < arguments.length; index++) { + const source = arguments[index]; + if (source !== undefined && source !== null) { + for (const nextKey in source) { + if (source.hasOwnProperty(nextKey)) { + output[nextKey] = source[nextKey]; + } + } + } + } + return output; + }; +} else { + assign = Object.assign; +} + +export default assign; diff --git a/src/utils/detection.js b/src/utils/detection.js new file mode 100644 index 000000000..eb1b28baf --- /dev/null +++ b/src/utils/detection.js @@ -0,0 +1,32 @@ +/* global navigator, window */ +import TouchInput from './../inputs/touch'; +import MouseInput from './../inputs/mouse'; +// import PointerInput from './inputs/pointer'; + +const MAY_SUPPORT_TOUCH = (('ontouchstart' in window) || // html5 browsers + (navigator.maxTouchPoints > 0) || // future IE + (navigator.msMaxTouchPoints > 0)); // current IE10 + +const MAY_SUPPORT_MOUSE = true; + +// const SUPPORT_POINTER_EVENTS = prefixed(window, 'PointerEvent') !== undefined; + +function availableInputs() { + let inputs = {}; + + if (MAY_SUPPORT_MOUSE) { + inputs.mouse = MouseInput; + } + + if (MAY_SUPPORT_TOUCH) { + inputs.touch = TouchInput; + } + + return inputs; +} + +export { + availableInputs +}; + +export default availableInputs; diff --git a/src/utils/macro-task.js b/src/utils/macro-task.js new file mode 100644 index 000000000..b4bbec27c --- /dev/null +++ b/src/utils/macro-task.js @@ -0,0 +1,7 @@ +export default class MacroTask { + + constructor(job) { + setTimeout(job, 0); + } + +} diff --git a/src/utils/supports-passive.js b/src/utils/supports-passive.js new file mode 100644 index 000000000..3a9e6df05 --- /dev/null +++ b/src/utils/supports-passive.js @@ -0,0 +1,13 @@ +let supportsPassive = false; + +try { + let opts = Object.defineProperty({}, 'passive', { + get() { + supportsPassive = true; + } + }); + + window.addEventListener('test', null, opts); +} catch (e) {} + +export default supportsPassive; diff --git a/tests/unit/index.html b/tests/unit/index.html index 2ad74a6cf..ccfdf5a59 100644 --- a/tests/unit/index.html +++ b/tests/unit/index.html @@ -17,7 +17,7 @@ Simulator.events.touch.fakeSupport(); - +
diff --git a/tests/unit/test_hammer.js b/tests/unit/test_hammer.js index da50006ab..e195b4d66 100644 --- a/tests/unit/test_hammer.js +++ b/tests/unit/test_hammer.js @@ -19,25 +19,30 @@ module('Tests', { } }); -test('hammer shortcut', function() { - expect(2); - - Hammer.defaults.touchAction = 'pan-y'; - hammer = Hammer(el); - - ok(hammer instanceof Hammer.Manager, 'returns an instance of Manager'); - ok(hammer.touchAction.actions == Hammer.defaults.touchAction, 'set the default touchAction'); -}); - -test('hammer shortcut with options', function() { - expect(2); - - hammer = Hammer(el, { - touchAction: 'none' - }); - ok(hammer instanceof Hammer.Manager, 'returns an instance of Manager'); - ok(hammer.touchAction.actions == 'none', 'set the default touchAction'); -}); +/** + * since Hammer is now a ES6 Class and we cannot call a class as a function, + * it needs a `new` keyword prefixed that makes this Shortcut test kinda Redundant. +**/ + +// test('hammer shortcut', function() { +// expect(2); +// +// Hammer.defaults.touchAction = 'pan-y'; +// hammer = Hammer(el); +// +// ok(hammer instanceof Hammer.Manager, 'returns an instance of Manager'); +// ok(hammer.touchAction.actions == Hammer.defaults.touchAction, 'set the default touchAction'); +// }); + +// test('hammer shortcut with options', function() { +// expect(2); +// +// hammer = Hammer(el, { +// touchAction: 'none' +// }); +// ok(hammer instanceof Hammer.Manager, 'returns an instance of Manager'); +// ok(hammer.touchAction.actions == 'none', 'set the default touchAction'); +// }); /* Creating a hammer instance does not work on the same way * when using Hammer or Hammer.Manager. @@ -175,7 +180,7 @@ test('check whether Hammer.defaults.cssProps is restored', function() { } }); - hammer = Hammer(el); + hammer = new Hammer(el); hammer.destroy(); hammer = null; Hammer.each(Hammer.defaults.cssProps, function(value, name) { diff --git a/tests/unit/test_jquery_plugin.js b/tests/unit/test_jquery_plugin.js index 01713e23a..20a9d60c3 100644 --- a/tests/unit/test_jquery_plugin.js +++ b/tests/unit/test_jquery_plugin.js @@ -41,7 +41,7 @@ asyncTest('trigger pan with jQuery', function() { asyncTest('trigger pan without jQuery should still work', function() { expect(1); - var hammer = Hammer(el); + var hammer = new Hammer(el); hammer.on('panstart pan panmove panright panend', function(ev) { events[ev.type] = true; }); diff --git a/tests/unit/test_utils.js b/tests/unit/test_utils.js index f9d5f80b3..321b1afac 100644 --- a/tests/unit/test_utils.js +++ b/tests/unit/test_utils.js @@ -1,12 +1,12 @@ module('utils'); -// for the tests, all hammer properties and methods of Hammer are exposed to window.$H +// for the tests, all hammer properties and methods of Hammer are exposed to window.Hammer test('get/set prefixed util', function() { - ok(_.isUndefined($H.prefixed(window, 'FakeProperty')), 'non existent property returns undefined'); + ok(_.isUndefined(Hammer.prefixed(window, 'FakeProperty')), 'non existent property returns undefined'); window.webkitFakeProperty = 1337; - ok($H.prefixed(window, 'FakeProperty') == 'webkitFakeProperty', 'existent prefixed property returns the prefixed name'); + ok(Hammer.prefixed(window, 'FakeProperty') == 'webkitFakeProperty', 'existent prefixed property returns the prefixed name'); delete window.webkitFakeProperty; }); @@ -14,7 +14,7 @@ test('get/set prefixed util', function() { test('fnBind', function() { var context = { a: true }; - $H.bindFn(function(b) { + Hammer.bindFn(function(b) { ok(this.a === true, 'bindFn scope'); ok(b === 123, 'bindFn argument'); }, context)(123); @@ -29,7 +29,7 @@ test('Inherit objects', function() { Base.call(this); } - $H.inherit(Child, Base, { + Hammer.inherit(Child, Base, { newMethod: function() { } }); @@ -45,29 +45,29 @@ test('Inherit objects', function() { }); test('toArray', function() { - ok(_.isArray($H.toArray({ 0: true, 1: 'second', length: 2 })), 'converted an array-like object to an array'); - ok(_.isArray($H.toArray([true, true])), 'array stays an array'); + ok(_.isArray(Hammer.toArray({ 0: true, 1: 'second', length: 2 })), 'converted an array-like object to an array'); + ok(_.isArray(Hammer.toArray([true, true])), 'array stays an array'); }); test('inArray', function() { - ok($H.inArray([1, 2, 3, 4, 'hammer'], 'hammer') === 4, 'found item and returned the index'); - ok($H.inArray([1, 2, 3, 4, 'hammer'], 'notfound') === -1, 'not found an item and returned -1'); - ok($H.inArray([ + ok(Hammer.inArray([1, 2, 3, 4, 'hammer'], 'hammer') === 4, 'found item and returned the index'); + ok(Hammer.inArray([1, 2, 3, 4, 'hammer'], 'notfound') === -1, 'not found an item and returned -1'); + ok(Hammer.inArray([ {id: 2}, {id: 24} ], '24', 'id') === 1, 'find by key and return the index'); - ok($H.inArray([ + ok(Hammer.inArray([ {id: 2}, {id: 24} ], '22', 'id') === -1, 'not found by key and return -1'); }); test('splitStr', function() { - deepEqual($H.splitStr(' a b c d '), ['a', 'b', 'c', 'd'], 'str split valid'); + deepEqual(Hammer.splitStr(' a b c d '), ['a', 'b', 'c', 'd'], 'str split valid'); }); test('uniqueArray', function() { - deepEqual($H.uniqueArray([ + deepEqual(Hammer.uniqueArray([ {id: 1}, {id: 2}, {id: 2} @@ -78,12 +78,12 @@ test('uniqueArray', function() { }); test('boolOrFn', function() { - equal($H.boolOrFn(true), true, 'Passing an boolean'); - equal($H.boolOrFn(false), false, 'Passing an boolean'); - equal($H.boolOrFn(function() { + equal(Hammer.boolOrFn(true), true, 'Passing an boolean'); + equal(Hammer.boolOrFn(false), false, 'Passing an boolean'); + equal(Hammer.boolOrFn(function() { return true; }), true, 'Passing an boolean'); - equal($H.boolOrFn(1), true, 'Passing an integer'); + equal(Hammer.boolOrFn(1), true, 'Passing an integer'); }); test('hasParent', function() { @@ -93,8 +93,8 @@ test('hasParent', function() { document.body.appendChild(parent); parent.appendChild(child); - equal($H.hasParent(child, parent), true, 'Found parent'); - equal($H.hasParent(parent, child), false, 'Not in parent'); + equal(Hammer.hasParent(child, parent), true, 'Found parent'); + equal(Hammer.hasParent(parent, child), false, 'Not in parent'); document.body.removeChild(parent); }); @@ -105,7 +105,7 @@ test('each', function() { var loop; loop = false; - $H.each(object, function(value, key) { + Hammer.each(object, function(value, key) { if (key == 'hi' && value === true) { loop = true; } @@ -113,7 +113,7 @@ test('each', function() { ok(loop, 'object loop'); loop = 0; - $H.each(array, function(value, key) { + Hammer.each(array, function(value, key) { if (value) { loop++; } @@ -122,7 +122,7 @@ test('each', function() { loop = 0; array.forEach = null; - $H.each(array, function(value, key) { + Hammer.each(array, function(value, key) { if (value) { loop++; } @@ -133,7 +133,7 @@ test('each', function() { test('assign', function() { expect(2); deepEqual( - $H.assign( + Hammer.assign( {a: 1, b: 3}, {b: 2, c: 3} ), @@ -142,7 +142,7 @@ test('assign', function() { ); var src = { foo: true }; - var dest = $H.assign({}, src); + var dest = Hammer.assign({}, src); src.foo = false; deepEqual(dest, {foo: true}, 'Clone reference'); }); @@ -154,11 +154,11 @@ test('test add/removeEventListener', function() { expect(2); - $H.addEventListeners(window, 'testEvent1 testEvent2 ', handleEvent); + Hammer.addEventListeners(window, 'testEvent1 testEvent2 ', handleEvent); utils.triggerDomEvent(window, 'testEvent1'); utils.triggerDomEvent(window, 'testEvent2'); - $H.removeEventListeners(window, ' testEvent1 testEvent2 ', handleEvent); + Hammer.removeEventListeners(window, ' testEvent1 testEvent2 ', handleEvent); utils.triggerDomEvent(window, 'testEvent1'); utils.triggerDomEvent(window, 'testEvent2'); });