diff --git a/README.md b/README.md index ee6d799..a198ef1 100644 --- a/README.md +++ b/README.md @@ -47,7 +47,7 @@ Using the `` component vue-touch supports all Hammer Events ot of the box, just bind a listener to the component with `v-on` and vue-touch will setup the Hammer Manager & Recognizer for you. |Recognizer|Events|Example| -|---|----|----| +|---|----|----|----| |**Pan**|`pan`, `panstart`, `panmove`, `panend`, `pancancel`,
`panleft`, `panright`, `panup`, `pandown` |`v-on:panstart="callback"`| |**Pinch**|`pinch`, `pinchstart`, `pinchmove`,`pinchend`,
`pinchcancel`, `pinchin`, `pinchout`| `v-on:pinchout="callback"`| |**Press**|`press`, `pressup`|`v-on:pressup="callback"`| @@ -92,6 +92,31 @@ VueTouch keeps that from you and accepts simple strings as directions: const directions = ['up', 'down', 'left', 'right', 'horizontal', 'vertical', 'all'] ``` +#### `recognize-with` & `require-failure` Props + +To define which gestures should be recognized together, use `recognize-with`: + +```html + +``` + +If you a recognizer to trigger only if another one failes, use `require-failure`: +```html + + + +``` + #### The 'enabled' Prop |Prop|allowed Values| diff --git a/build/rollup.config.prod.js b/build/rollup.config.prod.js deleted file mode 100644 index ee938d9..0000000 --- a/build/rollup.config.prod.js +++ /dev/null @@ -1,23 +0,0 @@ -import buble from 'rollup-plugin-buble' -import commonjs from 'rollup-plugin-commonjs' -import nodeResolve from 'rollup-plugin-node-resolve' -import cleanup from 'rollup-plugin-cleanup' - -export default { - entry: './src/index.js', - dest: './dist/vue-touch.js', - // Module settings - format: 'umd', - external: ['hammerjs'], - globals: { - hammerjs: 'Hammer' - }, - moduleName: 'VueTouch', - - plugins: [ - buble(), - nodeResolve({ jsnext: true, main: true }), - commonjs(), - cleanup() - ] -} diff --git a/build/rollup.js b/build/rollup.js new file mode 100644 index 0000000..a2cb1a9 --- /dev/null +++ b/build/rollup.js @@ -0,0 +1,45 @@ +const buble = require('rollup-plugin-buble'); +const commonjs = require('rollup-plugin-commonjs'); +const nodeResolve = require('rollup-plugin-node-resolve'); +const cleanup = require('rollup-plugin-cleanup'); +const { rollup } = require('rollup'); +const path = require('path'); + +const bubbleConfig = { + transforms: { dangerousForOf: true }, +}; + +// UMD +rollup({ + entry: path.resolve(__dirname, '../src/index.js'), + sourceMap: true, + external: ['hammerjs'], + + plugins: [ + buble(bubbleConfig), + nodeResolve({ jsnext: true, main: true }), + commonjs(), + cleanup() + ] +}).then(bundle => bundle.write({ + format: 'umd', + exports: 'named', + moduleName: 'VueTouch', + globals: { + hammerjs: 'Hammer' + }, + dest: path.join(path.resolve(__dirname, '../dist'), 'vue-touch.js') +})) + +// ESM +rollup({ + entry: path.resolve(__dirname, '../src/index.js'), + external: ['hammerjs'], + plugins: [ + buble(bubbleConfig), + cleanup() + ] +}).then(bundle => bundle.write({ + format: 'es', + dest: path.join(path.resolve(__dirname, '../dist'), 'vue-touch.esm.js'), +})); diff --git a/example/example.js b/example/example.js index cae595c..db82cfb 100644 --- a/example/example.js +++ b/example/example.js @@ -3,7 +3,7 @@ var Vue = require('vue') var VueTouch if (process.env.NODE_ENV === 'development') { - VueTouch = require('../src') + VueTouch = require('../src').default } else { VueTouch = require('../dist/vue-touch.js') @@ -29,10 +29,14 @@ new Vue({ state: {rotate: true, doubletap: true} }, methods: { - test: function (e) { + test: function (e, name = '') { delete e.target this.event = e - console.log(e) + console.log(e, name) + }, + testdouble: function (e) { + console.log('doubletap') + this.test(e) } } }) diff --git a/example/index.html b/example/index.html index b6102f0..7fd2d17 100644 --- a/example/index.html +++ b/example/index.html @@ -14,13 +14,19 @@
-

{{event.type}}

- --> +
{{event}}
diff --git a/package.json b/package.json index 5cecab2..e8301c5 100644 --- a/package.json +++ b/package.json @@ -2,8 +2,11 @@ "name": "vue-touch", "version": "2.0.0-beta.4", "main": "dist/vue-touch.js", + "jsnext:main": "dist/vue-touch.esm.js", + "module": "dist/vue-touch.esm.js", "files": [ "dist/vue-touch.js", + "dist/vue-touch.esm.js", "dist/vue-touch.js.map", "dist/vue-touch.min.js", "dist/hammer-ssr.js" @@ -39,7 +42,7 @@ "vue": "^2.0.0" }, "scripts": { - "build": "node_modules/.bin/rollup -m -c build/rollup.config.prod.js && uglifyjs dist/vue-touch.js -c -m > dist/vue-touch.min.js && cp src/hammer-ssr.js dist/", + "build": "node build/rollup.js && uglifyjs dist/vue-touch.js -c -m > dist/vue-touch.min.js && cp src/hammer-ssr.js dist/", "dev": "node build/devserver.js", "test:watch": "NODE_ENV=development node_modules/.bin/ava --watch --verbose", "test:unit:dev": "NODE_ENV=development node_modules/.bin/ava --verbose", diff --git a/src/component.js b/src/component.js index e560ff7..8522f10 100644 --- a/src/component.js +++ b/src/component.js @@ -1,43 +1,52 @@ -import Hammer from 'hammerjs' +import Hammer from 'hammerjs' import { createProp, capitalize, guardDirections, - gestures, - gestureMap, - directions, - assign, - config, - customEvents + normalizeGesture, + objectHasArrayValues, } from './utils' +import { + customEvents +} from './events' + export default { props: { options: createProp(), - tapOptions: createProp(), - panOptions: createProp(), - pinchOptions: createProp(), - pressOptions: createProp(), - rotateOptions: createProp(), - swipeOptions: createProp(), + tap: createProp(), + pan: createProp(), + pinch: createProp(), + press: createProp(), + rotate: createProp(), + swipe: createProp(), tag: { type: String, default: 'div' }, + recognizeWith: { + type: Object, + default: () => ({}), + validate: objectHasArrayValues, + }, + requireFailure: { + type: Object, default: () => ({}), + validate: objectHasArrayValues, + }, enabled: { default: true, type: [Boolean, Object], - } }, mounted() { if (!this.$isServer) { this.hammer = new Hammer.Manager(this.$el, this.options) - this.recognizers = {} // not reactive - this.setupBuiltinRecognizers() - this.setupCustomRecognizers() + this.recognizers = {} + this.setupRecognizers() + this.setupRecognizerDependencies() this.updateEnabled(this.enabled) } }, + destroyed() { if (!this.$isServer) { this.hammer.destroy() @@ -55,49 +64,42 @@ export default { methods: { - setupBuiltinRecognizers() { - // Built-in Hammer events - // We check weither any event callbacks are registered - // for the gesture, and if so, add a Recognizer - for (let i = 0; i < gestures.length; i++) { - const gesture = gestures[i] - if (this._events[gesture]) { - // get the main gesture (e.g. 'panstart' -> 'pan') - const mainGesture = gestureMap[gesture] - //merge global and local options - const options = assign({}, (config[mainGesture] || {}), this[`${mainGesture}Options`]) - // add recognizer for this main gesture - this.addRecognizer(mainGesture, options) - // register Event Emit for the specific gesture + /** + * Register recognizers for any event that matches + * a defined gesture or custom event. + */ + setupRecognizers() { + for (let gesture of Object.keys(this._events)) { + if (normalizeGesture(gesture)) { this.addEvent(gesture) + + gesture = normalizeGesture(gesture) + const options = Object.assign({}, this.$options.config[gesture] || {}, this[gesture]) + this.addRecognizer(gesture, options) + } else if (customEvents(gesture)) { + this.addEvent(gesture) + + const options = Object.assign({}, customEvents(gesture), this[gesture]) + this.addRecognizer(gesture, options, { mainGesture: options.type }) + } else { + throw new Error(`Unknown gesture: ${gesture}`) } } }, - setupCustomRecognizers() { - // Custom events - // We get the customGestures and options from the - // customEvents object, then basically do the same check - // as we did for the built-in events. - const gestures = Object.keys(customEvents) - - for (let i = 0; i < gestures.length; i++) { - - const gesture = gestures[i] + setupRecognizerDependencies() { + for (const [key, value] of Object.entries(this.recognizeWith)) { + this.recognizers[key] && this.recognizers[key].recognizeWith(value.map(name => this.recognizers[name])) + } - if (this._events[gesture]) { - const opts = customEvents[gesture] - const localCustomOpts = this[`${gesture}Options`] || {} - const options = assign({}, opts, localCustomOpts) - this.addRecognizer(gesture, options, {mainGesture: options.type}) - this.addEvent(gesture) - } + for (const [key, value] of Object.entries(this.requireFailure)) { + this.recognizers[key] && this.recognizers[key].requireFailure(value.map(name => this.recognizers[name])) } }, /** - * Registers a new Recognizer with the manager and saves it on the component - * instance + * Registers a new Recognizer with the manager and saves it on the component instance + * * @param {String} gesture See utils.js -> gestures * @param {Object} options Hammer options * @param {String} mainGesture if gesture is a custom event name, mapping to utils.js -> gestures @@ -105,22 +107,18 @@ export default { addRecognizer: function addRecognizer(gesture, options, { mainGesture } = {}) { // create recognizer, e.g. new Hammer['Swipe'](options) if (!this.recognizers[gesture]) { - const recognizer = new Hammer[capitalize(mainGesture || gesture)](guardDirections(options)) - this.recognizers[gesture] = recognizer - this.hammer.add(recognizer) - recognizer.recognizeWith(this.hammer.recognizers) + this.recognizers[gesture] = new Hammer[capitalize(mainGesture || gesture)](guardDirections(options)) + this.hammer.add(this.recognizers[gesture]) } }, addEvent(gesture) { - this.hammer.on(gesture, (e) => this.$emit(gesture, e)) + this.hammer.on(gesture, e => this.$emit(gesture, e)) }, - // Enabling / Disabling certain recognizers. - /** * Called when the `enabled` prop changes, and during mounted() - * It enables/disables values according to the value of the `emabled` prop + * It enables/disables values according to the value of the `enabled` prop * @param {Boolean|Object} newVal If an object: { recognizer: true|false } * @param {Boolean|Object} oldVal The previous value * @return {undefined} @@ -128,64 +126,55 @@ export default { updateEnabled: function updateEnabled(newVal, oldVal) { if (newVal === true) { this.enableAll() - } else if (newVal === false) { this.disableAll() - } else if (typeof newVal === 'object') { - const keys = Object.keys(newVal) - - for (let i = 0; i < keys.length; i++) { - const event = keys[i] - - if (this.recognizers[event]) { - newVal[event] - ? this.enable(event) - : this.disable(event) - } + for (const [event, status] of Object.entries(newVal)) { + this.recognizers[event] && status ? this.enable(event) : this.disable(event) } } }, - enable(r) { - const recognizer = this.recognizers[r] + enable(gesture) { + const recognizer = this.recognizers[gesture] if (!recognizer.options.enable) { recognizer.set({ enable: true }) } }, - disable(r) { - const recognizer = this.recognizers[r] + + disable(gesture) { + const recognizer = this.recognizers[gesture] if (recognizer.options.enable) { recognizer.set({ enable: false }) } }, - toggle(r) { - const recognizer = this.recognizers[r] + + toggle(gesture) { + const recognizer = this.recognizers[gesture] + if (recognizer) { - recognizer.options.enable - ? this.disable(r) - : this.enable(r) + recognizer.options.enable ? this.disable(gesture) : this.enable(gesture) } }, - enableAll(r) { - this.toggleAll({ enable: true }) + enableAll() { + this.setAll({ enable: true }) }, - disableAll(r) { - this.toggleAll({ enable: false }) + + disableAll() { + this.setAll({ enable: false }) }, - toggleAll({ enable }) { - const keys = Object.keys(this.recognizers) - for (let i = 0; i < keys.length; i++) { - const r = this.recognizers[keys[i]] - if (r.options.enable !== enable) { - r.set({ enable: enable }) + + setAll({ enable }) { + for (const recognizer of Object.values(this.recognizers)) { + if (recognizer.options.enable !== enable) { + recognizer.set({ enable }) } } }, - isEnabled(r) { - return this.recognizers[r] && this.recognizers[r].options.enable + isEnabled(gesture) { + return this.recognizers[gesture] && this.recognizers[gesture].options.enable } }, diff --git a/src/events.js b/src/events.js new file mode 100644 index 0000000..82670fc --- /dev/null +++ b/src/events.js @@ -0,0 +1,14 @@ +import Component from './component' +import { createProp } from './utils' + +const events = {} + +export const customEvents = (name) => name === undefined ? events : events[name] + +export const register = (event, options = {}) => { + options.event = event + events[event] = options + if (!(event in Component.props)) { + Component.props[event] = createProp() + } +} diff --git a/src/hammer-ssr.js b/src/hammer-ssr.js index 32b8f0e..b7cd66f 100644 --- a/src/hammer-ssr.js +++ b/src/hammer-ssr.js @@ -1,4 +1,4 @@ -module.exports = function Hammer { +export default function Hammer() { console.log(`[vue-touch] Your should never see this message. When you do, your code tried to call 'new Hammer(), but your app has included a stub for HammerJS, provided by vue-touch, instead of the actual HammerJS library. `) diff --git a/src/index.js b/src/index.js index 1718fed..f66e7f3 100644 --- a/src/index.js +++ b/src/index.js @@ -1,44 +1,32 @@ import Component from './component' -import { assign, config, customEvents } from './utils' +import { customEvents, register } from './events' -let installed = false +function plugin(Vue, options = {}) { + if (plugin.installed === true) return + plugin.installed = true + Component.config = plugin.config + Vue.component(options.name || 'v-touch', Component) +} -const vueTouch = { config, customEvents } +plugin.config = {} -// Plugin API -// ********* -vueTouch.install = function install(Vue, opts = {}) { - const name = opts.name || 'v-touch' - Vue.component(name, assign(Component, { name })) - installed = true -}.bind(vueTouch) +if (typeof window !== 'undefined' && window.Vue) { + window.Vue.use(plugin) +} -vueTouch.registerCustomEvent = function registerCustomEvent(event, options = {}) { - if (installed) { +export default plugin +export { + Component, + customEvents, +} +export const registerCustomEvent = (event, options) => { + if (plugin.installed) { console.warn(` [vue-touch]: Custom Event '${event}' couldn't be added to vue-touch. Custom Events have to be registered before installing the plugin. `) return } - options.event = event - customEvents[event] = options - Component.props[`${event}Options`] = { - type: Object, - default() { return {} } - } -}.bind(vueTouch) - -vueTouch.component = Component - -// Utilities -// ******** -if (typeof exports == "object") { - module.exports = vueTouch -} else if (typeof define == "function" && define.amd) { - define([], function(){ return vueTouch }) -} else if (typeof window !== 'undefined' && window.Vue) { - window.VueTouch = vueTouch - Vue.use(vueTouch) + register(event, options) } diff --git a/src/utils.js b/src/utils.js index 1d06a06..50473f5 100644 --- a/src/utils.js +++ b/src/utils.js @@ -1,22 +1,4 @@ -import Hammer from 'hammerjs' // used by guardDirections - -/** - * Tiny Object.assign replacement - * @param {Object} target Any type of object - * @param {Object} sources Any type of object - * @return {Object} Merged Object - */ -export function assign(target, ...sources) { - for (let i = 0; i < sources.length; i++) { - const source = sources[i] - const keys = Object.keys(source) - for (let i = 0; i < keys.length; i++) { - const key = keys[i] - target[key] = source[key] - } - } - return target -} +import Hammer from 'hammerjs' /** * Small helper method to generate prop options for all the @@ -26,30 +8,32 @@ export function assign(target, ...sources) { export function createProp() { return { type: Object, - default: function() { return {} } + default: function () { + return {} + } } } -export function capitalize (str) { +export function capitalize(str) { return str.charAt(0).toUpperCase() + str.slice(1) } /** * Directions that VueTouch understands. - * Will be tanslated to Hammer-style directions by guardDirections() + * Will be translated to Hammer-style directions by guardDirections() * @type {Array} */ export const directions = ['up', 'down', 'left', 'right', 'horizontal', 'vertical', 'all'] /** * Translates VueTouch direction names into Hammer Direction numbers. - * @param {Objects} options Hammer Options + * @param {Object} options Hammer Options * @return {Object} [Hammer Options] */ -export function guardDirections (options) { - var dir = options.direction +export function guardDirections(options) { + const dir = options.direction if (typeof dir === 'string') { - var hammerDirection = 'DIRECTION_' + dir.toUpperCase() + const hammerDirection = 'DIRECTION_' + dir.toUpperCase() if (directions.indexOf(dir) > -1 && Hammer.hasOwnProperty(hammerDirection)) { options.direction = Hammer[hammerDirection] } else { @@ -60,33 +44,23 @@ export function guardDirections (options) { } /** - * This pobject will contain global options for recognizers + * This object will contain global options for recognizers * see index.js -> vueTouch.config * @type {Object} */ -export const config = { - -} - -/** - * This object will contain recognizer options for custom events. - * see index.js -> registerCustomEvent - * @type {Object} - */ -export const customEvents = { - -} +export const config = {} /** * Names of all the builtin gestures of Hammer + * * @type {Array} */ export const gestures = [ - 'pan','panstart','panmove','panend','pancancel','panleft','panright','panup','pandown', - 'pinch','pinchstart','pinchmove','pinchend','pinchcancel','pinchin','pinchout', - 'press','pressup', - 'rotate','rotatestart','rotatemove','rotateend','rotatecancel', - 'swipe','swipeleft','swiperight','swipeup','swipedown', + 'pan', 'panstart', 'panmove', 'panend', 'pancancel', 'panleft', 'panright', 'panup', 'pandown', + 'pinch', 'pinchstart', 'pinchmove', 'pinchend', 'pinchcancel', 'pinchin', 'pinchout', + 'press', 'pressup', + 'rotate', 'rotatestart', 'rotatemove', 'rotateend', 'rotatecancel', + 'swipe', 'swipeleft', 'swiperight', 'swipeup', 'swipedown', 'tap' ] @@ -125,3 +99,7 @@ export const gestureMap = { swipedown: 'swipe', tap: 'tap' } + +export const normalizeGesture = name => gestureMap[name] + +export const objectHasArrayValues = value => typeof value === 'object' && Object.values(value).every(any => Array.isArray(any)) \ No newline at end of file diff --git a/test/e2e/index.js b/test/e2e/index.js index dba5980..649c0fa 100644 --- a/test/e2e/index.js +++ b/test/e2e/index.js @@ -1,12 +1,13 @@ -var server = require( '../../build/devserver.js') +const server = require('../../build/devserver.js') // initialize Testcafe -/* const createTestCafe = require('testcafe'); -const testCafe = await createTestCafe('localhost', 1337, 1338); -const runner = testcafe.createRunner(); +(async function () { + const createTestCafe = require('testcafe'); + const testCafe = await createTestCafe('localhost', 1337, 1338); + const runner = testcafe.createRunner(); -const failedCount = await runner - .src('tests/e2e/fixtures') - .browsers(['chrome']) - .run(); -*/ + const failedCount = await runner + .src('tests/e2e/fixtures') + .browsers(['chrome']) + .run(); +})(); diff --git a/test/unit/builtinEvents.js b/test/unit/builtinEvents.js index 21c1e76..e121da1 100644 --- a/test/unit/builtinEvents.js +++ b/test/unit/builtinEvents.js @@ -2,15 +2,12 @@ import test from 'ava' import sinon from 'sinon' import Vue from 'vue/dist/vue.common' import VueTouch from './helpers/vue-touch' -import Hammer from 'hammerjs' Vue.use(VueTouch) import { createFromTemplate, hasRecognizer, hasHandler, - isEnabled, isDisabled, - allEnabled, allDisabled } from './helpers' @@ -49,7 +46,7 @@ test('Uses global options from VueTouch.config', t => { test('Options prop overwrites global config', t => { const vt = createFromTemplate(` - + `) const options = vt.recognizers['swipe'].options t.true(options.threshold === 15) diff --git a/test/unit/customEvents.js b/test/unit/customEvents.js index fa6a21d..1c04b6a 100644 --- a/test/unit/customEvents.js +++ b/test/unit/customEvents.js @@ -1,10 +1,9 @@ import test from 'ava' import Vue from 'vue/dist/vue.common' -import VueTouch from './helpers/vue-touch' -import Hammer from 'hammerjs' +import VueTouch, { registerCustomEvent } from './helpers/vue-touch' -VueTouch.registerCustomEvent('doubletap', { +registerCustomEvent('doubletap', { type: 'tap', taps: 2 }) @@ -32,7 +31,7 @@ test('Custom Event local options work', t => { vt = createFromTemplate(` `) diff --git a/test/unit/enabledProp.js b/test/unit/enabledProp.js index eb5ee17..110c568 100644 --- a/test/unit/enabledProp.js +++ b/test/unit/enabledProp.js @@ -2,7 +2,6 @@ import test from 'ava' import Vue from 'vue/dist/vue.common' import VueTouch from './helpers/vue-touch' -import Hammer from 'hammerjs' Vue.use(VueTouch) @@ -12,8 +11,6 @@ import { allEnabled, allDisabled } from './helpers' -let vt - test('prop is true by default & events are enabled', t => { t.plan(2) diff --git a/test/unit/helpers/vue-touch.js b/test/unit/helpers/vue-touch.js index 6bcb85b..07f8bc5 100644 --- a/test/unit/helpers/vue-touch.js +++ b/test/unit/helpers/vue-touch.js @@ -1,7 +1,9 @@ -if (process.env.NODE_ENV === 'development') { - var vueTouch = require('../../../src/index.js') -} else { - var vueTouch = require('../../../dist/vue-touch.js') +import devVueTouch, { registerCustomEvent as devRegisterCustomEvent } from '../../../src/index' +import VueTouch, { registerCustomEvent as prodRegisterCustomEvent } from '../../../dist/vue-touch' + +function checkDev(a, b) { + return process.env.NODE_ENV === 'development' ? a : b } -export default vueTouch +export default checkDev(devVueTouch, VueTouch) +export const registerCustomEvent = checkDev(devRegisterCustomEvent, prodRegisterCustomEvent) diff --git a/test/unit/index.js b/test/unit/index.js index f56c107..801e20a 100644 --- a/test/unit/index.js +++ b/test/unit/index.js @@ -2,7 +2,6 @@ import test from 'ava' import Vue from 'vue/dist/vue.common' import VueTouch from './helpers/vue-touch' -import Hammer from 'hammerjs' import { createFromTemplate, createInstanceFromTemplate } from './helpers' diff --git a/test/unit/publicMethods.js b/test/unit/publicMethods.js index ded5afe..eeb3076 100644 --- a/test/unit/publicMethods.js +++ b/test/unit/publicMethods.js @@ -2,7 +2,6 @@ import test from 'ava' import Vue from 'vue/dist/vue.common' import VueTouch from './helpers/vue-touch' -import Hammer from 'hammerjs' Vue.use(VueTouch) diff --git a/test/unit/registerCustomEvent.js b/test/unit/registerCustomEvent.js index b198474..c26ce05 100644 --- a/test/unit/registerCustomEvent.js +++ b/test/unit/registerCustomEvent.js @@ -2,7 +2,7 @@ import test from 'ava' import sinon from 'sinon' import Vue from 'vue/dist/vue.common' -import VueTouch from './helpers/vue-touch' +import VueTouch, { registerCustomEvent } from './helpers/vue-touch' import Hammer from 'hammerjs' Vue.use(VueTouch, {hammer: Hammer}) @@ -12,7 +12,7 @@ test.before(t => { warn = sinon.stub(console, 'warn') }) test('registerCustomEvent works before Vue.use(), errors after', t => { - VueTouch.registerCustomEvent('doubletap', {taps: 2}) + registerCustomEvent('doubletap', {taps: 2}) // console.log(log.getCall(0).args[0]) const pattern = /\[vue\-touch\]: Custom Event/ t.true(pattern.test(warn.getCall(0).args[0]))