├── .editorconfig ├── .ember-cli ├── .eslintrc.js ├── .gitignore ├── .npmignore ├── .travis.yml ├── .watchmanconfig ├── CHANGELOG.md ├── LICENSE.md ├── README.md ├── addon ├── .gitkeep ├── components │ ├── async-element.js │ ├── context-element.js │ ├── fast-action.js │ ├── fast-async.js │ ├── gesture-element.js │ └── slide-toggle.js ├── event_dispatcher.js ├── hammer-events.js ├── mixins │ └── recognizers.js ├── modifiers │ └── recognize-gesture.js ├── recognizers │ ├── double-tap.js │ ├── pan.js │ ├── pinch.js │ ├── press.js │ ├── rotate.js │ ├── single-tap.js │ ├── swipe.js │ ├── tap.js │ ├── vertical-pan.js │ └── vertical-swipe.js ├── registry-walker.js ├── services │ └── -gestures.js ├── styles │ └── app.css ├── templates │ └── components │ │ ├── fast-action.hbs │ │ ├── fast-async.hbs │ │ ├── gesture-element.hbs │ │ └── slide-toggle.hbs └── utils │ ├── is-mobile.js │ └── string │ ├── cap-first-letter.js │ ├── capitalize-word.js │ ├── capitalize-words.js │ ├── dasherized-to-camel.js │ ├── dasherized-to-words.js │ ├── strip-whitespace.js │ └── uncapitalize-word.js ├── app ├── .gitkeep ├── ember-gestures │ └── recognizers │ │ ├── pan.js │ │ ├── pinch.js │ │ ├── press.js │ │ ├── rotate.js │ │ ├── swipe.js │ │ ├── tap.js │ │ ├── vertical-pan.js │ │ └── vertical-swipe.js ├── event_dispatcher.js ├── instance-initializers │ └── ember-gestures.js ├── modifiers │ └── recognize-gesture.js └── services │ └── -gestures.js ├── blueprints ├── doubletap-recognizer │ ├── files │ │ └── __root__ │ │ │ └── ember-gestures │ │ │ └── recognizers │ │ │ ├── double-tap.js │ │ │ └── tap.js │ └── index.js ├── ember-gestures │ └── index.js ├── gesture-components │ ├── files │ │ └── __root__ │ │ │ └── components │ │ │ ├── async-element.js │ │ │ ├── context-element.js │ │ │ ├── fast-action.js │ │ │ ├── fast-async.js │ │ │ ├── gesture-element.js │ │ │ └── slide-toggle.js │ └── index.js └── recognizer │ ├── files │ └── __root__ │ │ └── ember-gestures │ │ └── recognizers │ │ └── __name__.js │ └── index.js ├── config ├── dependency-lint.js ├── ember-cli-toolbelts.json ├── ember-try.js └── environment.js ├── ember-cli-build.js ├── index.js ├── package.json ├── testem.js ├── tests ├── acceptance │ └── index-test.js ├── dummy │ ├── app │ │ ├── app.js │ │ ├── components │ │ │ ├── async-element.js │ │ │ ├── context-element.js │ │ │ ├── fast-action.js │ │ │ ├── fast-async.js │ │ │ ├── gesture-element.js │ │ │ ├── recognizes-single-and-double-tap.js │ │ │ ├── single-and-double-tap-modifier.js │ │ │ ├── slide-toggle.js │ │ │ └── traditional-click.js │ │ ├── ember-gestures │ │ │ └── recognizers │ │ │ │ ├── double-tap.js │ │ │ │ └── tap.js │ │ ├── index.html │ │ ├── resolver.js │ │ ├── router.js │ │ ├── routes │ │ │ ├── modtaps.js │ │ │ └── taps.js │ │ ├── styles │ │ │ └── app.css │ │ └── templates │ │ │ ├── application.hbs │ │ │ ├── components │ │ │ ├── recognizes-single-and-double-tap.hbs │ │ │ ├── single-and-double-tap-modifier.hbs │ │ │ └── traditional-click.hbs │ │ │ ├── index.hbs │ │ │ ├── modtaps.hbs │ │ │ └── taps.hbs │ ├── config │ │ ├── environment.js │ │ └── targets.js │ └── public │ │ ├── interactive.svg │ │ └── robots.txt ├── helpers │ └── .gitkeep ├── index.html ├── integration │ ├── .gitkeep │ └── components │ │ ├── fast-action-test.js │ │ └── fast-async-test.js ├── test-helper.js └── unit │ └── utils │ └── string-test.js ├── vendor └── .gitkeep └── yarn.lock /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain consistent 2 | # coding styles between different editors and IDEs 3 | # editorconfig.org 4 | 5 | root = true 6 | 7 | 8 | [*] 9 | end_of_line = lf 10 | charset = utf-8 11 | trim_trailing_whitespace = true 12 | insert_final_newline = true 13 | indent_style = space 14 | indent_size = 2 15 | 16 | [*.hbs] 17 | insert_final_newline = false 18 | 19 | [*.{diff,md}] 20 | trim_trailing_whitespace = false 21 | -------------------------------------------------------------------------------- /.ember-cli: -------------------------------------------------------------------------------- 1 | { 2 | /** 3 | Ember CLI sends analytics information by default. The data is completely 4 | anonymous, but there are times when you might want to disable this behavior. 5 | 6 | Setting `disableAnalytics` to true will prevent any data from being sent. 7 | */ 8 | "disableAnalytics": false 9 | } 10 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | parserOptions: { 4 | ecmaVersion: 2017, 5 | sourceType: 'module' 6 | }, 7 | plugins: [ 8 | 'ember' 9 | ], 10 | extends: [ 11 | 'eslint:recommended', 12 | 'plugin:ember/recommended' 13 | ], 14 | env: { 15 | browser: true 16 | }, 17 | rules: { 18 | }, 19 | globals: { 20 | "Hammer": true 21 | }, 22 | overrides: [ 23 | // node files 24 | { 25 | files: [ 26 | 'ember-cli-build.js', 27 | 'index.js', 28 | 'testem.js', 29 | 'config/**/*.js', 30 | 'tests/dummy/config/**/*.js' 31 | ], 32 | excludedFiles: [ 33 | 'addon/**', 34 | 'addon-test-support/**', 35 | 'app/**', 36 | 'tests/dummy/app/**' 37 | ], 38 | parserOptions: { 39 | sourceType: 'script', 40 | ecmaVersion: 2015 41 | }, 42 | env: { 43 | browser: false, 44 | node: true 45 | }, 46 | plugins: ['node'], 47 | rules: Object.assign({}, require('eslint-plugin-node').configs.recommended.rules, { 48 | // add your custom rules and overrides for node files here 49 | }) 50 | } 51 | ] 52 | }; 53 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # compiled output 4 | /dist 5 | /tmp 6 | 7 | # dependencies 8 | /node_modules 9 | /bower_components 10 | 11 | # misc 12 | /.sass-cache 13 | /connect.lock 14 | /coverage/* 15 | /libpeerconnection.log 16 | npm-debug.log* 17 | yarn-error.log 18 | testem.log 19 | 20 | # ember-try 21 | .node_modules.ember-try/ 22 | bower.json.ember-try 23 | package.json.ember-try 24 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | /bower_components 2 | /config/ember-try.js 3 | /dist 4 | /tests 5 | /tmp 6 | **/.gitkeep 7 | .bowerrc 8 | .editorconfig 9 | .ember-cli 10 | .eslintrc.js 11 | .gitignore 12 | .watchmanconfig 13 | .travis.yml 14 | bower.json 15 | ember-cli-build.js 16 | testem.js 17 | 18 | # ember-try 19 | .node_modules.ember-try/ 20 | bower.json.ember-try 21 | package.json.ember-try 22 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | --- 2 | language: node_js 3 | node_js: 4 | # we recommend testing addons with the same minimum supported node version as Ember CLI 5 | # so that your addon works for all apps 6 | - "10" 7 | 8 | sudo: false 9 | dist: trusty 10 | 11 | addons: 12 | chrome: stable 13 | 14 | cache: 15 | yarn: true 16 | 17 | env: 18 | global: 19 | # See https://git.io/vdao3 for details. 20 | - JOBS=1 21 | matrix: 22 | # we recommend new addons test the current and previous LTS 23 | # as well as latest stable release (bonus points to beta/canary) 24 | - EMBER_TRY_SCENARIO=ember-lts-2.12 25 | - EMBER_TRY_SCENARIO=ember-lts-2.16 26 | - EMBER_TRY_SCENARIO=ember-lts-2.18 27 | - EMBER_TRY_SCENARIO=ember-lts-3.8 28 | - EMBER_TRY_SCENARIO=ember-lts-3.12 29 | - EMBER_TRY_SCENARIO=ember-release 30 | - EMBER_TRY_SCENARIO=ember-beta 31 | - EMBER_TRY_SCENARIO=ember-canary 32 | - EMBER_TRY_SCENARIO=ember-default 33 | 34 | matrix: 35 | fast_finish: true 36 | allow_failures: 37 | - env: EMBER_TRY_SCENARIO=ember-canary 38 | - env: EMBER_TRY_SCENARIO=ember-beta 39 | 40 | before_install: 41 | - curl -o- -L https://yarnpkg.com/install.sh | bash 42 | - export PATH=$HOME/.yarn/bin:$PATH 43 | 44 | install: 45 | - yarn install --no-lockfile --non-interactive 46 | 47 | script: 48 | - npm run lint:js 49 | # Usually, it's ok to finish the test scenario without reverting 50 | # to the addon's original dependency state, skipping "cleanup". 51 | - node_modules/.bin/ember try:one $EMBER_TRY_SCENARIO --skip-cleanup 52 | -------------------------------------------------------------------------------- /.watchmanconfig: -------------------------------------------------------------------------------- 1 | { 2 | "ignore_dirs": ["tmp", "dist"] 3 | } 4 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | Changelog 2 | --------- 3 | 4 | ## v2.0.1 5 | 6 | - [Dep] Set minimum node version to 10.x within package.json and travis.yml *by [html-next](https://github.com/html-next)* 7 | 8 | ## v2.0.0 9 | 10 | - [#129](https://github.com/html-next/ember-gestures/pull/129) [BREAKING] Set minimum node version to 8.x and bump to most recent qunit and ember-cli-babel 7 *by [Lupestro](https://github.com/lupestro)* 11 | - [#128](https://github.com/html-next/ember-gestures/pull/128) Added {recognize-gesture} modifier as alternative to mixin. Modifier requires Ember 2.18 or later to use. *by [Lupestro](https://github.com/lupestro)* 12 | 13 | ## v1.1.3 14 | 15 | - update yarn.lock *by [html-next](https://github.com/html-next)* 16 | 17 | ## v1.1.2 18 | 19 | ### Pull Requests 20 | 21 | - [#124](https://github.com/html-next/ember-gestures/pull/124) fixed a couple cases where the addon didn't work in an engine *by [Conrad Lippert-Zajaczkowski](https://github.com/PrinceCornNM)* 22 | 23 | - [#121](https://github.com/html-next/ember-gestures/pull/121) chore(deps): replace deprecated internal copy with ember-copy *by [Edward Huang](https://github.com/ilovethebends)* 24 | 25 | ## v1.1.1 26 | 27 | ### Pull Requests 28 | 29 | - [#119](https://github.com/html-next/ember-gestures/pull/119) Fix wrong resolution of rootElement *by [Christian Davatz](https://github.com/crixx)* 30 | 31 | ## v1.1.0 32 | 33 | ### Pull Requests 34 | 35 | - [#114](https://github.com/html-next/ember-gestures/pull/114) Remove jQuery *by [Simon Ihmig](https://github.com/simonihmig)* 36 | 37 | ## v1.0.0 38 | 39 | ### Pull Requests 40 | 41 | - [#111](https://github.com/html-next/ember-gestures/pull/111) Ember 3.1 *by [html-next](https://github.com/html-next)* 42 | 43 | ## 0.1.0 44 | 45 | - [FEAT] Recognizer Blueprints 46 | - [FEAT] Recognizer Mixin 47 | - [FEAT] Default Recognizers 48 | - [DEP] Hammertime 0.0.1 49 | - [DEP] HammerJS 2.0.5 50 | - [FEAT] Hammertime mixin 51 | - [FEAT] Velocity Mixin 52 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Ember Gestures [![npm version](https://badge.fury.io/js/ember-gestures.svg)](http://badge.fury.io/js/ember-gestures) 2 | ============== 3 | 4 | [![Build Status](https://travis-ci.org/html-next/ember-gestures.svg?branch=master)](https://travis-ci.org/html-next/ember-gestures) 5 | [![Ember Observer Score](http://emberobserver.com/badges/ember-gestures.svg)](http://emberobserver.com/addons/ember-gestures) 6 | 7 | Ember Gestures provides an easy way to use gestures by making it simple to define and use [HammerJS](https://github.com/hammerjs/hammer.js) managers 8 | and recognizers throughout your app. 9 | 10 | When installed via `ember install ember-gestures`, it will additionally install [ember-hammertime](https://github.com/runspired/ember-hammertime) 11 | to use for "fastclick" support. 12 | 13 | ## Support, Questions, Collaboration 14 | 15 | Join the [Ember community on Discord](https://discord.gg/zT3asNS) 16 | 17 | ### Status 18 | 19 | [Changelog](./CHANGELOG.md) 20 | 21 | [![dependencies](https://david-dm.org/html-next/ember-gestures.svg)](https://david-dm.org/html-next/ember-gestures) 22 | [![devDependency Status](https://david-dm.org/html-next/ember-gestures/dev-status.svg)](https://david-dm.org/runspired/ember-gestures#info=devDependencies) 23 | 24 | ## Usage 25 | 26 | `ember install ember-gestures` 27 | 28 | This will run the default blueprint which additionally installs `HammerJS`. 29 | 30 | 31 | #### Recognizers and Managers 32 | 33 | A `Recognizer` detects a gesture on a target element by listening to received touch, mouse, and pointer events 34 | and passing through a series of rules to determine whether it's gesture is occurring. 35 | 36 | `Recognizer`s are registered with a `Manager`, which emits the recognized gestures for consumption by your app. 37 | This addon does the grunt work of creating Managers and wiring up Recognizers for you. All you need to do 38 | to use gestures is tell your app where you want a Manager to be created and what Recognizers it should have. 39 | 40 | Example 41 | ```js 42 | import RecognizerMixin from 'ember-gestures/mixins/recognizers'; 43 | import Ember from 'ember'; 44 | 45 | const { 46 | Component 47 | } = Ember; 48 | 49 | export default Component.extend(RecognizerMixin, { 50 | recognizers: 'pan tap press' 51 | }); 52 | ``` 53 | 54 | The component would create a new Manager and add the recognizers for [pan](./addon/recognizers/pan.js), 55 | [tap](./addon/recognizers/tap.js), and [press](./addon/recognizers/press.js). 56 | 57 | The component would recognize gestures based on events originating on it or it's child elements. 58 | The corresponding gesture would be capable of being handled anywhere in the DOM branch in which the component 59 | exists. 60 | 61 | Example Dom Tree 62 | ``` 63 | body 64 | |_ div 1 65 | |_ component A 66 | |_ div 2 67 | |_ component B (with recognizers) 68 | |_ div 3 69 | ``` 70 | 71 | In the above example, input events originating on `component B` or `div 3` would be passed to the recognizers. 72 | The emitted gestures are triggered on the element which began the gesture (also `component B` or `div 3` and 73 | bubble up to `body`. This means that component A would also be capable of handling gestures emitted by the 74 | manager on component B. If `div 3` had an action handler that utilized a gesture, it too would be able to use 75 | gestures emitted by component B if they had begun on or within `div 3`. 76 | 77 | This means you should be strategic about where you put your components with Managers. You don't need a lot of 78 | Managers, you just need them placed strategically. You could even put one at the base of your app, but be 79 | warned `pinch` `rotate` `pan` and `swipe` can break scrolling behavior if not placed correctly. 80 | 81 | ##### Note 82 | 83 | `pan` and `swipe` are horizontal only (configured this way to avoid breaking vertical scroll). 84 | `vertical-pan` and `vertical-swipe` are vertical only (configured this way to avoid breaking horizontal scroll). 85 | 86 | ##### Modifier 87 | As an alternative to using the `RecognizerMixin`, you may use the `{{recognize-gesture}}` modifier. This is particularly useful when you are applying the recognizer to an element within the handlebars of your component, rather than to the root element of a component, and vital when your component _has_ no root element, as when you use Glimmer components or components with `tagName=""`. 88 | 89 | Example 90 | ```hbs 91 |
92 | ``` 93 | Gestures to recognize are supplied using positional parameters on the modifier. Hammer manager options can be supplied using named parameters on the modifier. 94 | 95 | When using `{{ember-on-modifier}}` with these events, you must use the real DOM event names, which are lowercase without hyphens. So your event for "double-tap" would be "doubletap". 96 | 97 | Also, if you are using objects derived from `EmberObject`, like Ember components, avoid using action names that are the camel-case form of the gesture names, like `doubleTap`, as these are also the names for the component's generated event listeners, and may get unexpectedly triggered when an event bubbles up to the component as well as when your `{{on}}` action fires on the element. (This is one more reason to migrate to Glimmer components, which don't automatically add event handlers to your namespace.) 98 | 99 | The modifier can only be used with Ember 2.18 or later. 100 | 101 | #### Using Gestures 102 | 103 | Using gestures emitted by Hammer recognizers with Ember is almost like using any other event with Ember. 104 | Your recognizers will be configured to emit `Events`, so to consume a gesture, you just need to add an 105 | event handler to your component. The handler's name just needs to match the camelCase version of a gesture 106 | event. 107 | 108 | Example 109 | ```js 110 | import Ember from 'ember'; 111 | 112 | const { 113 | Component 114 | } = Ember; 115 | 116 | export default Component.extend({ 117 | panStart(e) { 118 | // do something with the event 119 | } 120 | }); 121 | ``` 122 | 123 | Gesture events bubble through the DOM, so you can use them with actions as well. 124 | 125 | ```hbs 126 |
127 | ``` 128 | 129 | #### Create A Recognizer 130 | 131 | `ember g recognizer ` 132 | 133 | This will generate the file `ember-gestures/recognizers/name.js`. 134 | Once you've filled out the recognizer (see [./addon/recognizers/](./addon/recognizers/) for examples), 135 | it will be available to use in your app just like the default recognizers. 136 | 137 | ##### Note 138 | 139 | Although we have provided an example for `double-tap` and `single-tap` in the `addon/recognizers` folder, they 140 | are for demo purposes only and do not ship with the actual addon because they are non-standard. 141 | If you'd like to enable these recognizers for your own app; for instance `double-tap`; execute: 142 | 143 | `ember g recognizer double-tap` 144 | 145 | An `.app/ember-gestures/recognizers/double-tap.js` file will be created. Inspect its contents and replace 146 | it with the example `double-tap.js` from this addon's codebase. 147 | 148 | #### Increase the size of touchable areas to improve user experience on small controls 149 | 150 | Sometimes smaller buttons or critical buttons need a larger capture area than their visible area. 151 | You can increase the area that recognizes touch events for a specific button with a little bit of CSS. 152 | 153 | [Example Gist](https://gist.github.com/runspired/506f39a4abb2be48d63f) 154 | 155 | 156 | ### Components 157 | 158 | Components available by default include `context-element`, `fast-action`, and `fast-async`. 159 | 160 | 161 | ### Testing 162 | 163 | 164 | The jQuery events you need to trigger are the Hammer variant, meaning it is entirely lowercase `swiperight`, `panup`. 165 | 166 | jQuery events come with baggage, and using the `trigger` helper executes handlers in a different order than they 167 | would otherwise execute, and in some situations will cause a handler to execute twice. If you are experiencing 168 | issues with testing gesture events, try creating your own `trigger` helper that uses native APIs instead of jQuery 169 | to trigger the event. 170 | 171 | ## Tips and Tricks 172 | 173 | Don't add recognizers to components created within an `{{#each}}` loop. Use a recognizer at the base of the 174 | `each` instead. 175 | 176 | 177 | ## Contributing 178 | 179 | Contributions are very welcome. 180 | 181 | When making a PR try to use the following conventions: 182 | 183 | **Commit Messages:** 184 | 185 | `type(shortname): action based description` 186 | 187 | Examples: 188 | 189 | - chore(deps): bump deps in package.json and bower.json 190 | - docs(component): document the `fast-action` component 191 | 192 | **Branch Naming:** 193 | 194 | `type/short-description` 195 | 196 | Examples: 197 | 198 | - chore/bump-deps 199 | - docs/fast-action-component 200 | 201 | 202 | -------------------------------------------------------------------------------- /addon/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/html-next/ember-gestures/561cacc01d65e37e0c4bb8d544e7a06c14536f15/addon/.gitkeep -------------------------------------------------------------------------------- /addon/components/async-element.js: -------------------------------------------------------------------------------- 1 | import { get, observer, computed } from '@ember/object'; 2 | import GestureArea from './gesture-element'; 3 | 4 | 5 | /**! 6 | * 7 | * Provides the ability to easily build a 8 | * gesture-ful async-button implementation 9 | * 10 | */ 11 | export default GestureArea.extend({ 12 | 13 | classNameBindings: ['actionState'], 14 | actionState: 'default', 15 | 16 | isPending: computed('actionState', function() { 17 | return this.get('actionState') === 'pending'; 18 | }), 19 | 20 | _getParams: function(actionName) { 21 | let actionArguments = this._super(actionName); 22 | 23 | let callbackHandler = (promise) => { 24 | this.set('promise', promise); 25 | this.set('actionState', 'pending'); 26 | }; 27 | 28 | actionArguments.splice(1, 0, callbackHandler); 29 | return actionArguments; 30 | }, 31 | 32 | __observePromiseState: observer('promise', function promiseTheComponentState() { 33 | get(this, 'promise') 34 | .then(() => { 35 | if (!this.isDestroyed) { 36 | this.set('actionState', 'fulfilled'); 37 | }}) 38 | .catch(() => { 39 | if (!this.isDestroyed) { 40 | this.set('actionState', 'rejected'); 41 | }}); 42 | }) 43 | 44 | 45 | 46 | }); 47 | -------------------------------------------------------------------------------- /addon/components/context-element.js: -------------------------------------------------------------------------------- 1 | import GestureArea from './gesture-element'; 2 | 3 | export default GestureArea.extend({ 4 | 5 | _getParams: function(actionName) { 6 | let actionArguments = this._super(actionName); 7 | actionArguments.splice(1, 0, this.element); 8 | return actionArguments; 9 | } 10 | 11 | }); 12 | -------------------------------------------------------------------------------- /addon/components/fast-action.js: -------------------------------------------------------------------------------- 1 | import Component from '@ember/component'; 2 | import layout from '../templates/components/fast-action'; 3 | import { htmlSafe } from "@ember/string"; 4 | 5 | export default Component.extend({ 6 | layout: layout, 7 | tagName: 'button', 8 | attributeBindings: ['style', 'type'], 9 | style: htmlSafe('touch-action: manipulation; -ms-touch-action: manipulation;'), 10 | type: 'button', 11 | text: '', 12 | action: '', 13 | context: '', 14 | 15 | click: function() { 16 | this.sendAction('action', this.get('context')); // eslint-disable-line 17 | } 18 | 19 | }); 20 | -------------------------------------------------------------------------------- /addon/components/fast-async.js: -------------------------------------------------------------------------------- 1 | import layout from '../templates/components/fast-async'; 2 | import asyncAction from './async-element'; 3 | import { htmlSafe } from "@ember/string"; 4 | 5 | export default asyncAction.extend({ 6 | layout: layout, 7 | tagName: 'button', 8 | attributeBindings: ['style', 'disabled', 'type'], 9 | style: htmlSafe('touch-action: manipulation; -ms-touch-action: manipulation;'), 10 | type: 'button', 11 | text: '', 12 | context: null 13 | }); 14 | 15 | -------------------------------------------------------------------------------- /addon/components/gesture-element.js: -------------------------------------------------------------------------------- 1 | import { typeOf } from '@ember/utils'; 2 | import Component from '@ember/component'; 3 | import layout from '../templates/components/gesture-element'; 4 | import RecognizerMixin from '../mixins/recognizers'; 5 | import toCamel from '../utils/string/dasherized-to-camel'; 6 | 7 | 8 | function makeActionHandler(event, action) { 9 | 10 | return function actionHandler() { 11 | 12 | var target = this.get('target'); 13 | let args; 14 | 15 | if (target && target.send) { 16 | args = this._getParams(action); 17 | target.send.apply(this, args); 18 | } else { 19 | args = this._getParams(event + 'Action'); 20 | this.sendAction.apply(this, args); // eslint-disable-line 21 | } 22 | 23 | }; 24 | 25 | } 26 | 27 | /**! 28 | * 29 | * Provides the ability to easily build a 30 | * gesture-ful async-button implementation 31 | * 32 | */ 33 | export default Component.extend(RecognizerMixin, { 34 | 35 | layout: layout, 36 | 37 | context: '', 38 | _getParams: function(actionName) { 39 | let context = this.get('context'); 40 | return [actionName, context]; 41 | }, 42 | 43 | init() { 44 | 45 | this._super(); 46 | 47 | var v; 48 | var attrs = this.get('attrs') || this; 49 | 50 | for (var key in attrs) { 51 | 52 | if (attrs.hasOwnProperty(key)) { 53 | v = attrs[key]; 54 | if (v === 'toString') { 55 | continue; 56 | } // ignore useless items 57 | if (typeOf(v) === 'function') { 58 | continue; 59 | } 60 | 61 | //setup listener for key 62 | if (key.indexOf('on-') === 0) { 63 | let event = toCamel(key.substr(3)); 64 | let action = attrs[key]; 65 | 66 | this.set(event + 'Action', action); 67 | 68 | this.set(event, makeActionHandler(event, action)); 69 | } 70 | 71 | } 72 | } 73 | } 74 | 75 | 76 | 77 | }); 78 | -------------------------------------------------------------------------------- /addon/components/slide-toggle.js: -------------------------------------------------------------------------------- 1 | import { alias } from '@ember/object/computed'; 2 | import { run } from '@ember/runloop'; 3 | import { copy } from 'ember-copy'; 4 | import Component from '@ember/component'; 5 | import VelocityMixin from 'ember-velocity-mixin'; 6 | import RecognizerMixin from '../mixins/recognizers'; 7 | import layout from '../templates/components/slide-toggle'; 8 | 9 | const { 10 | throttle, 11 | debounce 12 | } = run; 13 | 14 | export default Component.extend(RecognizerMixin, VelocityMixin, { 15 | tagName: 'slide-toggle', 16 | classNameBindings: ['_value:isOn:isOff'], 17 | 18 | layout, 19 | 20 | recognizers: 'pan tap press', 21 | 22 | unidirectional: false, 23 | value: false, 24 | _value: false, 25 | target: null, 26 | 27 | __updateCSS(value) { 28 | if (!value) { 29 | this.$('.slideToggleButton').removeAttr('style'); 30 | } else { 31 | 32 | var element = this.$('.slideToggleButton').get(0); 33 | var maxMovement = element.clientWidth * 0.75; 34 | 35 | if (Math.abs(value) > maxMovement) { 36 | value = (value < 0) ? 0 : maxMovement; 37 | } else if (value < 0) { 38 | value = maxMovement + value; 39 | } 40 | 41 | // TODO can this happen via this.css ? 42 | this.animate(element, {translateX: value + 'px'}, {duration: 1}); 43 | } 44 | 45 | }, 46 | 47 | 'on-toggle': null, 48 | _defaultAction: 'slideToggleChange', 49 | 50 | _notify() { 51 | let unidirectional = this.get('unidirectional'); 52 | let action = this.get('on-toggle'); 53 | let defaultAction = this.get('_defaultAction'); 54 | let target = this.get('target'); 55 | let context = this.get('context'); 56 | 57 | if (unidirectional || action) { 58 | 59 | if (target && target.send) { 60 | target.send(action, context); 61 | } else { 62 | action = action ? 'on-toggle' : defaultAction; 63 | this.sendAction(action, context); // eslint-disable-line 64 | } 65 | 66 | } 67 | 68 | }, 69 | 70 | _trigger(dX) { 71 | 72 | this.__updateCSS(); 73 | 74 | if ((dX && dX > 8) || (!dX && dX !== 0)) { 75 | this.toggleProperty('_value'); 76 | this._notify(); 77 | } 78 | return false; 79 | }, 80 | 81 | pan(e) { 82 | var allowPanRight = !this.get('_value'); 83 | var dX = e.originalEvent.gesture.deltaX; 84 | 85 | if (allowPanRight) { 86 | if (dX < 0) { dX = 0; } 87 | } else { 88 | if (dX > 0) { dX = 0; } 89 | } 90 | 91 | throttle(this, this.__updateCSS, dX, 2); 92 | //schedule the dismissal 93 | debounce(this, this._trigger, Math.abs(dX), 250); 94 | return false; 95 | }, 96 | 97 | tap() { 98 | return this._trigger(); 99 | }, 100 | 101 | press() { 102 | return this._trigger(); 103 | }, 104 | 105 | init() { 106 | this._super(); 107 | 108 | var value = this.get('value'); 109 | 110 | // setup unidirection flow if desired 111 | if (!this.get('unidirectional')) { 112 | this.set('_value', alias('value')); 113 | } else { 114 | this.set('_value', copy(value, true)); 115 | } 116 | 117 | } 118 | 119 | }); 120 | -------------------------------------------------------------------------------- /addon/event_dispatcher.js: -------------------------------------------------------------------------------- 1 | import { getOwner } from '@ember/application'; 2 | import { merge, assign as _assign } from '@ember/polyfills'; 3 | import { get, set } from '@ember/object'; 4 | import Ember from 'ember'; 5 | import defaultHammerEvents from './hammer-events'; 6 | import dasherizedToCamel from './utils/string/dasherized-to-camel'; 7 | import mobileDetection from './utils/is-mobile'; 8 | import { isNone } from '@ember/utils'; 9 | 10 | const { 11 | EventDispatcher, 12 | } = Ember; 13 | 14 | 15 | const eventEndings = { 16 | pan: ['Start', 'Move', 'End', 'Cancel', 'Left', 'Right', 'Up', 'Down'], 17 | pinch: ['Start', 'Move', 'End', 'Cancel', 'In', 'Out'], 18 | press: ['Up'], 19 | rotate: ['Start', 'Move', 'End', 'Cancel'], 20 | swipe: ['Left', 'Right', 'Up', 'Down'], 21 | tap: [] 22 | }; 23 | 24 | const assign = _assign || merge; 25 | 26 | const notFocusableTypes = ['submit', 'file', 'button', 'hidden', 'reset', 'range', 'radio', 'image', 'checkbox']; 27 | 28 | const fastFocusEvents = ['click', 'touchend']; 29 | 30 | export default EventDispatcher.extend({ 31 | 32 | /** 33 | Whether or not to cache handler paths on the element 34 | when `useCapture` is also true. 35 | This needs to be replaced by a feature flag. 36 | @private 37 | @type {boolean} 38 | */ 39 | useFastPaths: false, 40 | useCapture: false, 41 | 42 | _gestures: null, 43 | _initializeGestures() { 44 | const list = getModuleList(); 45 | const events = assign({}, defaultHammerEvents); 46 | 47 | list.forEach((name) => { 48 | const recognizer = getOwner(this).factoryFor('ember-gesture:recognizers/' + name); 49 | 50 | if (recognizer) { 51 | addEvent(events, recognizer.class.recognizer, recognizer.class.eventName || name); 52 | } 53 | }); 54 | 55 | // add them to the event dispatcher 56 | this.set('_gestures', events); 57 | 58 | }, 59 | 60 | _fastFocus() { 61 | let rootElementSelector = get(this, 'rootElement'); 62 | let rootElement; 63 | if (rootElementSelector.nodeType) { 64 | rootElement = rootElementSelector; 65 | } else { 66 | rootElement = document.querySelector(rootElementSelector); 67 | } 68 | 69 | fastFocusEvents.forEach((event) => { 70 | let listener = this._gestureEvents[event] = (e) => { 71 | if (mobileDetection.is()) { 72 | let element = e.currentTarget; 73 | let target = e.target; 74 | 75 | /* 76 | If the click was on an input element that needs to be able to focus, recast 77 | the click as a "focus" event. 78 | This fixes tap events on mobile where keyboardShrinksView or similar is true. 79 | Such devices depend on the ghost click to trigger focus, but the ghost click 80 | will never reach the element. 81 | */ 82 | 83 | //fastfocus 84 | if (element.tagName === 'TEXTAREA' || (element.tagName === 'INPUT' && notFocusableTypes.indexOf(element.getAttribute('type')) === -1)) { 85 | element.focus(); 86 | 87 | } else if (target.tagName === 'TEXTAREA' || (target.tagName === 'INPUT' && notFocusableTypes.indexOf(target.getAttribute('type')) === -1)) { 88 | target.focus(); 89 | } 90 | } 91 | }; 92 | rootElement.addEventListener(event, listener); 93 | }); 94 | }, 95 | 96 | willDestroy() { 97 | let rootElementSelector = get(this, 'rootElement'); 98 | let rootElement; 99 | if (rootElementSelector.nodeType) { 100 | rootElement = rootElementSelector; 101 | } else { 102 | rootElement = document.querySelector(rootElementSelector); 103 | } 104 | 105 | if (rootElement) { 106 | for (let event in this._gestureEvents) { 107 | rootElement.removeEventListener(event, this._gestureEvents[event]); 108 | delete this._gestureEvents[event]; 109 | } 110 | } 111 | this._super(...arguments); 112 | }, 113 | 114 | _finalEvents: null, 115 | 116 | init() { 117 | this._gestureEvents = Object.create(null); 118 | this._super(...arguments); 119 | }, 120 | 121 | setup(addedEvents, rootElement) { 122 | this._initializeGestures(); 123 | let events = assign({}, get(this, 'events')); 124 | 125 | // remove undesirable events from Ember's Eventing 126 | if (this.get('removeTracking')) { 127 | events.touchstart = null; 128 | events.touchmove = null; 129 | events.touchcancel = null; 130 | events.touchend = null; 131 | 132 | events.mousedown = null; 133 | events.mouseenter = null; 134 | events.mousemove = null; 135 | events.mouseleave = null; 136 | events.mouseup = null; 137 | 138 | events.drag = null; 139 | events.dragend = null; 140 | events.dragenter = null; 141 | events.dragleave = null; 142 | events.dragover = null; 143 | events.dragstart = null; 144 | events.drop = null; 145 | 146 | events.dblclick = null; 147 | } 148 | 149 | // delete unwanted events 150 | for (let event in events) { 151 | if (events.hasOwnProperty(event) && !events[event]) { 152 | delete events[event]; 153 | } 154 | } 155 | 156 | // override default events 157 | this.set('events', events); 158 | 159 | // add our events to addition events 160 | let additionalEvents = assign({}, addedEvents); 161 | additionalEvents = assign(additionalEvents, this.get('_gestures')); 162 | 163 | if (!isNone(rootElement)) { 164 | set(this, 'rootElement', rootElement); 165 | } 166 | 167 | this._fastFocus(); 168 | 169 | return this._super(additionalEvents, rootElement); 170 | } 171 | 172 | }); 173 | 174 | function addEvent(hash, gesture, name) { 175 | const eventName = dasherizedToCamel(name); 176 | const eventBase = eventName.toLowerCase(); 177 | const endings = eventEndings[gesture]; 178 | 179 | hash[eventBase] = eventName; 180 | endings.forEach((ending) => { 181 | const event = eventName + ending; 182 | 183 | hash[event.toLowerCase()] = event; 184 | }); 185 | } 186 | 187 | // this function can be replaced in ember 1.13 with a private api 188 | // and in ember 2.0 with a potentially public api matching 1.13's 189 | // private api. This is a stop gap for pre-ember 1.13 190 | function getModuleList() { 191 | /* global requirejs */ 192 | const list = []; 193 | 194 | for (let moduleName in requirejs.entries) { 195 | if (Object.prototype.hasOwnProperty.call(requirejs.entries, moduleName)) { 196 | const parts = moduleName.match(/ember-gestures\/recognizers\/(.*)/); 197 | 198 | if (parts && parts[1].indexOf('jshint') === -1) { 199 | list.push(parts[1]); 200 | } 201 | } 202 | } 203 | return list; 204 | } 205 | -------------------------------------------------------------------------------- /addon/hammer-events.js: -------------------------------------------------------------------------------- 1 | export default { 2 | 3 | pan: 'pan', 4 | panstart: 'panStart', 5 | panmove: 'panMove', 6 | panend: 'panEnd', 7 | pancancel: 'panCancel', 8 | panleft: 'panLeft', 9 | panright: 'panRight', 10 | panup: 'panUp', 11 | pandown: 'panDown', 12 | 13 | pinch: 'pinch', 14 | pinchstart: 'pinchStart', 15 | pinchmove: 'pinchMove', 16 | pinchend: 'pinchEnd', 17 | pinchcancel: 'pinchCancel', 18 | pinchin: 'pinchIn', 19 | pinchout: 'pinchOut', 20 | 21 | press: 'press', 22 | pressup: 'pressUp', 23 | 24 | rotate: 'rotate', 25 | rotatestart: 'rotateStart', 26 | rotatemove: 'rotateMove', 27 | rotateend: 'rotateEnd', 28 | rotatecancel: 'rotateCancel', 29 | 30 | swipe: 'swipe', 31 | swipeleft: 'swipeLeft', 32 | swiperight: 'swipeRight', 33 | swipeup: 'swipeUp', 34 | swipedown: 'swipeDown', 35 | 36 | tap: 'tap' 37 | 38 | }; 39 | -------------------------------------------------------------------------------- /addon/mixins/recognizers.js: -------------------------------------------------------------------------------- 1 | import { inject as service } from '@ember/service'; 2 | import { computed } from '@ember/object'; 3 | import { getOwner } from '@ember/application'; 4 | import Mixin from '@ember/object/mixin'; 5 | import { on } from '@ember/object/evented'; 6 | 7 | export default Mixin.create({ 8 | 9 | '-gestures': service('-gestures'), 10 | 11 | __fastboot: computed(function() { 12 | let owner = getOwner(this); 13 | 14 | return owner.lookup('service:fastboot'); 15 | }), 16 | 17 | recognizers: null, 18 | managerOptions: null, 19 | __instance: null, 20 | __manager() { 21 | let instance = this.get('__instance'); 22 | if (instance) { 23 | return instance; 24 | } 25 | 26 | const opts = this.get('managerOptions') || { domEvents: true }; 27 | 28 | opts.useCapture = this.get('-gestures.useCapture'); 29 | instance = new Hammer.Manager(this.element, opts); 30 | this.set('__instance', instance); 31 | 32 | return instance; 33 | }, 34 | 35 | __setupRecognizers: on('didInsertElement', function() { 36 | if (this.get('__fastboot.isFastBoot')) { return; } 37 | 38 | const promise = this.get('recognizers'); 39 | if (promise) { 40 | promise.then((recognizers) => { 41 | if (this.get('isDestroyed')) { 42 | return; 43 | } 44 | const manager = this.__manager(); 45 | // sort the recognizers 46 | for (let i = 0; i < recognizers.length; i++) { 47 | const r = recognizers[i]; 48 | let currentIndex = i; 49 | if (r.exclude.length) { 50 | for (let j = 0; j < r.exclude.length; j++) { 51 | const newIndex = recognizers.indexOf(r.exclude[j]); 52 | if (newIndex > 0 && currentIndex < newIndex) { 53 | recognizers.splice(currentIndex, 1); 54 | recognizers.splice(newIndex, 0, r); 55 | currentIndex = newIndex; 56 | } 57 | } 58 | } 59 | } 60 | 61 | for (let i = 0; i < recognizers.length; i++) { 62 | manager.add(recognizers[i]); 63 | } 64 | }); 65 | } 66 | }), 67 | 68 | __teardownRecognizers: on('willDestroyElement', function() { 69 | let instance = this.get('__instance'); 70 | if (instance) { 71 | //instance.off(); 72 | instance.destroy(); 73 | this.set('__instance', null); 74 | } 75 | }), 76 | 77 | 78 | init() { 79 | this._super(); 80 | 81 | // setup recognizers 82 | let recognizers = this.get('recognizers'); 83 | if (recognizers) { 84 | this.set('recognizers', this.get('-gestures').retrieve(recognizers.split(' '))); 85 | } 86 | } 87 | 88 | }); 89 | -------------------------------------------------------------------------------- /addon/modifiers/recognize-gesture.js: -------------------------------------------------------------------------------- 1 | 2 | import Modifier from 'ember-class-based-modifier'; 3 | import { getOwner } from '@ember/application'; 4 | 5 | export default class RecognizeGestureModifier extends Modifier { 6 | 7 | constructor() { 8 | super(...arguments); 9 | this.recognizers = null; 10 | this.manager = null; 11 | this.gestures = getOwner(this).lookup('service:-gestures'); 12 | 13 | if (this.args.positional) { 14 | this.recognizers = this.gestures.retrieve(this.args.positional); 15 | } 16 | this.managerOptions = (this.args.named && (Object.keys(this.args.named).length > 0)) ? Object.assign({}, this.args.named) : { domEvents: true }; 17 | this.managerOptions.useCapture = this.gestures.useCapture; 18 | 19 | } 20 | 21 | didInstall() { 22 | if (!this.recognizers) return; 23 | this.element.style['touch-action'] = 'manipulation'; 24 | this.element.style['-ms-touch-action'] = 'manipulation'; 25 | this.recognizers.then ( (recognizers) => { 26 | if (this.isDestroyed) return; 27 | this.sortRecognizers(recognizers); 28 | this.manager = new Hammer.Manager(this.element, this.managerOptions); 29 | recognizers.forEach((recognizer) => { this.manager.add(recognizer); }); 30 | }); 31 | } 32 | 33 | willRemove() { 34 | this.manager.destroy(); 35 | this.manager = null; 36 | } 37 | 38 | // Move each recognizer after all recognizers it excludes in the list - why? 39 | sortRecognizers(recognizers) { 40 | for (let i = 0; i < recognizers.length; i++) { 41 | const r = recognizers[i]; 42 | let currentIndex = i; 43 | if (r.exclude.length) { 44 | for (let j = 0; j < r.exclude.length; j++) { 45 | const newIndex = recognizers.indexOf(r.exclude[j]); 46 | if (newIndex > 0 && currentIndex < newIndex) { 47 | recognizers.splice(currentIndex, 1); 48 | recognizers.splice(newIndex, 0, r); 49 | currentIndex = newIndex; 50 | } 51 | } 52 | } 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /addon/recognizers/double-tap.js: -------------------------------------------------------------------------------- 1 | export default { 2 | include: ['tap'], 3 | exclude: [], 4 | options: { 5 | taps: 2 6 | }, 7 | recognizer: 'tap' 8 | }; 9 | -------------------------------------------------------------------------------- /addon/recognizers/pan.js: -------------------------------------------------------------------------------- 1 | export default { 2 | include: [], 3 | exclude: [], 4 | options: { direction: typeof Hammer === 'undefined' ? '' : Hammer.DIRECTION_HORIZONTAL }, 5 | recognizer: 'pan' 6 | }; 7 | -------------------------------------------------------------------------------- /addon/recognizers/pinch.js: -------------------------------------------------------------------------------- 1 | export default { 2 | include: [], 3 | exclude: [], 4 | options: {}, 5 | recognizer: 'pinch' 6 | }; 7 | -------------------------------------------------------------------------------- /addon/recognizers/press.js: -------------------------------------------------------------------------------- 1 | export default { 2 | include: [], 3 | exclude: [], 4 | options: {}, 5 | recognizer: 'press' 6 | }; 7 | -------------------------------------------------------------------------------- /addon/recognizers/rotate.js: -------------------------------------------------------------------------------- 1 | export default { 2 | include: [], 3 | exclude: [], 4 | options: {}, 5 | recognizer: 'rotate' 6 | }; 7 | -------------------------------------------------------------------------------- /addon/recognizers/single-tap.js: -------------------------------------------------------------------------------- 1 | export default { 2 | include: [], 3 | exclude: ['double-tap'], 4 | eventName: 'tap', 5 | options: { 6 | taps: 1 7 | }, 8 | recognizer: 'tap' 9 | }; 10 | -------------------------------------------------------------------------------- /addon/recognizers/swipe.js: -------------------------------------------------------------------------------- 1 | export default { 2 | include: [], 3 | exclude: [], 4 | options: { threshold: 25, direction: typeof Hammer === 'undefined' ? '' : Hammer.DIRECTION_HORIZONTAL }, 5 | recognizer: 'swipe' 6 | }; 7 | -------------------------------------------------------------------------------- /addon/recognizers/tap.js: -------------------------------------------------------------------------------- 1 | export default { 2 | include: [], 3 | exclude: [], 4 | options: {}, 5 | recognizer: 'tap' 6 | }; 7 | -------------------------------------------------------------------------------- /addon/recognizers/vertical-pan.js: -------------------------------------------------------------------------------- 1 | export default { 2 | include: [], 3 | exclude: [], 4 | eventName: 'pan', 5 | options: { direction: typeof Hammer === 'undefined' ? '' : Hammer.DIRECTION_VERTICAL }, 6 | recognizer: 'pan' 7 | }; 8 | -------------------------------------------------------------------------------- /addon/recognizers/vertical-swipe.js: -------------------------------------------------------------------------------- 1 | export default { 2 | include: [], 3 | exclude: [], 4 | eventName: 'swipe', 5 | options: { threshold: 25, direction: typeof Hammer === 'undefined' ? '' : Hammer.DIRECTION_VERTICAL}, 6 | recognizer: 'swipe' 7 | }; 8 | -------------------------------------------------------------------------------- /addon/registry-walker.js: -------------------------------------------------------------------------------- 1 | export default function ViewWalker(registry) { 2 | 3 | function inRegistry(id) { 4 | return !!registry[id]; 5 | } 6 | 7 | this.closest = function(closest) { 8 | do { 9 | if (closest.id && inRegistry(closest.id)) { 10 | return ['id', closest]; 11 | } 12 | if (closest.hasAttribute('data-ember-action')) { 13 | return ['action', closest]; 14 | } 15 | } while (closest = closest.parentNode); // eslint-disable-line 16 | 17 | return null; 18 | }; 19 | 20 | } 21 | -------------------------------------------------------------------------------- /addon/services/-gestures.js: -------------------------------------------------------------------------------- 1 | import { computed } from '@ember/object'; 2 | import { getOwner } from '@ember/application'; 3 | import Service from '@ember/service'; 4 | import RSVP from 'rsvp'; 5 | import camelize from '../utils/string/dasherized-to-camel'; 6 | import capitalize from '../utils/string/capitalize-word'; 7 | 8 | const { 9 | Promise, // jshint ignore:line 10 | defer // jshint ignore:line 11 | } = RSVP; 12 | 13 | export default Service.extend({ 14 | 15 | _recognizers: null, 16 | _fastboot: computed(function() { 17 | let owner = getOwner(this); 18 | 19 | return owner.lookup('service:fastboot'); 20 | }), 21 | 22 | retrieve(names) { 23 | let promises = names.map((name) => { return this.lookupRecognizer(name); }); 24 | return RSVP.all(promises); 25 | }, 26 | 27 | createRecognizer(name, details) { 28 | const eventName = camelize(details.eventName || name).toLowerCase(); 29 | const gesture = capitalize(details.recognizer); 30 | 31 | const options = details.options || {}; 32 | options.event = eventName; 33 | options.name = name; 34 | 35 | const Recognizer = new Hammer[gesture](options); 36 | Recognizer.initialize = defer(); 37 | 38 | this.set(`_recognizers.${name}`, Recognizer); 39 | return Recognizer; 40 | }, 41 | 42 | setupRecognizer(name, details) { 43 | if (this.get('_fastboot.isFastBoot')) { return; } 44 | return Promise.resolve(this.createRecognizer(name, details)) 45 | 46 | // includes 47 | .then((Recognizer) => { 48 | if (details.include) { 49 | const included = details.include.map((name) => { 50 | return this.__speedyLookupRecognizer(name); 51 | }); 52 | return RSVP.all(included).then((recognizers) => { 53 | Recognizer.recognizeWith(recognizers); 54 | return Recognizer; 55 | }); 56 | } 57 | return Recognizer; 58 | }) 59 | 60 | // excludes 61 | .then((Recognizer) => { 62 | if (details.exclude) { 63 | const excluded = details.exclude.map((name) => { 64 | return this.__speedyLookupRecognizer(name); 65 | }); 66 | 67 | return RSVP.all(excluded).then((recognizers) => { 68 | Recognizer.requireFailure(recognizers); 69 | Recognizer.exclude = recognizers; 70 | Recognizer.initialize.resolve(Recognizer); 71 | return Recognizer; 72 | }); 73 | } else { 74 | Recognizer.exclude = []; 75 | Recognizer.initialize.resolve(Recognizer); 76 | return Recognizer; 77 | } 78 | }); 79 | }, 80 | 81 | __speedyLookupRecognizer(name) { 82 | let recognizer = this.get(`_recognizers.${name}`); 83 | if (recognizer) { 84 | return recognizer; 85 | } 86 | 87 | const path = `ember-gesture:recognizers/${name}`; 88 | const details = getOwner(this).factoryFor(path); 89 | 90 | if (details) { 91 | return this.setupRecognizer(name, details.class); 92 | } 93 | 94 | return Promise.reject(new Error(`ember-gestures/recognizers/${name} was not found. You can scaffold this recognizer with 'ember g recognizer ${name}'`)); 95 | 96 | }, 97 | 98 | lookupRecognizer(name) { 99 | let recognizer = this.get(`_recognizers.${name}`); 100 | if (recognizer) { 101 | return recognizer.initialize.promise.then(function(recognizer) { 102 | return recognizer; 103 | }); 104 | } 105 | 106 | const path = `ember-gesture:recognizers/${name}`; 107 | const details = getOwner(this).factoryFor(path); 108 | 109 | if (details) { 110 | return this.setupRecognizer(name, details.class); 111 | } 112 | 113 | return Promise.reject(new Error(`ember-gestures/recognizers/${name} was not found. You can scaffold this recognizer with 'ember g recognizer ${name}'`)); 114 | }, 115 | 116 | init() { 117 | this._super(); 118 | this._recognizers = {}; 119 | } 120 | 121 | }); 122 | -------------------------------------------------------------------------------- /addon/styles/app.css: -------------------------------------------------------------------------------- 1 | slide-toggle { 2 | box-sizing: border-box; 3 | background: #f0f0f0; 4 | border: 0.05em solid #e0e0e0; 5 | cursor: pointer; 6 | width: 1.75em; 7 | height: 1em; 8 | position: static; 9 | display: inline-block; 10 | font-size: 2em; 11 | border-radius: 0.5em; 12 | } 13 | 14 | slide-toggle > .slideToggleButton { 15 | background: #f8f8f8; 16 | border-radius: 0.5em; 17 | height: 1em; 18 | position: relative; 19 | left: 0; 20 | top: -0.05em; 21 | width: 1em; 22 | box-shadow: 1px 0 2px 0 #bbb; 23 | transition: 0.1s ease-out; 24 | } 25 | 26 | slide-toggle.isOn { 27 | background: #37b737 linear-gradient(to bottom, #2c922c 0, #2c922c 20%, #37b737 100%); 28 | border-color: #37b737; 29 | } 30 | 31 | slide-toggle.isOn > .slideToggleButton { 32 | -ms-transform: translate(0.75em, 0); 33 | transform: translate(0.75em, 0); 34 | -webkit-transform: translate(0.75em, 0); 35 | -moz-transform: translate(0.75em, 0); 36 | -o-transform: translate(0.75em, 0); 37 | box-shadow: -1px 0 2px 0 #3f7952; 38 | } 39 | -------------------------------------------------------------------------------- /addon/templates/components/fast-action.hbs: -------------------------------------------------------------------------------- 1 | {{yield}}{{text}} 2 | -------------------------------------------------------------------------------- /addon/templates/components/fast-async.hbs: -------------------------------------------------------------------------------- 1 | {{yield isPending}}{{text}} 2 | -------------------------------------------------------------------------------- /addon/templates/components/gesture-element.hbs: -------------------------------------------------------------------------------- 1 | {{yield}} 2 | -------------------------------------------------------------------------------- /addon/templates/components/slide-toggle.hbs: -------------------------------------------------------------------------------- 1 |
-------------------------------------------------------------------------------- /addon/utils/is-mobile.js: -------------------------------------------------------------------------------- 1 | //ensure singleton 2 | function MobileDetection() { 3 | 4 | var IS_MOBILE; 5 | 6 | // Set our belief about whether the devise is mobile by inspecting ontouchstart 7 | this.detect = function() { IS_MOBILE = !!("ontouchstart" in window); }; 8 | 9 | // Return the current belief about whether the browser is mobile. 10 | this.is = function() { return IS_MOBILE; }; 11 | 12 | // This will generally only be done in tests. 13 | this.fake = function(value) { IS_MOBILE = value; }; 14 | 15 | // Set the initial value of IS_MOBILE so that calls to isMobile will have 16 | // the correct value. 17 | this.detect(); 18 | 19 | } 20 | 21 | export default new MobileDetection(); 22 | -------------------------------------------------------------------------------- /addon/utils/string/cap-first-letter.js: -------------------------------------------------------------------------------- 1 | export default function (s) { 2 | return s.charAt(0).toUpperCase() + s.slice(1); 3 | } 4 | -------------------------------------------------------------------------------- /addon/utils/string/capitalize-word.js: -------------------------------------------------------------------------------- 1 | export default function (s) { 2 | return s.charAt(0).toUpperCase() + s.slice(1); 3 | } 4 | -------------------------------------------------------------------------------- /addon/utils/string/capitalize-words.js: -------------------------------------------------------------------------------- 1 | import capFirstLetter from "./cap-first-letter"; 2 | 3 | export default function (sentence) { 4 | return sentence.split(' ') 5 | .map(function (word) { 6 | return capFirstLetter(word); 7 | }) 8 | .join(' '); 9 | } 10 | -------------------------------------------------------------------------------- /addon/utils/string/dasherized-to-camel.js: -------------------------------------------------------------------------------- 1 | import uncapitalize from './uncapitalize-word'; 2 | import dashToWords from './dasherized-to-words'; 3 | import capitalizeWords from './capitalize-words'; 4 | import stripWhiteSpace from './strip-whitespace'; 5 | 6 | export default function(s) { 7 | return uncapitalize( 8 | stripWhiteSpace( 9 | capitalizeWords( 10 | dashToWords(s)))); 11 | } 12 | -------------------------------------------------------------------------------- /addon/utils/string/dasherized-to-words.js: -------------------------------------------------------------------------------- 1 | export default function dasherizedToWords(s) { 2 | return s.replace(/-/g, ' '); 3 | } 4 | -------------------------------------------------------------------------------- /addon/utils/string/strip-whitespace.js: -------------------------------------------------------------------------------- 1 | export default function stripWhiteSpace(s) { 2 | return s.replace(/\s/g, ''); 3 | } 4 | -------------------------------------------------------------------------------- /addon/utils/string/uncapitalize-word.js: -------------------------------------------------------------------------------- 1 | export default function (s) { 2 | return s.charAt(0).toLowerCase() + s.slice(1); 3 | } 4 | -------------------------------------------------------------------------------- /app/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/html-next/ember-gestures/561cacc01d65e37e0c4bb8d544e7a06c14536f15/app/.gitkeep -------------------------------------------------------------------------------- /app/ember-gestures/recognizers/pan.js: -------------------------------------------------------------------------------- 1 | import Gesture from 'ember-gestures/recognizers/pan'; 2 | export default Gesture; 3 | -------------------------------------------------------------------------------- /app/ember-gestures/recognizers/pinch.js: -------------------------------------------------------------------------------- 1 | import Gesture from 'ember-gestures/recognizers/pinch'; 2 | export default Gesture; 3 | -------------------------------------------------------------------------------- /app/ember-gestures/recognizers/press.js: -------------------------------------------------------------------------------- 1 | import Gesture from 'ember-gestures/recognizers/press'; 2 | export default Gesture; 3 | -------------------------------------------------------------------------------- /app/ember-gestures/recognizers/rotate.js: -------------------------------------------------------------------------------- 1 | import Gesture from 'ember-gestures/recognizers/rotate'; 2 | export default Gesture; 3 | -------------------------------------------------------------------------------- /app/ember-gestures/recognizers/swipe.js: -------------------------------------------------------------------------------- 1 | import Gesture from 'ember-gestures/recognizers/swipe'; 2 | export default Gesture; 3 | -------------------------------------------------------------------------------- /app/ember-gestures/recognizers/tap.js: -------------------------------------------------------------------------------- 1 | // tap is not configured for double-tap, export single-tap 2 | // if using double-tap 3 | export { default } from 'ember-gestures/recognizers/tap'; 4 | -------------------------------------------------------------------------------- /app/ember-gestures/recognizers/vertical-pan.js: -------------------------------------------------------------------------------- 1 | export { default } from 'ember-gestures/recognizers/vertical-pan'; 2 | -------------------------------------------------------------------------------- /app/ember-gestures/recognizers/vertical-swipe.js: -------------------------------------------------------------------------------- 1 | export { default } from 'ember-gestures/recognizers/vertical-swipe'; 2 | -------------------------------------------------------------------------------- /app/event_dispatcher.js: -------------------------------------------------------------------------------- 1 | import { merge, assign as _assign } from '@ember/polyfills'; 2 | 3 | 4 | import EventDispatcher from 'ember-gestures/event_dispatcher'; 5 | import config from './config/environment'; 6 | 7 | const assign = _assign || merge; 8 | 9 | let gestures = assign({}, { 10 | emberUseCapture: false, 11 | removeTracking: false, 12 | useFastPaths: false 13 | }); 14 | gestures = assign(gestures, config.gestures); 15 | 16 | export default EventDispatcher.extend({ 17 | useCapture: gestures.emberUseCapture, 18 | removeTracking: gestures.removeTracking, 19 | useFastPaths: gestures.useFastPaths 20 | }); 21 | -------------------------------------------------------------------------------- /app/instance-initializers/ember-gestures.js: -------------------------------------------------------------------------------- 1 | import { getOwner } from '@ember/application'; 2 | 3 | export default { 4 | name: 'ember-gestures', 5 | 6 | initialize: function (instance) { 7 | if (typeof(instance.lookup) === "function") { 8 | instance.lookup('service:-gestures'); 9 | } else { 10 | // This can be removed when we no-longer support ember 1.12 and 1.13 11 | getOwner(instance).lookup('service:-gestures'); 12 | } 13 | } 14 | 15 | }; 16 | -------------------------------------------------------------------------------- /app/modifiers/recognize-gesture.js: -------------------------------------------------------------------------------- 1 | export { default } from 'ember-gestures/modifiers/recognize-gesture'; -------------------------------------------------------------------------------- /app/services/-gestures.js: -------------------------------------------------------------------------------- 1 | import { merge, assign as _assign } from '@ember/polyfills'; 2 | import config from '../config/environment'; 3 | import Service from 'ember-gestures/services/-gestures'; 4 | 5 | const assign = _assign || merge; 6 | 7 | let gestures = assign({}, { 8 | useCapture: false 9 | }); 10 | gestures = assign(gestures, config.gestures); 11 | 12 | export default Service.extend({ 13 | useCapture: gestures.useCapture 14 | }); 15 | -------------------------------------------------------------------------------- /blueprints/doubletap-recognizer/files/__root__/ember-gestures/recognizers/double-tap.js: -------------------------------------------------------------------------------- 1 | export { default } from 'ember-gestures/recognizers/double-tap'; 2 | -------------------------------------------------------------------------------- /blueprints/doubletap-recognizer/files/__root__/ember-gestures/recognizers/tap.js: -------------------------------------------------------------------------------- 1 | // single-tap is configured to work with double-tap 2 | export { default } from 'ember-gestures/recognizers/single-tap'; 3 | -------------------------------------------------------------------------------- /blueprints/doubletap-recognizer/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | description: '' 3 | 4 | // locals: function(options) { 5 | // // Return custom template variables here. 6 | // return { 7 | // foo: options.entity.options.foo 8 | // }; 9 | // } 10 | 11 | // afterInstall: function(options) { 12 | // // Perform extra work here. 13 | // } 14 | }; 15 | -------------------------------------------------------------------------------- /blueprints/ember-gestures/index.js: -------------------------------------------------------------------------------- 1 | const VersionChecker = require('ember-cli-version-checker'); 2 | 3 | module.exports = { 4 | 5 | name: 'ember-gestures', 6 | 7 | normalizeEntityName: function() { 8 | }, 9 | 10 | afterInstall: function() { 11 | const packages = [ 12 | { name: 'ember-hammertime', target: '^1.2.4' } 13 | ]; 14 | 15 | const checker = new VersionChecker(this); 16 | 17 | if (checker.forEmber().satisfies('< 2.3')) { 18 | packages.push({ name: 'ember-getowner-polyfill', target: '^1.2.3' }); 19 | } 20 | 21 | return this.addAddonsToProject({ packages }); 22 | } 23 | }; 24 | -------------------------------------------------------------------------------- /blueprints/gesture-components/files/__root__/components/async-element.js: -------------------------------------------------------------------------------- 1 | export { default } from 'ember-gestures/components/async-element'; 2 | -------------------------------------------------------------------------------- /blueprints/gesture-components/files/__root__/components/context-element.js: -------------------------------------------------------------------------------- 1 | export { default } from 'ember-gestures/components/context-element'; 2 | -------------------------------------------------------------------------------- /blueprints/gesture-components/files/__root__/components/fast-action.js: -------------------------------------------------------------------------------- 1 | export { default } from 'ember-gestures/components/fast-action'; -------------------------------------------------------------------------------- /blueprints/gesture-components/files/__root__/components/fast-async.js: -------------------------------------------------------------------------------- 1 | export { default } from 'ember-gestures/components/fast-async'; -------------------------------------------------------------------------------- /blueprints/gesture-components/files/__root__/components/gesture-element.js: -------------------------------------------------------------------------------- 1 | export { default } from 'ember-gestures/components/gesture-element'; 2 | -------------------------------------------------------------------------------- /blueprints/gesture-components/files/__root__/components/slide-toggle.js: -------------------------------------------------------------------------------- 1 | export { default } from 'ember-gestures/components/slide-toggle'; 2 | -------------------------------------------------------------------------------- /blueprints/gesture-components/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | description: 'Install helpful components and the ember-velocity-mixin', 3 | 4 | normalizeEntityName: function() {}, 5 | 6 | // locals: function(options) { 7 | // // Return custom template variables here. 8 | // return { 9 | // foo: options.entity.options.foo 10 | // }; 11 | // } 12 | 13 | afterInstall: function() { 14 | this.addAddonToProject('ember-velocity-mixin'); 15 | } 16 | 17 | }; 18 | -------------------------------------------------------------------------------- /blueprints/recognizer/files/__root__/ember-gestures/recognizers/__name__.js: -------------------------------------------------------------------------------- 1 | export default { 2 | include: ['tap'], //an array of recognizers to recognize with. 3 | exclude: [], //an array of recognizers that must first fail 4 | options: { 5 | taps: 2 // the settings to pass to the recognizer, event will be added automatically 6 | }, 7 | recognizer: 'tap' // `tap|press|pan|swipe|rotate|pinch` the base Hammer recognizer to use 8 | }; 9 | -------------------------------------------------------------------------------- /blueprints/recognizer/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | description: 'Generates a recognizer for use with HammerJS.', 3 | 4 | // locals: function(options) { 5 | // // Return custom template variables here. 6 | // return { 7 | // foo: options.entity.options.foo 8 | // }; 9 | // } 10 | 11 | // afterInstall: function(options) { 12 | // // Perform extra work here. 13 | // } 14 | 15 | // normalizeEntityName: function(entityName) { 16 | // entityName = Blueprint.prototype.normalizeEntityName.apply(this, arguments); 17 | 18 | // if (entityName.indexOf('-') === -1) { 19 | // this.ui.write(chalk.yellow('[WARN]: helpers without a dash will not automatically be resolved \n')); 20 | // } 21 | // return entityName; 22 | // } 23 | 24 | }; 25 | -------------------------------------------------------------------------------- /config/dependency-lint.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | //generateTests: false 5 | }; 6 | -------------------------------------------------------------------------------- /config/ember-cli-toolbelts.json: -------------------------------------------------------------------------------- 1 | { 2 | "babel": { 3 | "includePolyfill": true 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /config/ember-try.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const getChannelURL = require('ember-source-channel-url'); 4 | 5 | module.exports = function() { 6 | return Promise.all([ 7 | getChannelURL('release'), 8 | getChannelURL('beta'), 9 | getChannelURL('canary') 10 | ]).then((urls) => { 11 | return { 12 | useYarn: true, 13 | scenarios: [ 14 | { 15 | name: 'ember-lts-2.12', 16 | npm: { 17 | devDependencies: { 18 | 'ember-source': '~2.12.0' 19 | } 20 | } 21 | }, 22 | { 23 | name: 'ember-lts-2.16', 24 | npm: { 25 | devDependencies: { 26 | 'ember-source': '~2.16.0' 27 | } 28 | } 29 | }, 30 | { 31 | name: 'ember-lts-2.18', 32 | npm: { 33 | devDependencies: { 34 | 'ember-source': '~2.18.0' 35 | } 36 | } 37 | }, 38 | { 39 | name: 'ember-lts-3.4', 40 | npm: { 41 | devDependencies: { 42 | 'ember-source': '~3.4.0' 43 | } 44 | } 45 | }, 46 | { 47 | name: 'ember-lts-3.8', 48 | npm: { 49 | devDependencies: { 50 | 'ember-source': '~3.8.0' 51 | } 52 | } 53 | }, 54 | { 55 | name: 'ember-lts-3.12', 56 | npm: { 57 | devDependencies: { 58 | 'ember-source': '~3.12.0' 59 | } 60 | } 61 | }, 62 | { 63 | name: 'ember-release', 64 | npm: { 65 | devDependencies: { 66 | 'ember-source': urls[0] 67 | } 68 | } 69 | }, 70 | { 71 | name: 'ember-beta', 72 | npm: { 73 | devDependencies: { 74 | 'ember-source': urls[1] 75 | } 76 | } 77 | }, 78 | { 79 | name: 'ember-canary', 80 | npm: { 81 | devDependencies: { 82 | 'ember-source': urls[2] 83 | } 84 | } 85 | }, 86 | { 87 | name: 'ember-default', 88 | npm: { 89 | devDependencies: {} 90 | } 91 | } 92 | ] 93 | }; 94 | }); 95 | }; 96 | -------------------------------------------------------------------------------- /config/environment.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function(/* environment, appConfig */) { 4 | return { }; 5 | }; 6 | -------------------------------------------------------------------------------- /ember-cli-build.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const EmberAddon = require('ember-cli/lib/broccoli/ember-addon'); 4 | 5 | module.exports = function(defaults) { 6 | let app = new EmberAddon(defaults, { 7 | 'ember-cli-babel': { 8 | "includePolyfill": true 9 | }, 10 | velocityOptions: { 11 | enabled: true, 12 | ui: false 13 | }, 14 | snippetPaths: ['tests/dummy/snippets'], 15 | snippetSearchPaths: ['app', 'tests/dummy/app', 'addon'] 16 | }); 17 | 18 | /* 19 | This build file specifies the options for the dummy test app of this 20 | addon, located in `/tests/dummy` 21 | This build file does *not* influence how the addon or the app using it 22 | behave. You most likely want to be modifying `./index.js` or app's build file 23 | */ 24 | 25 | return app.toTree(); 26 | }; 27 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const path = require('path'); 4 | const Funnel = require('broccoli-funnel'); 5 | const BroccoliMergeTrees = require('broccoli-merge-trees'); 6 | const fastbootTransform = require('fastboot-transform'); 7 | const resolve = require('resolve'); 8 | 9 | module.exports = { 10 | 11 | name: 'ember-gestures', 12 | 13 | included() { 14 | this._super.included.apply(this, arguments); 15 | let app; 16 | 17 | // If the addon has the _findHost() method (in ember-cli >= 2.7.0), we'll just 18 | // use that. 19 | if (typeof this._findHost === 'function') { 20 | app = this._findHost(); 21 | } else { 22 | // Otherwise, we'll use this implementation borrowed from the _findHost() 23 | // method in ember-cli. 24 | let current = this; 25 | do { 26 | app = current.app || app; 27 | } while (current.parent.parent && (current = current.parent)); 28 | } 29 | 30 | app.import('vendor/hammerjs/hammer.js'); 31 | app.import('vendor/animation-frame/AnimationFrame.js'); 32 | }, 33 | 34 | treeForVendor(tree) { 35 | let trees = []; 36 | 37 | let hammerJs = fastbootTransform(new Funnel(this.pathBase('hammerjs'), { 38 | files: ['hammer.js'], 39 | destDir: 'hammerjs' 40 | })); 41 | 42 | let animationFrame = fastbootTransform(new Funnel(this.pathBase('animation-frame'), { 43 | files: ['AnimationFrame.js'], 44 | destDir: 'animation-frame' 45 | })); 46 | 47 | trees = trees.concat([hammerJs, animationFrame]); 48 | 49 | if (tree) { 50 | trees.push(tree); 51 | } 52 | 53 | return new BroccoliMergeTrees(trees); 54 | }, 55 | 56 | /* 57 | Rely on the `resolve` package to mimic node's resolve algorithm. 58 | It finds the modules in a manner that works for npm 2.x, 59 | 3.x, and yarn in both the addon itself and projects that depend on this addon. 60 | This is an edge case b/c some modules do not have a main 61 | module we can require.resolve through node itself and similarily ember-cli 62 | does not have such a hack for the same reason. 63 | */ 64 | pathBase(packageName) { 65 | return path.dirname(resolve.sync(`${packageName}/package.json`, { basedir: __dirname })); 66 | }, 67 | 68 | isDevelopingAddon() { 69 | return false; 70 | } 71 | 72 | }; 73 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ember-gestures", 3 | "version": "2.0.1", 4 | "description": "TouchAction and Gesture support for Ember Applications", 5 | "keywords": [ 6 | "ember-addon", 7 | "mobile", 8 | "touch", 9 | "gestures", 10 | "hammerjs", 11 | "ember", 12 | "fastclick", 13 | "touch action", 14 | "pointer events" 15 | ], 16 | "license": "MIT", 17 | "author": { 18 | "name": "Chris Thoburn (@runspired)", 19 | "url": "http://runspired.com" 20 | }, 21 | "contributors": [ 22 | { 23 | "name": "Robert Wagner", 24 | "email": "rwwagner90@gmail.com", 25 | "url": "https://github.com/rwwagner90" 26 | } 27 | ], 28 | "directories": { 29 | "doc": "doc", 30 | "test": "tests" 31 | }, 32 | "repository": "git@github.com:html-next/ember-gestures.git", 33 | "scripts": { 34 | "lint:js": "eslint ./*.js addon addon-test-support app config lib server test-support tests", 35 | "start": "ember serve", 36 | "test": "ember test", 37 | "build": "ember build", 38 | "test:all": "ember try:each" 39 | }, 40 | "homepage": "https://github.com/html-next/ember-gestures", 41 | "bugs": "https://github.com/html-next/ember-gestures/issues", 42 | "dependencies": { 43 | "animation-frame": "~0.2.4", 44 | "broccoli-funnel": "^2.0.2", 45 | "broccoli-merge-trees": "^3.0.2", 46 | "ember-class-based-modifier": "^0.10.0", 47 | "ember-cli-babel": "^7.7.0", 48 | "ember-cli-htmlbars": "^2.0.1", 49 | "ember-cli-version-checker": "^2.1.0", 50 | "ember-copy": "^2.0.0", 51 | "fastboot-transform": "^0.1.3", 52 | "hammerjs": "^2.0.8", 53 | "resolve": "^1.10.0" 54 | }, 55 | "devDependencies": { 56 | "broccoli-asset-rev": "^2.4.5", 57 | "ember-cli": "~3.1.2", 58 | "ember-cli-dependency-checker": "^2.0.0", 59 | "ember-cli-dependency-lint": "^1.1.3", 60 | "ember-cli-eslint": "^4.2.1", 61 | "ember-cli-htmlbars-inline-precompile": "^1.0.0", 62 | "ember-cli-inject-live-reload": "^1.4.1", 63 | "ember-cli-release": "^0.2.9", 64 | "ember-cli-shims": "^1.2.0", 65 | "ember-cli-sri": "^2.1.0", 66 | "ember-cli-uglify": "^2.0.0", 67 | "ember-code-snippet": "^1.2.1", 68 | "ember-disable-prototype-extensions": "^1.1.2", 69 | "ember-export-application-global": "^2.0.0", 70 | "ember-hammertime": "^1.2.3", 71 | "ember-load-initializers": "^1.0.0", 72 | "ember-maybe-import-regenerator": "^0.1.6", 73 | "ember-on-modifier": "1.0.0", 74 | "ember-qunit": "^4.5.1", 75 | "ember-resolver": "^4.0.0", 76 | "ember-source": "~3.1.0", 77 | "ember-source-channel-url": "^1.0.1", 78 | "ember-try": "^0.2.23", 79 | "eslint-plugin-ember": "^5.0.0", 80 | "eslint-plugin-node": "^6.0.1", 81 | "loader.js": "^4.2.3" 82 | }, 83 | "engines": { 84 | "node": ">= 10.*" 85 | }, 86 | "ember-addon": { 87 | "configPath": "tests/dummy/config" 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /testem.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | test_page: 'tests/index.html?hidepassed', 3 | disable_watching: true, 4 | launch_in_ci: [ 5 | 'Chrome' 6 | ], 7 | launch_in_dev: [ 8 | 'Chrome' 9 | ], 10 | browser_args: { 11 | Chrome: { 12 | mode: 'ci', 13 | args: [ 14 | // --no-sandbox is needed when running Chrome inside a container 15 | process.env.TRAVIS ? '--no-sandbox' : null, 16 | 17 | '--disable-gpu', 18 | '--headless', 19 | '--remote-debugging-port=0', 20 | '--window-size=1440,900' 21 | ].filter(Boolean) 22 | } 23 | } 24 | }; -------------------------------------------------------------------------------- /tests/acceptance/index-test.js: -------------------------------------------------------------------------------- 1 | import { module, test } from 'qunit'; 2 | import { visit, currentURL } from '@ember/test-helpers'; 3 | import { setupApplicationTest } from 'ember-qunit'; 4 | 5 | module('Acceptance | index', function (hooks) { 6 | setupApplicationTest(hooks); 7 | 8 | test('visiting /', async function(assert) { 9 | await visit('/'); 10 | 11 | assert.equal(currentURL(), '/'); 12 | }); 13 | }); 14 | -------------------------------------------------------------------------------- /tests/dummy/app/app.js: -------------------------------------------------------------------------------- 1 | import Application from '@ember/application'; 2 | import Resolver from './resolver'; 3 | import loadInitializers from 'ember-load-initializers'; 4 | import config from './config/environment'; 5 | 6 | const App = Application.extend({ 7 | modulePrefix: config.modulePrefix, 8 | podModulePrefix: config.podModulePrefix, 9 | Resolver 10 | }); 11 | 12 | loadInitializers(App, config.modulePrefix); 13 | 14 | export default App; 15 | -------------------------------------------------------------------------------- /tests/dummy/app/components/async-element.js: -------------------------------------------------------------------------------- 1 | export { default } from 'ember-gestures/components/async-element'; 2 | -------------------------------------------------------------------------------- /tests/dummy/app/components/context-element.js: -------------------------------------------------------------------------------- 1 | export { default } from 'ember-gestures/components/context-element'; 2 | -------------------------------------------------------------------------------- /tests/dummy/app/components/fast-action.js: -------------------------------------------------------------------------------- 1 | export { default } from 'ember-gestures/components/fast-action'; -------------------------------------------------------------------------------- /tests/dummy/app/components/fast-async.js: -------------------------------------------------------------------------------- 1 | export { default } from 'ember-gestures/components/fast-async'; -------------------------------------------------------------------------------- /tests/dummy/app/components/gesture-element.js: -------------------------------------------------------------------------------- 1 | export { default } from 'ember-gestures/components/gesture-element'; 2 | -------------------------------------------------------------------------------- /tests/dummy/app/components/recognizes-single-and-double-tap.js: -------------------------------------------------------------------------------- 1 | import Component from '@ember/component'; 2 | import { run } from '@ember/runloop'; 3 | import layout from '../templates/components/recognizes-single-and-double-tap'; 4 | import RecognizerMixin from 'ember-gestures/mixins/recognizers'; 5 | 6 | export default Component.extend(RecognizerMixin, { 7 | layout, 8 | recognizers: 'tap double-tap', 9 | sawSingle: false, 10 | sawDouble: false, 11 | 12 | tap() { 13 | this.set('sawSingle', true); 14 | run.debounce(this, this.reset, 1000); 15 | }, 16 | 17 | doubleTap() { 18 | this.set('sawDouble', true); 19 | run.debounce(this, this.reset, 1000); 20 | }, 21 | 22 | reset() { 23 | if (!this.get('isDestroyed')) { 24 | this.set('sawSingle', false); 25 | this.set('sawDouble', false); 26 | } 27 | } 28 | 29 | }); 30 | -------------------------------------------------------------------------------- /tests/dummy/app/components/single-and-double-tap-modifier.js: -------------------------------------------------------------------------------- 1 | import Component from '@ember/component'; 2 | import { run } from '@ember/runloop'; 3 | import layout from '../templates/components/recognizes-single-and-double-tap'; 4 | 5 | export default Component.extend({ 6 | layout, 7 | tagName: '', 8 | sawSingle: false, 9 | sawDouble: false, 10 | 11 | reset: function () { 12 | if (!this.get('isDestroyed')) { 13 | this.set('sawSingle', false); 14 | this.set('sawDouble', false); 15 | } 16 | }, 17 | 18 | actions: { 19 | doTap: function() { 20 | this.set('sawSingle', true); 21 | run.debounce(this, this.reset, 1000); 22 | }, 23 | 24 | doDoubleTap: function() { 25 | this.set('sawDouble', true); 26 | run.debounce(this, this.reset, 1000); 27 | } 28 | 29 | } 30 | 31 | }); 32 | -------------------------------------------------------------------------------- /tests/dummy/app/components/slide-toggle.js: -------------------------------------------------------------------------------- 1 | export { default } from 'ember-gestures/components/slide-toggle'; 2 | -------------------------------------------------------------------------------- /tests/dummy/app/components/traditional-click.js: -------------------------------------------------------------------------------- 1 | import Component from '@ember/component'; 2 | import { run } from '@ember/runloop'; 3 | import layout from '../templates/components/traditional-click'; 4 | 5 | export default Component.extend({ 6 | layout, 7 | sawClick: false, 8 | 9 | click() { 10 | this.set('sawClick', true); 11 | run.debounce(this, this.reset, 1000); 12 | }, 13 | reset() { 14 | if (!this.get('isDestroyed')) { 15 | this.set('sawClick', false); 16 | } 17 | } 18 | 19 | }); 20 | -------------------------------------------------------------------------------- /tests/dummy/app/ember-gestures/recognizers/double-tap.js: -------------------------------------------------------------------------------- 1 | export { default } from 'ember-gestures/recognizers/double-tap'; 2 | -------------------------------------------------------------------------------- /tests/dummy/app/ember-gestures/recognizers/tap.js: -------------------------------------------------------------------------------- 1 | export { default } from 'ember-gestures/recognizers/single-tap'; 2 | -------------------------------------------------------------------------------- /tests/dummy/app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Ember Gestures 7 | 8 | 10 | 11 | 12 | 13 | 14 | {{content-for "head"}} 15 | 16 | 17 | 18 | 19 | {{content-for "head-footer"}} 20 | 21 | 22 | {{content-for "body"}} 23 | 24 | 25 | 26 | 27 | {{content-for "body-footer"}} 28 | 29 | 30 | -------------------------------------------------------------------------------- /tests/dummy/app/resolver.js: -------------------------------------------------------------------------------- 1 | import Resolver from 'ember-resolver'; 2 | 3 | export default Resolver; 4 | -------------------------------------------------------------------------------- /tests/dummy/app/router.js: -------------------------------------------------------------------------------- 1 | import EmberRouter from '@ember/routing/router'; 2 | import config from './config/environment'; 3 | 4 | const Router = EmberRouter.extend({ 5 | location: config.locationType, 6 | rootURL: config.rootURL 7 | }); 8 | 9 | Router.map(function() { 10 | this.route('taps'); 11 | this.route('modtaps'); 12 | }); 13 | 14 | export default Router; 15 | -------------------------------------------------------------------------------- /tests/dummy/app/routes/modtaps.js: -------------------------------------------------------------------------------- 1 | import Route from '@ember/routing/route'; 2 | 3 | export default Route.extend({ 4 | init: function() { 5 | this._super(...arguments); 6 | } 7 | }); 8 | -------------------------------------------------------------------------------- /tests/dummy/app/routes/taps.js: -------------------------------------------------------------------------------- 1 | import Route from '@ember/routing/route'; 2 | 3 | export default Route.extend({ 4 | }); 5 | -------------------------------------------------------------------------------- /tests/dummy/app/styles/app.css: -------------------------------------------------------------------------------- 1 | html { 2 | font-size: 125%; 3 | } 4 | body { 5 | font-size: .75em; 6 | background: #f0f0f0; 7 | font-family: 'raleway', 'open sans', 'helvetica', 'arial', sans-serif; 8 | margin: 0px; 9 | } 10 | 11 | .block-element { 12 | width: 100%; 13 | position: static; 14 | display: block; 15 | } 16 | 17 | .actionable-example { 18 | background: #444; 19 | border-radius: 3px; 20 | border: 1px dashed #222; 21 | color: #ffbe57; 22 | padding: 0 10px 10px; 23 | z-index: 1000; 24 | position: relative; 25 | } 26 | 27 | .actionable-example.pending { 28 | background: #aaa; 29 | } 30 | .actionable-example.fulfilled { 31 | background: #00dd55; 32 | } 33 | .actionable-example.rejected { 34 | background: #ee3300; 35 | } 36 | 37 | aside { 38 | color: #555; 39 | } 40 | 41 | aside a { 42 | color: #888888; 43 | } 44 | aside a:hover, aside a:active, aside a:focus { 45 | color: #999999; 46 | } 47 | 48 | .ui-header { 49 | background: #17B56C; 50 | border-bottom: 1px solid #0b6033; 51 | min-height: 2.5rem; 52 | line-height: 2.5rem; 53 | z-index: 1000; 54 | } 55 | 56 | .ui-header h1 { 57 | margin: 0; 58 | color: #0b6033; 59 | font-weight: 400; 60 | font-size: 1.5rem; 61 | line-height: 1.66666667em; 62 | } 63 | .ui-header h3 { 64 | font-size: .8rem; 65 | color: #fff; 66 | } 67 | 68 | .ui-logo { 69 | position: relative; 70 | display: inline-block; 71 | width: 1em; 72 | height: 1em; 73 | font-size: 1em; 74 | margin: 0; 75 | padding: 0; 76 | vertical-align: top; 77 | } 78 | 79 | .billboard-right { 80 | display: inline-block; 81 | width: 1em; 82 | height: 1em; 83 | float: right; 84 | text-align: center; 85 | font-size: 8rem; 86 | } 87 | 88 | .ui-logo:before { 89 | content: ' '; 90 | width: 100%; 91 | height: 100%; 92 | display: block; 93 | position: relative; 94 | background: url('../interactive.svg') center center / 100% 100% no-repeat; 95 | } 96 | 97 | .ui-body { 98 | margin-top: .25rem; 99 | } 100 | 101 | h2 { 102 | font-size: 1.25rem; 103 | } 104 | 105 | section.container-fluid { 106 | margin: 5px; 107 | background: #fff; 108 | border: 1px solid #e8e8e8; 109 | border-radius: 2px; 110 | } 111 | 112 | .application-template { 113 | margin: 2px 8px 10px 8px; 114 | } 115 | -------------------------------------------------------------------------------- /tests/dummy/app/templates/application.hbs: -------------------------------------------------------------------------------- 1 |
2 |

ember-gestures dummy app

3 | {{outlet}} 4 |
5 | -------------------------------------------------------------------------------- /tests/dummy/app/templates/components/recognizes-single-and-double-tap.hbs: -------------------------------------------------------------------------------- 1 | Tap on me once or twice. 2 | 3 | {{#if sawSingle}} 4 | I saw a single-tap. 5 | {{/if}} 6 | 7 | {{#if sawDouble}} 8 | I saw a double-tap. 9 | {{/if}} 10 | -------------------------------------------------------------------------------- /tests/dummy/app/templates/components/single-and-double-tap-modifier.hbs: -------------------------------------------------------------------------------- 1 |
6 | Tap on me once or twice (implemented with modifier). 7 | 8 | {{#if sawSingle}} 9 | I saw a single-tap. 10 | {{/if}} 11 | 12 | {{#if sawDouble}} 13 | I saw a double-tap. 14 | {{/if}} 15 |
-------------------------------------------------------------------------------- /tests/dummy/app/templates/components/traditional-click.hbs: -------------------------------------------------------------------------------- 1 | Tap to test a traditional click handler on a component. 2 | 3 | {{#if sawClick}} 4 | I saw a click 5 | {{/if}} 6 | -------------------------------------------------------------------------------- /tests/dummy/app/templates/index.hbs: -------------------------------------------------------------------------------- 1 |

Demos index

2 |
    3 |
  1. {{#link-to "taps"}}taps{{/link-to}}
  2. 4 |
  3. {{#link-to "modtaps"}}taps (w/Modifiers){{/link-to}}
  4. 5 |
6 | -------------------------------------------------------------------------------- /tests/dummy/app/templates/modtaps.hbs: -------------------------------------------------------------------------------- 1 |
{{#link-to "index"}}back to index{{/link-to}}
2 | 3 |

Taps tests (with modifiers)

4 |
    5 |
  1. {{single-and-double-tap-modifier}}
  2. 6 |
  3. {{traditional-click}}
  4. 7 |
8 | 9 | 10 | -------------------------------------------------------------------------------- /tests/dummy/app/templates/taps.hbs: -------------------------------------------------------------------------------- 1 |
{{#link-to "index"}}back to index{{/link-to}}
2 | 3 |

Taps tests

4 |
    5 |
  1. {{recognizes-single-and-double-tap}}
  2. 6 |
  3. {{traditional-click}}
  4. 7 |
8 | 9 | {{outlet}} 10 | -------------------------------------------------------------------------------- /tests/dummy/config/environment.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function(environment) { 4 | let ENV = { 5 | modulePrefix: 'dummy', 6 | environment, 7 | rootURL: '/', 8 | locationType: 'auto', 9 | EmberENV: { 10 | FEATURES: { 11 | // Here you can enable experimental features on an ember canary build 12 | // e.g. 'with-controller': true 13 | }, 14 | EXTEND_PROTOTYPES: { 15 | // Prevent Ember Data from overriding Date.parse. 16 | Date: false 17 | } 18 | }, 19 | 20 | APP: { 21 | // Here you can pass flags/options to your application instance 22 | // when it is created 23 | } 24 | }; 25 | 26 | if (environment === 'development') { 27 | // ENV.APP.LOG_RESOLVER = true; 28 | // ENV.APP.LOG_ACTIVE_GENERATION = true; 29 | // ENV.APP.LOG_TRANSITIONS = true; 30 | // ENV.APP.LOG_TRANSITIONS_INTERNAL = true; 31 | // ENV.APP.LOG_VIEW_LOOKUPS = true; 32 | } 33 | 34 | if (environment === 'test') { 35 | // Testem prefers this... 36 | ENV.locationType = 'none'; 37 | 38 | // keep test console output quieter 39 | ENV.APP.LOG_ACTIVE_GENERATION = false; 40 | ENV.APP.LOG_VIEW_LOOKUPS = false; 41 | 42 | ENV.APP.rootElement = '#ember-testing'; 43 | ENV.APP.autoboot = false; 44 | } 45 | 46 | if (environment === 'production') { 47 | // noop block 48 | } 49 | 50 | 51 | return ENV; 52 | }; 53 | -------------------------------------------------------------------------------- /tests/dummy/config/targets.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const browsers = [ 4 | 'last 1 Chrome versions', 5 | 'last 1 Firefox versions', 6 | 'last 1 Safari versions' 7 | ]; 8 | 9 | const isCI = !!process.env.CI; 10 | const isProduction = process.env.EMBER_ENV === 'production'; 11 | 12 | if (isCI || isProduction) { 13 | browsers.push('ie 11'); 14 | } 15 | 16 | module.exports = { 17 | browsers 18 | }; 19 | -------------------------------------------------------------------------------- /tests/dummy/public/interactive.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 8 | 9 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /tests/dummy/public/robots.txt: -------------------------------------------------------------------------------- 1 | # http://www.robotstxt.org 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /tests/helpers/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/html-next/ember-gestures/561cacc01d65e37e0c4bb8d544e7a06c14536f15/tests/helpers/.gitkeep -------------------------------------------------------------------------------- /tests/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Dummy Tests 7 | 8 | 9 | 10 | {{content-for "head"}} 11 | {{content-for "test-head"}} 12 | 13 | 14 | 15 | 16 | 17 | {{content-for "head-footer"}} 18 | {{content-for "test-head-footer"}} 19 | 20 | 21 | {{content-for "body"}} 22 | {{content-for "test-body"}} 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | {{content-for "body-footer"}} 31 | {{content-for "test-body-footer"}} 32 | 33 | 34 | -------------------------------------------------------------------------------- /tests/integration/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/html-next/ember-gestures/561cacc01d65e37e0c4bb8d544e7a06c14536f15/tests/integration/.gitkeep -------------------------------------------------------------------------------- /tests/integration/components/fast-action-test.js: -------------------------------------------------------------------------------- 1 | import { module, test } from 'qunit'; 2 | import { setupRenderingTest } from 'ember-qunit'; 3 | import { render } from '@ember/test-helpers'; 4 | import hbs from 'htmlbars-inline-precompile'; 5 | 6 | module('Integration | Component | fast-action', function(hooks) { 7 | setupRenderingTest(hooks); 8 | 9 | test('it renders', async function(assert) { 10 | assert.expect(2); 11 | 12 | // Set any properties with this.set('myProperty', 'value'); 13 | // Handle any actions with this.set('myAction', function(val) { ... }); 14 | 15 | await render(hbs`{{fast-action}}`); 16 | 17 | assert.equal(this.element.textContent.trim(), ''); 18 | 19 | // Template block usage: 20 | await render(hbs` 21 | {{#fast-action}} 22 | template block text 23 | {{/fast-action}} 24 | `); 25 | 26 | assert.equal(this.element.textContent.trim(), 'template block text'); 27 | }); 28 | }); 29 | -------------------------------------------------------------------------------- /tests/integration/components/fast-async-test.js: -------------------------------------------------------------------------------- 1 | import { module, test } from 'qunit'; 2 | import { setupRenderingTest } from 'ember-qunit'; 3 | import { render } from '@ember/test-helpers'; 4 | import hbs from 'htmlbars-inline-precompile'; 5 | 6 | module('Integration | Component | fast-async', function(hooks) { 7 | setupRenderingTest(hooks); 8 | 9 | test('it renders', async function(assert) { 10 | assert.expect(2); 11 | 12 | // Set any properties with this.set('myProperty', 'value'); 13 | // Handle any actions with this.set('myAction', function(val) { ... }); 14 | 15 | await render(hbs`{{fast-async}}`); 16 | 17 | assert.equal(this.element.textContent.trim(), ''); 18 | 19 | // Template block usage: 20 | await render(hbs` 21 | {{#fast-async}} 22 | template block text 23 | {{/fast-async}} 24 | `); 25 | 26 | assert.equal(this.element.textContent.trim(), 'template block text'); 27 | }); 28 | }); 29 | -------------------------------------------------------------------------------- /tests/test-helper.js: -------------------------------------------------------------------------------- 1 | import Application from '../app'; 2 | import config from '../config/environment'; 3 | import { setApplication } from '@ember/test-helpers'; 4 | import { start } from 'ember-qunit'; 5 | 6 | setApplication(Application.create(config.APP)); 7 | 8 | start(); 9 | -------------------------------------------------------------------------------- /tests/unit/utils/string-test.js: -------------------------------------------------------------------------------- 1 | import { test } from 'ember-qunit'; 2 | import toCamel from 'ember-gestures/utils/string/dasherized-to-camel'; 3 | import capitalize from 'ember-gestures/utils/string/capitalize-word'; 4 | import uncapitalize from 'ember-gestures/utils/string/uncapitalize-word'; 5 | import stripWhiteSpace from 'ember-gestures/utils/string/strip-whitespace'; 6 | import capitalizeWords from 'ember-gestures/utils/string/capitalize-words'; 7 | import dashToWords from 'ember-gestures/utils/string/dasherized-to-words'; 8 | 9 | test('toCamel: properly camelizes basic string', function(assert) { 10 | assert.equal(toCamel('FooBar'), 'fooBar'); 11 | }); 12 | 13 | test('toCamel: properly capitalizes sequences of words', function(assert) { 14 | assert.equal(toCamel('Foo bar Baz Quux'), 'fooBarBazQuux'); 15 | }); 16 | 17 | test('capitalize: capitalizes the first character of a string', function(assert) { 18 | assert.equal(capitalize('foobar'), 'Foobar'); 19 | }); 20 | 21 | test('uncapitalize: uncapitalizes the first character of a string', function(assert) { 22 | assert.equal(uncapitalize('Foobar'), 'foobar'); 23 | }); 24 | 25 | test('uncapitalize: is a noop when first character needs no work done', function(assert) { 26 | assert.equal(uncapitalize('foobar'), 'foobar'); 27 | }); 28 | 29 | test('stripWhitespace: removes whitespace from string', function(assert) { 30 | assert.equal(stripWhiteSpace('foo bar'), 'foobar'); 31 | }); 32 | 33 | test('capitalizeWords: capitalizes each string in given sequence', function(assert) { 34 | assert.equal(capitalizeWords('foo bar baz quux'), 'Foo Bar Baz Quux'); 35 | }); 36 | 37 | test('dashToWords: takes dasherized string and splits to sequence', function(assert) { 38 | assert.equal(dashToWords('foo-bar-baz'), 'foo bar baz'); 39 | }); 40 | -------------------------------------------------------------------------------- /vendor/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/html-next/ember-gestures/561cacc01d65e37e0c4bb8d544e7a06c14536f15/vendor/.gitkeep --------------------------------------------------------------------------------