Repository: hoch/WAAX Branch: master Commit: ca933954877e Files: 206 Total size: 2.8 MB Directory structure: gitextract_ubii12jd/ ├── .gitignore ├── LICENSE ├── README.md ├── build/ │ └── mui/ │ ├── bower_components/ │ │ ├── core-component-page/ │ │ │ ├── README.md │ │ │ ├── bower.json │ │ │ ├── core-component-page.html │ │ │ ├── demo.html │ │ │ └── index.html │ │ ├── core-icon/ │ │ │ ├── README.md │ │ │ ├── bower.json │ │ │ ├── core-icon.css │ │ │ ├── core-icon.html │ │ │ ├── demo.html │ │ │ ├── index.html │ │ │ └── metadata.html │ │ ├── core-icons/ │ │ │ ├── README.md │ │ │ ├── av-icons.html │ │ │ ├── bower.json │ │ │ ├── communication-icons.html │ │ │ ├── core-icons.html │ │ │ ├── demo.html │ │ │ ├── device-icons.html │ │ │ ├── editor-icons.html │ │ │ ├── hardware-icons.html │ │ │ ├── image-icons.html │ │ │ ├── index.html │ │ │ ├── maps-icons.html │ │ │ ├── notification-icons.html │ │ │ ├── png-icons.html │ │ │ └── social-icons.html │ │ ├── core-iconset/ │ │ │ ├── README.md │ │ │ ├── bower.json │ │ │ ├── core-iconset.html │ │ │ ├── demo.html │ │ │ └── index.html │ │ ├── core-iconset-svg/ │ │ │ ├── README.md │ │ │ ├── bower.json │ │ │ ├── core-iconset-svg.html │ │ │ ├── demo.html │ │ │ ├── index.html │ │ │ └── svg-sample-icons.html │ │ ├── core-meta/ │ │ │ ├── README.md │ │ │ ├── bower.json │ │ │ ├── core-meta.html │ │ │ ├── demo.html │ │ │ └── index.html │ │ ├── polymer/ │ │ │ ├── README.md │ │ │ ├── bower.json │ │ │ ├── build.log │ │ │ ├── dist/ │ │ │ │ └── polymer.html │ │ │ ├── layout.html │ │ │ ├── polymer.html │ │ │ └── polymer.js │ │ └── webcomponentsjs/ │ │ ├── CustomElements.js │ │ ├── HTMLImports.js │ │ ├── README.md │ │ ├── ShadowDOM.js │ │ ├── bower.json │ │ ├── build.log │ │ ├── package.json │ │ ├── webcomponents-lite.js │ │ └── webcomponents.js │ ├── mui-button/ │ │ └── mui-button.html │ ├── mui-eblock/ │ │ └── mui-eblock.html │ ├── mui-group/ │ │ └── mui-group.html │ ├── mui-knob/ │ │ └── mui-knob.html │ ├── mui-knobh/ │ │ └── mui-knobh.html │ ├── mui-meter/ │ │ └── mui-meter.html │ ├── mui-pianoroll/ │ │ └── mui-pianoroll.html │ ├── mui-rack/ │ │ └── mui-rack.html │ ├── mui-select/ │ │ └── mui-select.html │ ├── mui-spectrum/ │ │ └── mui-spectrum.html │ ├── mui-vkey/ │ │ └── mui-vkey.html │ ├── mui.html │ └── mui.js ├── examples/ │ ├── chorus/ │ │ └── index.html │ ├── cmp1/ │ │ └── index.html │ ├── converb/ │ │ └── index.html │ ├── eq4/ │ │ └── index.html │ ├── examples.js │ ├── fader/ │ │ └── index.html │ ├── filterbank/ │ │ └── index.html │ ├── fmk1/ │ │ └── index.html │ ├── hellowaax/ │ │ └── index.html │ ├── impulse/ │ │ └── index.html │ ├── index.html │ ├── lab/ │ │ └── index.html │ ├── mui/ │ │ ├── ex-mui-meter.html │ │ ├── ex-mui-pianoroll.html │ │ ├── index.html │ │ └── showcase.html │ ├── noise/ │ │ └── index.html │ ├── simpleosc/ │ │ └── index.html │ ├── sp1/ │ │ └── index.html │ ├── stereodelay/ │ │ └── index.html │ ├── style.css │ ├── workshop/ │ │ └── index.html │ └── wxs1/ │ └── index.html ├── gulpfile.js ├── index.html ├── package.json ├── sound/ │ └── LICENSE ├── src/ │ ├── ktrl.js │ ├── mui/ │ │ ├── bower.json │ │ ├── bower_components/ │ │ │ ├── core-component-page/ │ │ │ │ ├── .bower.json │ │ │ │ ├── README.md │ │ │ │ ├── bower.json │ │ │ │ ├── core-component-page.html │ │ │ │ ├── demo.html │ │ │ │ └── index.html │ │ │ ├── core-icon/ │ │ │ │ ├── .bower.json │ │ │ │ ├── README.md │ │ │ │ ├── bower.json │ │ │ │ ├── core-icon.css │ │ │ │ ├── core-icon.html │ │ │ │ ├── demo.html │ │ │ │ ├── index.html │ │ │ │ └── metadata.html │ │ │ ├── core-icons/ │ │ │ │ ├── .bower.json │ │ │ │ ├── README.md │ │ │ │ ├── av-icons.html │ │ │ │ ├── bower.json │ │ │ │ ├── communication-icons.html │ │ │ │ ├── core-icons.html │ │ │ │ ├── demo.html │ │ │ │ ├── device-icons.html │ │ │ │ ├── editor-icons.html │ │ │ │ ├── hardware-icons.html │ │ │ │ ├── image-icons.html │ │ │ │ ├── index.html │ │ │ │ ├── maps-icons.html │ │ │ │ ├── notification-icons.html │ │ │ │ ├── png-icons.html │ │ │ │ └── social-icons.html │ │ │ ├── core-iconset/ │ │ │ │ ├── .bower.json │ │ │ │ ├── README.md │ │ │ │ ├── bower.json │ │ │ │ ├── core-iconset.html │ │ │ │ ├── demo.html │ │ │ │ └── index.html │ │ │ ├── core-iconset-svg/ │ │ │ │ ├── .bower.json │ │ │ │ ├── README.md │ │ │ │ ├── bower.json │ │ │ │ ├── core-iconset-svg.html │ │ │ │ ├── demo.html │ │ │ │ ├── index.html │ │ │ │ └── svg-sample-icons.html │ │ │ ├── core-meta/ │ │ │ │ ├── .bower.json │ │ │ │ ├── README.md │ │ │ │ ├── bower.json │ │ │ │ ├── core-meta.html │ │ │ │ ├── demo.html │ │ │ │ └── index.html │ │ │ ├── polymer/ │ │ │ │ ├── .bower.json │ │ │ │ ├── README.md │ │ │ │ ├── bower.json │ │ │ │ ├── build.log │ │ │ │ ├── dist/ │ │ │ │ │ └── polymer.html │ │ │ │ ├── layout.html │ │ │ │ ├── polymer.html │ │ │ │ └── polymer.js │ │ │ └── webcomponentsjs/ │ │ │ ├── .bower.json │ │ │ ├── CustomElements.js │ │ │ ├── HTMLImports.js │ │ │ ├── README.md │ │ │ ├── ShadowDOM.js │ │ │ ├── bower.json │ │ │ ├── build.log │ │ │ ├── package.json │ │ │ ├── webcomponents-lite.js │ │ │ └── webcomponents.js │ │ ├── mui-button/ │ │ │ └── mui-button.html │ │ ├── mui-eblock/ │ │ │ └── mui-eblock.html │ │ ├── mui-group/ │ │ │ └── mui-group.html │ │ ├── mui-knob/ │ │ │ └── mui-knob.html │ │ ├── mui-knobh/ │ │ │ └── mui-knobh.html │ │ ├── mui-meter/ │ │ │ └── mui-meter.html │ │ ├── mui-pianoroll/ │ │ │ └── mui-pianoroll.html │ │ ├── mui-rack/ │ │ │ └── mui-rack.html │ │ ├── mui-select/ │ │ │ └── mui-select.html │ │ ├── mui-spectrum/ │ │ │ └── mui-spectrum.html │ │ ├── mui-vkey/ │ │ │ └── mui-vkey.html │ │ ├── mui.html │ │ └── mui.js │ ├── plug_ins/ │ │ ├── CMP1/ │ │ │ └── cmp1.js │ │ ├── Chorus/ │ │ │ └── chorus.js │ │ ├── ConVerb/ │ │ │ └── converb.js │ │ ├── EQ4/ │ │ │ └── eq4.js │ │ ├── FMK1/ │ │ │ └── fmk1.js │ │ ├── Fader/ │ │ │ └── fader.js │ │ ├── FilterBank/ │ │ │ └── filterbank.js │ │ ├── Impulse/ │ │ │ └── Impulse.js │ │ ├── Noise/ │ │ │ └── noise.js │ │ ├── SP1/ │ │ │ └── sp1.js │ │ ├── SimpleOsc/ │ │ │ └── SimpleOsc.js │ │ ├── StereoDelay/ │ │ │ └── StereoDelay.js │ │ └── WXS1/ │ │ └── wxs1.js │ ├── waax.core.js │ ├── waax.extension.js │ ├── waax.js │ ├── waax.timebase.js │ └── waax.util.js └── test/ ├── index.html ├── test-core.js ├── test-setup.js └── test-timebase.js ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ .DS_Store node_modules/ NOTES.md ================================================ FILE: LICENSE ================================================ Copyright 2011-2014 Hongchan Choi. 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: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 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. ================================================ FILE: README.md ================================================ # WAAX Web Audio API eXtension (1.0.0-alpha3) > NOTE: WAAX requires Web Audio API and Web Components. ## Introduction __WAAX__ offers a comprehensive framework for web-based music application. Its goal is to facilitate and support the development of web-based music software. ## Feature Highlights - **Fast** - Built around native Web Audio API nodes. - **Less code** - Web Audio API helpers and utilities - Succint parameter control - Transport and event management (i.e. sequencer) - **Modular and extensible** - WAPL (Web Audio PLug-in): Plug-in for Web Audio API ecosystem - MUI (Musical User Interface): GUI for music apps - **Robust workflow** - Preconfigured with Bower and Gulp ## Prerequisites The complete WAAX development setup requires the following software. Make sure they are installed with appropriate scope and permission. - [Git](http://git-scm.com/) - [Installation](http://git-scm.com/downloads) - [Node.js](http://nodejs.org/) - [Installation](http://nodejs.org/) - [Gulp](http://gulpjs.com/) - [Installation](https://github.com/gulpjs/gulp/blob/master/docs/getting-started.md) ## Installation and Quick Start If you have all the above installed, execute the following commands in the terminal to install and launch WAAX. ~~~bash git clone https://github.com/hoch/WAAX waax cd waax npm install gulp ~~~ Note that WAAX is pre-configured for the optimum development workflow. Type `gulp` in the terminal and then your web browser (Chrome by default) will open the project index page automatically. ## What's Next? Go to the [project landing page](http://hoch.github.io/WAAX) and see what WAAX can do. ## Change Log - 1.0.0-alpha3 + Updated dependencies with latest version: Polymer, Gulp-related utilities. + mui-vkey polyphonic release issue solved. + WXS-1 monophonic legato behavior fixed. + New project landing page and API reference are online. + Using MUI package is significantly simplified. + `bower_components` are now part of MUI package, not WAAX. + License for sound resources is added. - 1.0.0-alpha2 + Updated dependencies with latest version: Gulp, Polymer. + MUI elements updated for new version of Polymer. + Timebase code has been cleaned/refactored. + Updated README and the temporary landing page is removed. + MUI elements and test files are now compatible with FireFox/Safari. + Audio assets are converted to MP3. - [bug] `WXS1` and `FMK1` plug-ins produce distorted sound in FireFox. - [bug] Safari does not load the example with `StereoDelay` plug-in. - 1.0.0-alpha + First alpha version before stable release. + Dropped deprecated components from repository. + New plug-in builder introduced. - r17 (dev) + Last version of dev/experimental revision. ## License and Contact MIT License. Copyright 2011-2014 [Hongchan Choi](http://www.hoch.io) ================================================ FILE: build/mui/bower_components/core-component-page/README.md ================================================ core-component-page =================== See the [component page](http://polymer-project.org/docs/elements/core-elements.html#core-component-page) for more information. Note: this is the vulcanized version of [`core-component-page-dev`](https://github.com/Polymer/core-component-page-dev) (the source). ================================================ FILE: build/mui/bower_components/core-component-page/bower.json ================================================ { "name": "core-component-page", "private": true, "dependencies": { "webcomponentsjs": "Polymer/webcomponentsjs#^0.5.0", "polymer": "Polymer/polymer#^0.5.0" }, "version": "0.5.2" } ================================================ FILE: build/mui/bower_components/core-component-page/core-component-page.html ================================================ ================================================ FILE: build/mui/bower_components/core-component-page/demo.html ================================================ ================================================ FILE: build/mui/bower_components/core-component-page/index.html ================================================ ================================================ FILE: build/mui/bower_components/core-icon/README.md ================================================ core-icon ========= See the [component page](http://polymer-project.org/docs/elements/core-elements.html#core-icon) for more information. ================================================ FILE: build/mui/bower_components/core-icon/bower.json ================================================ { "name": "core-icon", "private": true, "dependencies": { "core-iconset": "Polymer/core-iconset#^0.5.0", "core-icons": "Polymer/core-icons#^0.5.0" }, "version": "0.5.2" } ================================================ FILE: build/mui/bower_components/core-icon/core-icon.css ================================================ /* Copyright (c) 2014 The Polymer Project Authors. All rights reserved. This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt Code distributed by Google as part of the polymer project is also subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt */ html /deep/ core-icon { display: inline-block; vertical-align: middle; background-repeat: no-repeat; fill: currentcolor; position: relative; height: 24px; width: 24px; } ================================================ FILE: build/mui/bower_components/core-icon/core-icon.html ================================================ ================================================ FILE: build/mui/bower_components/core-icon/demo.html ================================================ core-icon ================================================ FILE: build/mui/bower_components/core-icon/index.html ================================================ ================================================ FILE: build/mui/bower_components/core-icon/metadata.html ================================================ ================================================ FILE: build/mui/bower_components/core-icons/README.md ================================================ core-icons ========= See the [component page](http://polymer-project.org/docs/elements/core-elements.html#core-icons) for more information. ================================================ FILE: build/mui/bower_components/core-icons/av-icons.html ================================================ ================================================ FILE: build/mui/bower_components/core-icons/bower.json ================================================ { "name": "core-icons", "private": true, "dependencies": { "core-icon": "Polymer/core-icon#^0.5.0", "core-iconset-svg": "Polymer/core-iconset-svg#^0.5.0", "polymer": "Polymer/polymer#^0.5.0" }, "version": "0.5.2" } ================================================ FILE: build/mui/bower_components/core-icons/communication-icons.html ================================================ ================================================ FILE: build/mui/bower_components/core-icons/core-icons.html ================================================ ================================================ FILE: build/mui/bower_components/core-icons/demo.html ================================================ core-icons ================================================ FILE: build/mui/bower_components/core-icons/device-icons.html ================================================ ================================================ FILE: build/mui/bower_components/core-icons/editor-icons.html ================================================ ================================================ FILE: build/mui/bower_components/core-icons/hardware-icons.html ================================================ ================================================ FILE: build/mui/bower_components/core-icons/image-icons.html ================================================ ================================================ FILE: build/mui/bower_components/core-icons/index.html ================================================ ================================================ FILE: build/mui/bower_components/core-icons/maps-icons.html ================================================ ================================================ FILE: build/mui/bower_components/core-icons/notification-icons.html ================================================ ================================================ FILE: build/mui/bower_components/core-icons/png-icons.html ================================================ ================================================ FILE: build/mui/bower_components/core-icons/social-icons.html ================================================ + ================================================ FILE: build/mui/bower_components/core-iconset/README.md ================================================ core-iconset ============ See the [component page](http://polymer-project.org/docs/elements/core-elements.html#core-iconset) for more information. ================================================ FILE: build/mui/bower_components/core-iconset/bower.json ================================================ { "name": "core-iconset", "private": true, "dependencies": { "polymer": "Polymer/polymer#^0.5.0", "core-meta": "Polymer/core-meta#^0.5.0", "core-icon": "Polymer/core-icon#^0.5.0" }, "version": "0.5.2" } ================================================ FILE: build/mui/bower_components/core-iconset/core-iconset.html ================================================ ================================================ FILE: build/mui/bower_components/core-iconset/demo.html ================================================ core-iconset
================================================ FILE: build/mui/bower_components/core-iconset/index.html ================================================ ================================================ FILE: build/mui/bower_components/core-iconset-svg/README.md ================================================ core-iconset-svg ========= See the [component page](http://polymer-project.org/docs/elements/core-elements.html#core-iconset-svg) for more information. ================================================ FILE: build/mui/bower_components/core-iconset-svg/bower.json ================================================ { "name": "core-iconset-svg", "private": true, "dependencies": { "polymer": "Polymer/polymer#^0.5.0", "core-iconset": "Polymer/core-iconset#^0.5.0" }, "version": "0.5.2" } ================================================ FILE: build/mui/bower_components/core-iconset-svg/core-iconset-svg.html ================================================ ================================================ FILE: build/mui/bower_components/core-iconset-svg/demo.html ================================================ core-iconset-svg ================================================ FILE: build/mui/bower_components/core-iconset-svg/index.html ================================================ ================================================ FILE: build/mui/bower_components/core-iconset-svg/svg-sample-icons.html ================================================ ================================================ FILE: build/mui/bower_components/core-meta/README.md ================================================ core-meta ========= See the [component page](http://polymer-project.org/docs/elements/core-elements.html#core-meta) for more information. ================================================ FILE: build/mui/bower_components/core-meta/bower.json ================================================ { "name": "core-meta", "private": true, "dependencies": { "polymer": "Polymer/polymer#^0.5.0" }, "version": "0.5.2" } ================================================ FILE: build/mui/bower_components/core-meta/core-meta.html ================================================ ================================================ FILE: build/mui/bower_components/core-meta/demo.html ================================================ core-meta

meta-data

meta-data (type: fruit)

================================================ FILE: build/mui/bower_components/core-meta/index.html ================================================ ================================================ FILE: build/mui/bower_components/polymer/README.md ================================================ # Polymer [![Polymer build status](http://www.polymer-project.org/build/polymer-dev/status.png "Polymer build status")](http://build.chromium.org/p/client.polymer/waterfall) ## Brief Overview For more detailed info goto [http://polymer-project.org/](http://polymer-project.org/). Polymer is a new type of library for the web, designed to leverage the existing browser infrastructure to provide the encapsulation and extendability currently only available in JS libraries. Polymer is based on a set of future technologies, including [Shadow DOM](http://w3c.github.io/webcomponents/spec/shadow/), [Custom Elements](http://w3c.github.io/webcomponents/spec/custom/) and Model Driven Views. Currently these technologies are implemented as polyfills or shims, but as browsers adopt these features natively, the platform code that drives Polymer evacipates, leaving only the value-adds. ## Tools & Testing For running tests or building minified files, consult the [tooling information](https://www.polymer-project.org/resources/tooling-strategy.html). ## Releases [Release (tagged) versions](https://github.com/Polymer/polymer/releases) of Polymer include concatenated and minified sources for your convenience. [![Analytics](https://ga-beacon.appspot.com/UA-39334307-2/Polymer/polymer/README)](https://github.com/igrigorik/ga-beacon) ================================================ FILE: build/mui/bower_components/polymer/bower.json ================================================ { "name": "polymer", "description": "Polymer is a new type of library for the web, built on top of Web Components, and designed to leverage the evolving web platform on modern browsers.", "homepage": "http://www.polymer-project.org/", "keywords": [ "util", "client", "browser", "web components", "web-components" ], "author": "Polymer Authors ", "private": true, "dependencies": { "core-component-page": "Polymer/core-component-page#^0.5.0", "webcomponentsjs": "Polymer/webcomponentsjs#^0.5.0" }, "devDependencies": { "tools": "Polymer/tools#master", "web-component-tester": "Polymer/web-component-tester#^1.4.2" }, "version": "0.5.2" } ================================================ FILE: build/mui/bower_components/polymer/build.log ================================================ BUILD LOG --------- Build Time: 2014-12-11T12:46:30 NODEJS INFORMATION ================== nodejs: v0.10.33 grunt: 0.4.5 grunt-audit: 1.0.0 grunt-contrib-concat: 0.5.0 grunt-contrib-copy: 0.7.0 grunt-contrib-uglify: 0.6.0 grunt-string-replace: 1.0.0 REPO REVISIONS ============== polymer-expressions: 197c3a0150e7a13374cfcc72e7066113723a623d polymer-gestures: 17a6304916521be39409af292e8adf899bae0ce7 polymer: a74e9f36526361dccb6df91be439ff9c3e043f41 BUILD HASHES ============ dist/polymer.js: b9ad4c86af79c748cf4ea722f6d56671079fadf7 dist/polymer.min.js: 2f2021ba9682b0bb702ee7fb68fb6fbfd288eac2 dist/layout.html: 348d358a91712ecc2f8811efa430fcd954b4590c ================================================ FILE: build/mui/bower_components/polymer/dist/polymer.html ================================================ ================================================ FILE: build/mui/bower_components/polymer/layout.html ================================================ ================================================ FILE: build/mui/bower_components/polymer/polymer.html ================================================ ================================================ FILE: build/mui/bower_components/polymer/polymer.js ================================================ /** * @license * Copyright (c) 2014 The Polymer Project Authors. All rights reserved. * This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt * The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt * The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt * Code distributed by Google as part of the polymer project is also * subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt */ // @version 0.5.1 window.PolymerGestures = {}; (function(scope) { var HAS_FULL_PATH = false; // test for full event path support var pathTest = document.createElement('meta'); if (pathTest.createShadowRoot) { var sr = pathTest.createShadowRoot(); var s = document.createElement('span'); sr.appendChild(s); pathTest.addEventListener('testpath', function(ev) { if (ev.path) { // if the span is in the event path, then path[0] is the real source for all events HAS_FULL_PATH = ev.path[0] === s; } ev.stopPropagation(); }); var ev = new CustomEvent('testpath', {bubbles: true}); // must add node to DOM to trigger event listener document.head.appendChild(pathTest); s.dispatchEvent(ev); pathTest.parentNode.removeChild(pathTest); sr = s = null; } pathTest = null; var target = { shadow: function(inEl) { if (inEl) { return inEl.shadowRoot || inEl.webkitShadowRoot; } }, canTarget: function(shadow) { return shadow && Boolean(shadow.elementFromPoint); }, targetingShadow: function(inEl) { var s = this.shadow(inEl); if (this.canTarget(s)) { return s; } }, olderShadow: function(shadow) { var os = shadow.olderShadowRoot; if (!os) { var se = shadow.querySelector('shadow'); if (se) { os = se.olderShadowRoot; } } return os; }, allShadows: function(element) { var shadows = [], s = this.shadow(element); while(s) { shadows.push(s); s = this.olderShadow(s); } return shadows; }, searchRoot: function(inRoot, x, y) { var t, st, sr, os; if (inRoot) { t = inRoot.elementFromPoint(x, y); if (t) { // found element, check if it has a ShadowRoot sr = this.targetingShadow(t); } else if (inRoot !== document) { // check for sibling roots sr = this.olderShadow(inRoot); } // search other roots, fall back to light dom element return this.searchRoot(sr, x, y) || t; } }, owner: function(element) { if (!element) { return document; } var s = element; // walk up until you hit the shadow root or document while (s.parentNode) { s = s.parentNode; } // the owner element is expected to be a Document or ShadowRoot if (s.nodeType != Node.DOCUMENT_NODE && s.nodeType != Node.DOCUMENT_FRAGMENT_NODE) { s = document; } return s; }, findTarget: function(inEvent) { if (HAS_FULL_PATH && inEvent.path && inEvent.path.length) { return inEvent.path[0]; } var x = inEvent.clientX, y = inEvent.clientY; // if the listener is in the shadow root, it is much faster to start there var s = this.owner(inEvent.target); // if x, y is not in this root, fall back to document search if (!s.elementFromPoint(x, y)) { s = document; } return this.searchRoot(s, x, y); }, findTouchAction: function(inEvent) { var n; if (HAS_FULL_PATH && inEvent.path && inEvent.path.length) { var path = inEvent.path; for (var i = 0; i < path.length; i++) { n = path[i]; if (n.nodeType === Node.ELEMENT_NODE && n.hasAttribute('touch-action')) { return n.getAttribute('touch-action'); } } } else { n = inEvent.target; while(n) { if (n.nodeType === Node.ELEMENT_NODE && n.hasAttribute('touch-action')) { return n.getAttribute('touch-action'); } n = n.parentNode || n.host; } } // auto is default return "auto"; }, LCA: function(a, b) { if (a === b) { return a; } if (a && !b) { return a; } if (b && !a) { return b; } if (!b && !a) { return document; } // fast case, a is a direct descendant of b or vice versa if (a.contains && a.contains(b)) { return a; } if (b.contains && b.contains(a)) { return b; } var adepth = this.depth(a); var bdepth = this.depth(b); var d = adepth - bdepth; if (d >= 0) { a = this.walk(a, d); } else { b = this.walk(b, -d); } while (a && b && a !== b) { a = a.parentNode || a.host; b = b.parentNode || b.host; } return a; }, walk: function(n, u) { for (var i = 0; n && (i < u); i++) { n = n.parentNode || n.host; } return n; }, depth: function(n) { var d = 0; while(n) { d++; n = n.parentNode || n.host; } return d; }, deepContains: function(a, b) { var common = this.LCA(a, b); // if a is the common ancestor, it must "deeply" contain b return common === a; }, insideNode: function(node, x, y) { var rect = node.getBoundingClientRect(); return (rect.left <= x) && (x <= rect.right) && (rect.top <= y) && (y <= rect.bottom); }, path: function(event) { var p; if (HAS_FULL_PATH && event.path && event.path.length) { p = event.path; } else { p = []; var n = this.findTarget(event); while (n) { p.push(n); n = n.parentNode || n.host; } } return p; } }; scope.targetFinding = target; /** * Given an event, finds the "deepest" node that could have been the original target before ShadowDOM retargetting * * @param {Event} Event An event object with clientX and clientY properties * @return {Element} The probable event origninator */ scope.findTarget = target.findTarget.bind(target); /** * Determines if the "container" node deeply contains the "containee" node, including situations where the "containee" is contained by one or more ShadowDOM * roots. * * @param {Node} container * @param {Node} containee * @return {Boolean} */ scope.deepContains = target.deepContains.bind(target); /** * Determines if the x/y position is inside the given node. * * Example: * * function upHandler(event) { * var innode = PolymerGestures.insideNode(event.target, event.clientX, event.clientY); * if (innode) { * // wait for tap? * } else { * // tap will never happen * } * } * * @param {Node} node * @param {Number} x Screen X position * @param {Number} y screen Y position * @return {Boolean} */ scope.insideNode = target.insideNode; })(window.PolymerGestures); (function() { function shadowSelector(v) { return 'html /deep/ ' + selector(v); } function selector(v) { return '[touch-action="' + v + '"]'; } function rule(v) { return '{ -ms-touch-action: ' + v + '; touch-action: ' + v + ';}'; } var attrib2css = [ 'none', 'auto', 'pan-x', 'pan-y', { rule: 'pan-x pan-y', selectors: [ 'pan-x pan-y', 'pan-y pan-x' ] }, 'manipulation' ]; var styles = ''; // only install stylesheet if the browser has touch action support var hasTouchAction = typeof document.head.style.touchAction === 'string'; // only add shadow selectors if shadowdom is supported var hasShadowRoot = !window.ShadowDOMPolyfill && document.head.createShadowRoot; if (hasTouchAction) { attrib2css.forEach(function(r) { if (String(r) === r) { styles += selector(r) + rule(r) + '\n'; if (hasShadowRoot) { styles += shadowSelector(r) + rule(r) + '\n'; } } else { styles += r.selectors.map(selector) + rule(r.rule) + '\n'; if (hasShadowRoot) { styles += r.selectors.map(shadowSelector) + rule(r.rule) + '\n'; } } }); var el = document.createElement('style'); el.textContent = styles; document.head.appendChild(el); } })(); /** * This is the constructor for new PointerEvents. * * New Pointer Events must be given a type, and an optional dictionary of * initialization properties. * * Due to certain platform requirements, events returned from the constructor * identify as MouseEvents. * * @constructor * @param {String} inType The type of the event to create. * @param {Object} [inDict] An optional dictionary of initial event properties. * @return {Event} A new PointerEvent of type `inType` and initialized with properties from `inDict`. */ (function(scope) { var MOUSE_PROPS = [ 'bubbles', 'cancelable', 'view', 'detail', 'screenX', 'screenY', 'clientX', 'clientY', 'ctrlKey', 'altKey', 'shiftKey', 'metaKey', 'button', 'relatedTarget', 'pageX', 'pageY' ]; var MOUSE_DEFAULTS = [ false, false, null, null, 0, 0, 0, 0, false, false, false, false, 0, null, 0, 0 ]; var NOP_FACTORY = function(){ return function(){}; }; var eventFactory = { // TODO(dfreedm): this is overridden by tap recognizer, needs review preventTap: NOP_FACTORY, makeBaseEvent: function(inType, inDict) { var e = document.createEvent('Event'); e.initEvent(inType, inDict.bubbles || false, inDict.cancelable || false); e.preventTap = eventFactory.preventTap(e); return e; }, makeGestureEvent: function(inType, inDict) { inDict = inDict || Object.create(null); var e = this.makeBaseEvent(inType, inDict); for (var i = 0, keys = Object.keys(inDict), k; i < keys.length; i++) { k = keys[i]; e[k] = inDict[k]; } return e; }, makePointerEvent: function(inType, inDict) { inDict = inDict || Object.create(null); var e = this.makeBaseEvent(inType, inDict); // define inherited MouseEvent properties for(var i = 0, p; i < MOUSE_PROPS.length; i++) { p = MOUSE_PROPS[i]; e[p] = inDict[p] || MOUSE_DEFAULTS[i]; } e.buttons = inDict.buttons || 0; // Spec requires that pointers without pressure specified use 0.5 for down // state and 0 for up state. var pressure = 0; if (inDict.pressure) { pressure = inDict.pressure; } else { pressure = e.buttons ? 0.5 : 0; } // add x/y properties aliased to clientX/Y e.x = e.clientX; e.y = e.clientY; // define the properties of the PointerEvent interface e.pointerId = inDict.pointerId || 0; e.width = inDict.width || 0; e.height = inDict.height || 0; e.pressure = pressure; e.tiltX = inDict.tiltX || 0; e.tiltY = inDict.tiltY || 0; e.pointerType = inDict.pointerType || ''; e.hwTimestamp = inDict.hwTimestamp || 0; e.isPrimary = inDict.isPrimary || false; e._source = inDict._source || ''; return e; } }; scope.eventFactory = eventFactory; })(window.PolymerGestures); /** * This module implements an map of pointer states */ (function(scope) { var USE_MAP = window.Map && window.Map.prototype.forEach; var POINTERS_FN = function(){ return this.size; }; function PointerMap() { if (USE_MAP) { var m = new Map(); m.pointers = POINTERS_FN; return m; } else { this.keys = []; this.values = []; } } PointerMap.prototype = { set: function(inId, inEvent) { var i = this.keys.indexOf(inId); if (i > -1) { this.values[i] = inEvent; } else { this.keys.push(inId); this.values.push(inEvent); } }, has: function(inId) { return this.keys.indexOf(inId) > -1; }, 'delete': function(inId) { var i = this.keys.indexOf(inId); if (i > -1) { this.keys.splice(i, 1); this.values.splice(i, 1); } }, get: function(inId) { var i = this.keys.indexOf(inId); return this.values[i]; }, clear: function() { this.keys.length = 0; this.values.length = 0; }, // return value, key, map forEach: function(callback, thisArg) { this.values.forEach(function(v, i) { callback.call(thisArg, v, this.keys[i], this); }, this); }, pointers: function() { return this.keys.length; } }; scope.PointerMap = PointerMap; })(window.PolymerGestures); (function(scope) { var CLONE_PROPS = [ // MouseEvent 'bubbles', 'cancelable', 'view', 'detail', 'screenX', 'screenY', 'clientX', 'clientY', 'ctrlKey', 'altKey', 'shiftKey', 'metaKey', 'button', 'relatedTarget', // DOM Level 3 'buttons', // PointerEvent 'pointerId', 'width', 'height', 'pressure', 'tiltX', 'tiltY', 'pointerType', 'hwTimestamp', 'isPrimary', // event instance 'type', 'target', 'currentTarget', 'which', 'pageX', 'pageY', 'timeStamp', // gesture addons 'preventTap', 'tapPrevented', '_source' ]; var CLONE_DEFAULTS = [ // MouseEvent false, false, null, null, 0, 0, 0, 0, false, false, false, false, 0, null, // DOM Level 3 0, // PointerEvent 0, 0, 0, 0, 0, 0, '', 0, false, // event instance '', null, null, 0, 0, 0, 0, function(){}, false ]; var HAS_SVG_INSTANCE = (typeof SVGElementInstance !== 'undefined'); var eventFactory = scope.eventFactory; // set of recognizers to run for the currently handled event var currentGestures; /** * This module is for normalizing events. Mouse and Touch events will be * collected here, and fire PointerEvents that have the same semantics, no * matter the source. * Events fired: * - pointerdown: a pointing is added * - pointerup: a pointer is removed * - pointermove: a pointer is moved * - pointerover: a pointer crosses into an element * - pointerout: a pointer leaves an element * - pointercancel: a pointer will no longer generate events */ var dispatcher = { IS_IOS: false, pointermap: new scope.PointerMap(), requiredGestures: new scope.PointerMap(), eventMap: Object.create(null), // Scope objects for native events. // This exists for ease of testing. eventSources: Object.create(null), eventSourceList: [], gestures: [], // map gesture event -> {listeners: int, index: gestures[int]} dependencyMap: { // make sure down and up are in the map to trigger "register" down: {listeners: 0, index: -1}, up: {listeners: 0, index: -1} }, gestureQueue: [], /** * Add a new event source that will generate pointer events. * * `inSource` must contain an array of event names named `events`, and * functions with the names specified in the `events` array. * @param {string} name A name for the event source * @param {Object} source A new source of platform events. */ registerSource: function(name, source) { var s = source; var newEvents = s.events; if (newEvents) { newEvents.forEach(function(e) { if (s[e]) { this.eventMap[e] = s[e].bind(s); } }, this); this.eventSources[name] = s; this.eventSourceList.push(s); } }, registerGesture: function(name, source) { var obj = Object.create(null); obj.listeners = 0; obj.index = this.gestures.length; for (var i = 0, g; i < source.exposes.length; i++) { g = source.exposes[i].toLowerCase(); this.dependencyMap[g] = obj; } this.gestures.push(source); }, register: function(element, initial) { var l = this.eventSourceList.length; for (var i = 0, es; (i < l) && (es = this.eventSourceList[i]); i++) { // call eventsource register es.register.call(es, element, initial); } }, unregister: function(element) { var l = this.eventSourceList.length; for (var i = 0, es; (i < l) && (es = this.eventSourceList[i]); i++) { // call eventsource register es.unregister.call(es, element); } }, // EVENTS down: function(inEvent) { this.requiredGestures.set(inEvent.pointerId, currentGestures); this.fireEvent('down', inEvent); }, move: function(inEvent) { // pipe move events into gesture queue directly inEvent.type = 'move'; this.fillGestureQueue(inEvent); }, up: function(inEvent) { this.fireEvent('up', inEvent); this.requiredGestures.delete(inEvent.pointerId); }, cancel: function(inEvent) { inEvent.tapPrevented = true; this.fireEvent('up', inEvent); this.requiredGestures.delete(inEvent.pointerId); }, addGestureDependency: function(node, currentGestures) { var gesturesWanted = node._pgEvents; if (gesturesWanted && currentGestures) { var gk = Object.keys(gesturesWanted); for (var i = 0, r, ri, g; i < gk.length; i++) { // gesture g = gk[i]; if (gesturesWanted[g] > 0) { // lookup gesture recognizer r = this.dependencyMap[g]; // recognizer index ri = r ? r.index : -1; currentGestures[ri] = true; } } } }, // LISTENER LOGIC eventHandler: function(inEvent) { // This is used to prevent multiple dispatch of events from // platform events. This can happen when two elements in different scopes // are set up to create pointer events, which is relevant to Shadow DOM. var type = inEvent.type; // only generate the list of desired events on "down" if (type === 'touchstart' || type === 'mousedown' || type === 'pointerdown' || type === 'MSPointerDown') { if (!inEvent._handledByPG) { currentGestures = {}; } // in IOS mode, there is only a listener on the document, so this is not re-entrant if (this.IS_IOS) { var ev = inEvent; if (type === 'touchstart') { var ct = inEvent.changedTouches[0]; // set up a fake event to give to the path builder ev = {target: inEvent.target, clientX: ct.clientX, clientY: ct.clientY, path: inEvent.path}; } // use event path if available, otherwise build a path from target finding var nodes = inEvent.path || scope.targetFinding.path(ev); for (var i = 0, n; i < nodes.length; i++) { n = nodes[i]; this.addGestureDependency(n, currentGestures); } } else { this.addGestureDependency(inEvent.currentTarget, currentGestures); } } if (inEvent._handledByPG) { return; } var fn = this.eventMap && this.eventMap[type]; if (fn) { fn(inEvent); } inEvent._handledByPG = true; }, // set up event listeners listen: function(target, events) { for (var i = 0, l = events.length, e; (i < l) && (e = events[i]); i++) { this.addEvent(target, e); } }, // remove event listeners unlisten: function(target, events) { for (var i = 0, l = events.length, e; (i < l) && (e = events[i]); i++) { this.removeEvent(target, e); } }, addEvent: function(target, eventName) { target.addEventListener(eventName, this.boundHandler); }, removeEvent: function(target, eventName) { target.removeEventListener(eventName, this.boundHandler); }, // EVENT CREATION AND TRACKING /** * Creates a new Event of type `inType`, based on the information in * `inEvent`. * * @param {string} inType A string representing the type of event to create * @param {Event} inEvent A platform event with a target * @return {Event} A PointerEvent of type `inType` */ makeEvent: function(inType, inEvent) { var e = eventFactory.makePointerEvent(inType, inEvent); e.preventDefault = inEvent.preventDefault; e.tapPrevented = inEvent.tapPrevented; e._target = e._target || inEvent.target; return e; }, // make and dispatch an event in one call fireEvent: function(inType, inEvent) { var e = this.makeEvent(inType, inEvent); return this.dispatchEvent(e); }, /** * Returns a snapshot of inEvent, with writable properties. * * @param {Event} inEvent An event that contains properties to copy. * @return {Object} An object containing shallow copies of `inEvent`'s * properties. */ cloneEvent: function(inEvent) { var eventCopy = Object.create(null), p; for (var i = 0; i < CLONE_PROPS.length; i++) { p = CLONE_PROPS[i]; eventCopy[p] = inEvent[p] || CLONE_DEFAULTS[i]; // Work around SVGInstanceElement shadow tree // Return the element that is represented by the instance for Safari, Chrome, IE. // This is the behavior implemented by Firefox. if (p === 'target' || p === 'relatedTarget') { if (HAS_SVG_INSTANCE && eventCopy[p] instanceof SVGElementInstance) { eventCopy[p] = eventCopy[p].correspondingUseElement; } } } // keep the semantics of preventDefault eventCopy.preventDefault = function() { inEvent.preventDefault(); }; return eventCopy; }, /** * Dispatches the event to its target. * * @param {Event} inEvent The event to be dispatched. * @return {Boolean} True if an event handler returns true, false otherwise. */ dispatchEvent: function(inEvent) { var t = inEvent._target; if (t) { t.dispatchEvent(inEvent); // clone the event for the gesture system to process // clone after dispatch to pick up gesture prevention code var clone = this.cloneEvent(inEvent); clone.target = t; this.fillGestureQueue(clone); } }, gestureTrigger: function() { // process the gesture queue for (var i = 0, e, rg; i < this.gestureQueue.length; i++) { e = this.gestureQueue[i]; rg = e._requiredGestures; if (rg) { for (var j = 0, g, fn; j < this.gestures.length; j++) { // only run recognizer if an element in the source event's path is listening for those gestures if (rg[j]) { g = this.gestures[j]; fn = g[e.type]; if (fn) { fn.call(g, e); } } } } } this.gestureQueue.length = 0; }, fillGestureQueue: function(ev) { // only trigger the gesture queue once if (!this.gestureQueue.length) { requestAnimationFrame(this.boundGestureTrigger); } ev._requiredGestures = this.requiredGestures.get(ev.pointerId); this.gestureQueue.push(ev); } }; dispatcher.boundHandler = dispatcher.eventHandler.bind(dispatcher); dispatcher.boundGestureTrigger = dispatcher.gestureTrigger.bind(dispatcher); scope.dispatcher = dispatcher; /** * Listen for `gesture` on `node` with the `handler` function * * If `handler` is the first listener for `gesture`, the underlying gesture recognizer is then enabled. * * @param {Element} node * @param {string} gesture * @return Boolean `gesture` is a valid gesture */ scope.activateGesture = function(node, gesture) { var g = gesture.toLowerCase(); var dep = dispatcher.dependencyMap[g]; if (dep) { var recognizer = dispatcher.gestures[dep.index]; if (!node._pgListeners) { dispatcher.register(node); node._pgListeners = 0; } // TODO(dfreedm): re-evaluate bookkeeping to avoid using attributes if (recognizer) { var touchAction = recognizer.defaultActions && recognizer.defaultActions[g]; var actionNode; switch(node.nodeType) { case Node.ELEMENT_NODE: actionNode = node; break; case Node.DOCUMENT_FRAGMENT_NODE: actionNode = node.host; break; default: actionNode = null; break; } if (touchAction && actionNode && !actionNode.hasAttribute('touch-action')) { actionNode.setAttribute('touch-action', touchAction); } } if (!node._pgEvents) { node._pgEvents = {}; } node._pgEvents[g] = (node._pgEvents[g] || 0) + 1; node._pgListeners++; } return Boolean(dep); }; /** * * Listen for `gesture` from `node` with `handler` function. * * @param {Element} node * @param {string} gesture * @param {Function} handler * @param {Boolean} capture */ scope.addEventListener = function(node, gesture, handler, capture) { if (handler) { scope.activateGesture(node, gesture); node.addEventListener(gesture, handler, capture); } }; /** * Tears down the gesture configuration for `node` * * If `handler` is the last listener for `gesture`, the underlying gesture recognizer is disabled. * * @param {Element} node * @param {string} gesture * @return Boolean `gesture` is a valid gesture */ scope.deactivateGesture = function(node, gesture) { var g = gesture.toLowerCase(); var dep = dispatcher.dependencyMap[g]; if (dep) { if (node._pgListeners > 0) { node._pgListeners--; } if (node._pgListeners === 0) { dispatcher.unregister(node); } if (node._pgEvents) { if (node._pgEvents[g] > 0) { node._pgEvents[g]--; } else { node._pgEvents[g] = 0; } } } return Boolean(dep); }; /** * Stop listening for `gesture` from `node` with `handler` function. * * @param {Element} node * @param {string} gesture * @param {Function} handler * @param {Boolean} capture */ scope.removeEventListener = function(node, gesture, handler, capture) { if (handler) { scope.deactivateGesture(node, gesture); node.removeEventListener(gesture, handler, capture); } }; })(window.PolymerGestures); (function(scope) { var dispatcher = scope.dispatcher; var pointermap = dispatcher.pointermap; // radius around touchend that swallows mouse events var DEDUP_DIST = 25; var WHICH_TO_BUTTONS = [0, 1, 4, 2]; var CURRENT_BUTTONS = 0; var FIREFOX_LINUX = /Linux.*Firefox\//i; var HAS_BUTTONS = (function() { // firefox on linux returns spec-incorrect values for mouseup.buttons // https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent.buttons#See_also // https://codereview.chromium.org/727593003/#msg16 if (FIREFOX_LINUX.test(navigator.userAgent)) { return false; } try { return new MouseEvent('test', {buttons: 1}).buttons === 1; } catch (e) { return false; } })(); // handler block for native mouse events var mouseEvents = { POINTER_ID: 1, POINTER_TYPE: 'mouse', events: [ 'mousedown', 'mousemove', 'mouseup' ], exposes: [ 'down', 'up', 'move' ], register: function(target) { dispatcher.listen(target, this.events); }, unregister: function(target) { if (target === document) { return; } dispatcher.unlisten(target, this.events); }, lastTouches: [], // collide with the global mouse listener isEventSimulatedFromTouch: function(inEvent) { var lts = this.lastTouches; var x = inEvent.clientX, y = inEvent.clientY; for (var i = 0, l = lts.length, t; i < l && (t = lts[i]); i++) { // simulated mouse events will be swallowed near a primary touchend var dx = Math.abs(x - t.x), dy = Math.abs(y - t.y); if (dx <= DEDUP_DIST && dy <= DEDUP_DIST) { return true; } } }, prepareEvent: function(inEvent) { var e = dispatcher.cloneEvent(inEvent); e.pointerId = this.POINTER_ID; e.isPrimary = true; e.pointerType = this.POINTER_TYPE; e._source = 'mouse'; if (!HAS_BUTTONS) { var type = inEvent.type; var bit = WHICH_TO_BUTTONS[inEvent.which] || 0; if (type === 'mousedown') { CURRENT_BUTTONS |= bit; } else if (type === 'mouseup') { CURRENT_BUTTONS &= ~bit; } e.buttons = CURRENT_BUTTONS; } return e; }, mousedown: function(inEvent) { if (!this.isEventSimulatedFromTouch(inEvent)) { var p = pointermap.has(this.POINTER_ID); var e = this.prepareEvent(inEvent); e.target = scope.findTarget(inEvent); pointermap.set(this.POINTER_ID, e.target); dispatcher.down(e); } }, mousemove: function(inEvent) { if (!this.isEventSimulatedFromTouch(inEvent)) { var target = pointermap.get(this.POINTER_ID); if (target) { var e = this.prepareEvent(inEvent); e.target = target; // handle case where we missed a mouseup if ((HAS_BUTTONS ? e.buttons : e.which) === 0) { if (!HAS_BUTTONS) { CURRENT_BUTTONS = e.buttons = 0; } dispatcher.cancel(e); this.cleanupMouse(e.buttons); } else { dispatcher.move(e); } } } }, mouseup: function(inEvent) { if (!this.isEventSimulatedFromTouch(inEvent)) { var e = this.prepareEvent(inEvent); e.relatedTarget = scope.findTarget(inEvent); e.target = pointermap.get(this.POINTER_ID); dispatcher.up(e); this.cleanupMouse(e.buttons); } }, cleanupMouse: function(buttons) { if (buttons === 0) { pointermap.delete(this.POINTER_ID); } } }; scope.mouseEvents = mouseEvents; })(window.PolymerGestures); (function(scope) { var dispatcher = scope.dispatcher; var allShadows = scope.targetFinding.allShadows.bind(scope.targetFinding); var pointermap = dispatcher.pointermap; var touchMap = Array.prototype.map.call.bind(Array.prototype.map); // This should be long enough to ignore compat mouse events made by touch var DEDUP_TIMEOUT = 2500; var DEDUP_DIST = 25; var CLICK_COUNT_TIMEOUT = 200; var HYSTERESIS = 20; var ATTRIB = 'touch-action'; // TODO(dfreedm): disable until http://crbug.com/399765 is resolved // var HAS_TOUCH_ACTION = ATTRIB in document.head.style; var HAS_TOUCH_ACTION = false; // handler block for native touch events var touchEvents = { IS_IOS: false, events: [ 'touchstart', 'touchmove', 'touchend', 'touchcancel' ], exposes: [ 'down', 'up', 'move' ], register: function(target, initial) { if (this.IS_IOS ? initial : !initial) { dispatcher.listen(target, this.events); } }, unregister: function(target) { if (!this.IS_IOS) { dispatcher.unlisten(target, this.events); } }, scrollTypes: { EMITTER: 'none', XSCROLLER: 'pan-x', YSCROLLER: 'pan-y', }, touchActionToScrollType: function(touchAction) { var t = touchAction; var st = this.scrollTypes; if (t === st.EMITTER) { return 'none'; } else if (t === st.XSCROLLER) { return 'X'; } else if (t === st.YSCROLLER) { return 'Y'; } else { return 'XY'; } }, POINTER_TYPE: 'touch', firstTouch: null, isPrimaryTouch: function(inTouch) { return this.firstTouch === inTouch.identifier; }, setPrimaryTouch: function(inTouch) { // set primary touch if there no pointers, or the only pointer is the mouse if (pointermap.pointers() === 0 || (pointermap.pointers() === 1 && pointermap.has(1))) { this.firstTouch = inTouch.identifier; this.firstXY = {X: inTouch.clientX, Y: inTouch.clientY}; this.firstTarget = inTouch.target; this.scrolling = null; this.cancelResetClickCount(); } }, removePrimaryPointer: function(inPointer) { if (inPointer.isPrimary) { this.firstTouch = null; this.firstXY = null; this.resetClickCount(); } }, clickCount: 0, resetId: null, resetClickCount: function() { var fn = function() { this.clickCount = 0; this.resetId = null; }.bind(this); this.resetId = setTimeout(fn, CLICK_COUNT_TIMEOUT); }, cancelResetClickCount: function() { if (this.resetId) { clearTimeout(this.resetId); } }, typeToButtons: function(type) { var ret = 0; if (type === 'touchstart' || type === 'touchmove') { ret = 1; } return ret; }, findTarget: function(touch, id) { if (this.currentTouchEvent.type === 'touchstart') { if (this.isPrimaryTouch(touch)) { var fastPath = { clientX: touch.clientX, clientY: touch.clientY, path: this.currentTouchEvent.path, target: this.currentTouchEvent.target }; return scope.findTarget(fastPath); } else { return scope.findTarget(touch); } } // reuse target we found in touchstart return pointermap.get(id); }, touchToPointer: function(inTouch) { var cte = this.currentTouchEvent; var e = dispatcher.cloneEvent(inTouch); // Spec specifies that pointerId 1 is reserved for Mouse. // Touch identifiers can start at 0. // Add 2 to the touch identifier for compatibility. var id = e.pointerId = inTouch.identifier + 2; e.target = this.findTarget(inTouch, id); e.bubbles = true; e.cancelable = true; e.detail = this.clickCount; e.buttons = this.typeToButtons(cte.type); e.width = inTouch.webkitRadiusX || inTouch.radiusX || 0; e.height = inTouch.webkitRadiusY || inTouch.radiusY || 0; e.pressure = inTouch.webkitForce || inTouch.force || 0.5; e.isPrimary = this.isPrimaryTouch(inTouch); e.pointerType = this.POINTER_TYPE; e._source = 'touch'; // forward touch preventDefaults var self = this; e.preventDefault = function() { self.scrolling = false; self.firstXY = null; cte.preventDefault(); }; return e; }, processTouches: function(inEvent, inFunction) { var tl = inEvent.changedTouches; this.currentTouchEvent = inEvent; for (var i = 0, t, p; i < tl.length; i++) { t = tl[i]; p = this.touchToPointer(t); if (inEvent.type === 'touchstart') { pointermap.set(p.pointerId, p.target); } if (pointermap.has(p.pointerId)) { inFunction.call(this, p); } if (inEvent.type === 'touchend' || inEvent._cancel) { this.cleanUpPointer(p); } } }, // For single axis scrollers, determines whether the element should emit // pointer events or behave as a scroller shouldScroll: function(inEvent) { if (this.firstXY) { var ret; var touchAction = scope.targetFinding.findTouchAction(inEvent); var scrollAxis = this.touchActionToScrollType(touchAction); if (scrollAxis === 'none') { // this element is a touch-action: none, should never scroll ret = false; } else if (scrollAxis === 'XY') { // this element should always scroll ret = true; } else { var t = inEvent.changedTouches[0]; // check the intended scroll axis, and other axis var a = scrollAxis; var oa = scrollAxis === 'Y' ? 'X' : 'Y'; var da = Math.abs(t['client' + a] - this.firstXY[a]); var doa = Math.abs(t['client' + oa] - this.firstXY[oa]); // if delta in the scroll axis > delta other axis, scroll instead of // making events ret = da >= doa; } return ret; } }, findTouch: function(inTL, inId) { for (var i = 0, l = inTL.length, t; i < l && (t = inTL[i]); i++) { if (t.identifier === inId) { return true; } } }, // In some instances, a touchstart can happen without a touchend. This // leaves the pointermap in a broken state. // Therefore, on every touchstart, we remove the touches that did not fire a // touchend event. // To keep state globally consistent, we fire a // pointercancel for this "abandoned" touch vacuumTouches: function(inEvent) { var tl = inEvent.touches; // pointermap.pointers() should be < tl.length here, as the touchstart has not // been processed yet. if (pointermap.pointers() >= tl.length) { var d = []; pointermap.forEach(function(value, key) { // Never remove pointerId == 1, which is mouse. // Touch identifiers are 2 smaller than their pointerId, which is the // index in pointermap. if (key !== 1 && !this.findTouch(tl, key - 2)) { var p = value; d.push(p); } }, this); d.forEach(function(p) { this.cancel(p); pointermap.delete(p.pointerId); }, this); } }, touchstart: function(inEvent) { this.vacuumTouches(inEvent); this.setPrimaryTouch(inEvent.changedTouches[0]); this.dedupSynthMouse(inEvent); if (!this.scrolling) { this.clickCount++; this.processTouches(inEvent, this.down); } }, down: function(inPointer) { dispatcher.down(inPointer); }, touchmove: function(inEvent) { if (HAS_TOUCH_ACTION) { // touchevent.cancelable == false is sent when the page is scrolling under native Touch Action in Chrome 36 // https://groups.google.com/a/chromium.org/d/msg/input-dev/wHnyukcYBcA/b9kmtwM1jJQJ if (inEvent.cancelable) { this.processTouches(inEvent, this.move); } } else { if (!this.scrolling) { if (this.scrolling === null && this.shouldScroll(inEvent)) { this.scrolling = true; } else { this.scrolling = false; inEvent.preventDefault(); this.processTouches(inEvent, this.move); } } else if (this.firstXY) { var t = inEvent.changedTouches[0]; var dx = t.clientX - this.firstXY.X; var dy = t.clientY - this.firstXY.Y; var dd = Math.sqrt(dx * dx + dy * dy); if (dd >= HYSTERESIS) { this.touchcancel(inEvent); this.scrolling = true; this.firstXY = null; } } } }, move: function(inPointer) { dispatcher.move(inPointer); }, touchend: function(inEvent) { this.dedupSynthMouse(inEvent); this.processTouches(inEvent, this.up); }, up: function(inPointer) { inPointer.relatedTarget = scope.findTarget(inPointer); dispatcher.up(inPointer); }, cancel: function(inPointer) { dispatcher.cancel(inPointer); }, touchcancel: function(inEvent) { inEvent._cancel = true; this.processTouches(inEvent, this.cancel); }, cleanUpPointer: function(inPointer) { pointermap['delete'](inPointer.pointerId); this.removePrimaryPointer(inPointer); }, // prevent synth mouse events from creating pointer events dedupSynthMouse: function(inEvent) { var lts = scope.mouseEvents.lastTouches; var t = inEvent.changedTouches[0]; // only the primary finger will synth mouse events if (this.isPrimaryTouch(t)) { // remember x/y of last touch var lt = {x: t.clientX, y: t.clientY}; lts.push(lt); var fn = (function(lts, lt){ var i = lts.indexOf(lt); if (i > -1) { lts.splice(i, 1); } }).bind(null, lts, lt); setTimeout(fn, DEDUP_TIMEOUT); } } }; // prevent "ghost clicks" that come from elements that were removed in a touch handler var STOP_PROP_FN = Event.prototype.stopImmediatePropagation || Event.prototype.stopPropagation; document.addEventListener('click', function(ev) { var x = ev.clientX, y = ev.clientY; // check if a click is within DEDUP_DIST px radius of the touchstart var closeTo = function(touch) { var dx = Math.abs(x - touch.x), dy = Math.abs(y - touch.y); return (dx <= DEDUP_DIST && dy <= DEDUP_DIST); }; // if click coordinates are close to touch coordinates, assume the click came from a touch var wasTouched = scope.mouseEvents.lastTouches.some(closeTo); // if the click came from touch, and the touchstart target is not in the path of the click event, // then the touchstart target was probably removed, and the click should be "busted" var path = scope.targetFinding.path(ev); if (wasTouched) { for (var i = 0; i < path.length; i++) { if (path[i] === touchEvents.firstTarget) { return; } } ev.preventDefault(); STOP_PROP_FN.call(ev); } }, true); scope.touchEvents = touchEvents; })(window.PolymerGestures); (function(scope) { var dispatcher = scope.dispatcher; var pointermap = dispatcher.pointermap; var HAS_BITMAP_TYPE = window.MSPointerEvent && typeof window.MSPointerEvent.MSPOINTER_TYPE_MOUSE === 'number'; var msEvents = { events: [ 'MSPointerDown', 'MSPointerMove', 'MSPointerUp', 'MSPointerCancel', ], register: function(target) { dispatcher.listen(target, this.events); }, unregister: function(target) { if (target === document) { return; } dispatcher.unlisten(target, this.events); }, POINTER_TYPES: [ '', 'unavailable', 'touch', 'pen', 'mouse' ], prepareEvent: function(inEvent) { var e = inEvent; e = dispatcher.cloneEvent(inEvent); if (HAS_BITMAP_TYPE) { e.pointerType = this.POINTER_TYPES[inEvent.pointerType]; } e._source = 'ms'; return e; }, cleanup: function(id) { pointermap['delete'](id); }, MSPointerDown: function(inEvent) { var e = this.prepareEvent(inEvent); e.target = scope.findTarget(inEvent); pointermap.set(inEvent.pointerId, e.target); dispatcher.down(e); }, MSPointerMove: function(inEvent) { var target = pointermap.get(inEvent.pointerId); if (target) { var e = this.prepareEvent(inEvent); e.target = target; dispatcher.move(e); } }, MSPointerUp: function(inEvent) { var e = this.prepareEvent(inEvent); e.relatedTarget = scope.findTarget(inEvent); e.target = pointermap.get(e.pointerId); dispatcher.up(e); this.cleanup(inEvent.pointerId); }, MSPointerCancel: function(inEvent) { var e = this.prepareEvent(inEvent); e.relatedTarget = scope.findTarget(inEvent); e.target = pointermap.get(e.pointerId); dispatcher.cancel(e); this.cleanup(inEvent.pointerId); } }; scope.msEvents = msEvents; })(window.PolymerGestures); (function(scope) { var dispatcher = scope.dispatcher; var pointermap = dispatcher.pointermap; var pointerEvents = { events: [ 'pointerdown', 'pointermove', 'pointerup', 'pointercancel' ], prepareEvent: function(inEvent) { var e = dispatcher.cloneEvent(inEvent); e._source = 'pointer'; return e; }, register: function(target) { dispatcher.listen(target, this.events); }, unregister: function(target) { if (target === document) { return; } dispatcher.unlisten(target, this.events); }, cleanup: function(id) { pointermap['delete'](id); }, pointerdown: function(inEvent) { var e = this.prepareEvent(inEvent); e.target = scope.findTarget(inEvent); pointermap.set(e.pointerId, e.target); dispatcher.down(e); }, pointermove: function(inEvent) { var target = pointermap.get(inEvent.pointerId); if (target) { var e = this.prepareEvent(inEvent); e.target = target; dispatcher.move(e); } }, pointerup: function(inEvent) { var e = this.prepareEvent(inEvent); e.relatedTarget = scope.findTarget(inEvent); e.target = pointermap.get(e.pointerId); dispatcher.up(e); this.cleanup(inEvent.pointerId); }, pointercancel: function(inEvent) { var e = this.prepareEvent(inEvent); e.relatedTarget = scope.findTarget(inEvent); e.target = pointermap.get(e.pointerId); dispatcher.cancel(e); this.cleanup(inEvent.pointerId); } }; scope.pointerEvents = pointerEvents; })(window.PolymerGestures); /** * This module contains the handlers for native platform events. * From here, the dispatcher is called to create unified pointer events. * Included are touch events (v1), mouse events, and MSPointerEvents. */ (function(scope) { var dispatcher = scope.dispatcher; var nav = window.navigator; if (window.PointerEvent) { dispatcher.registerSource('pointer', scope.pointerEvents); } else if (nav.msPointerEnabled) { dispatcher.registerSource('ms', scope.msEvents); } else { dispatcher.registerSource('mouse', scope.mouseEvents); if (window.ontouchstart !== undefined) { dispatcher.registerSource('touch', scope.touchEvents); } } // Work around iOS bugs https://bugs.webkit.org/show_bug.cgi?id=135628 and https://bugs.webkit.org/show_bug.cgi?id=136506 var ua = navigator.userAgent; var IS_IOS = ua.match(/iPad|iPhone|iPod/) && 'ontouchstart' in window; dispatcher.IS_IOS = IS_IOS; scope.touchEvents.IS_IOS = IS_IOS; dispatcher.register(document, true); })(window.PolymerGestures); /** * This event denotes the beginning of a series of tracking events. * * @module PointerGestures * @submodule Events * @class trackstart */ /** * Pixels moved in the x direction since trackstart. * @type Number * @property dx */ /** * Pixes moved in the y direction since trackstart. * @type Number * @property dy */ /** * Pixels moved in the x direction since the last track. * @type Number * @property ddx */ /** * Pixles moved in the y direction since the last track. * @type Number * @property ddy */ /** * The clientX position of the track gesture. * @type Number * @property clientX */ /** * The clientY position of the track gesture. * @type Number * @property clientY */ /** * The pageX position of the track gesture. * @type Number * @property pageX */ /** * The pageY position of the track gesture. * @type Number * @property pageY */ /** * The screenX position of the track gesture. * @type Number * @property screenX */ /** * The screenY position of the track gesture. * @type Number * @property screenY */ /** * The last x axis direction of the pointer. * @type Number * @property xDirection */ /** * The last y axis direction of the pointer. * @type Number * @property yDirection */ /** * A shared object between all tracking events. * @type Object * @property trackInfo */ /** * The element currently under the pointer. * @type Element * @property relatedTarget */ /** * The type of pointer that make the track gesture. * @type String * @property pointerType */ /** * * This event fires for all pointer movement being tracked. * * @class track * @extends trackstart */ /** * This event fires when the pointer is no longer being tracked. * * @class trackend * @extends trackstart */ (function(scope) { var dispatcher = scope.dispatcher; var eventFactory = scope.eventFactory; var pointermap = new scope.PointerMap(); var track = { events: [ 'down', 'move', 'up', ], exposes: [ 'trackstart', 'track', 'trackx', 'tracky', 'trackend' ], defaultActions: { 'track': 'none', 'trackx': 'pan-y', 'tracky': 'pan-x' }, WIGGLE_THRESHOLD: 4, clampDir: function(inDelta) { return inDelta > 0 ? 1 : -1; }, calcPositionDelta: function(inA, inB) { var x = 0, y = 0; if (inA && inB) { x = inB.pageX - inA.pageX; y = inB.pageY - inA.pageY; } return {x: x, y: y}; }, fireTrack: function(inType, inEvent, inTrackingData) { var t = inTrackingData; var d = this.calcPositionDelta(t.downEvent, inEvent); var dd = this.calcPositionDelta(t.lastMoveEvent, inEvent); if (dd.x) { t.xDirection = this.clampDir(dd.x); } else if (inType === 'trackx') { return; } if (dd.y) { t.yDirection = this.clampDir(dd.y); } else if (inType === 'tracky') { return; } var gestureProto = { bubbles: true, cancelable: true, trackInfo: t.trackInfo, relatedTarget: inEvent.relatedTarget, pointerType: inEvent.pointerType, pointerId: inEvent.pointerId, _source: 'track' }; if (inType !== 'tracky') { gestureProto.x = inEvent.x; gestureProto.dx = d.x; gestureProto.ddx = dd.x; gestureProto.clientX = inEvent.clientX; gestureProto.pageX = inEvent.pageX; gestureProto.screenX = inEvent.screenX; gestureProto.xDirection = t.xDirection; } if (inType !== 'trackx') { gestureProto.dy = d.y; gestureProto.ddy = dd.y; gestureProto.y = inEvent.y; gestureProto.clientY = inEvent.clientY; gestureProto.pageY = inEvent.pageY; gestureProto.screenY = inEvent.screenY; gestureProto.yDirection = t.yDirection; } var e = eventFactory.makeGestureEvent(inType, gestureProto); t.downTarget.dispatchEvent(e); }, down: function(inEvent) { if (inEvent.isPrimary && (inEvent.pointerType === 'mouse' ? inEvent.buttons === 1 : true)) { var p = { downEvent: inEvent, downTarget: inEvent.target, trackInfo: {}, lastMoveEvent: null, xDirection: 0, yDirection: 0, tracking: false }; pointermap.set(inEvent.pointerId, p); } }, move: function(inEvent) { var p = pointermap.get(inEvent.pointerId); if (p) { if (!p.tracking) { var d = this.calcPositionDelta(p.downEvent, inEvent); var move = d.x * d.x + d.y * d.y; // start tracking only if finger moves more than WIGGLE_THRESHOLD if (move > this.WIGGLE_THRESHOLD) { p.tracking = true; p.lastMoveEvent = p.downEvent; this.fireTrack('trackstart', inEvent, p); } } if (p.tracking) { this.fireTrack('track', inEvent, p); this.fireTrack('trackx', inEvent, p); this.fireTrack('tracky', inEvent, p); } p.lastMoveEvent = inEvent; } }, up: function(inEvent) { var p = pointermap.get(inEvent.pointerId); if (p) { if (p.tracking) { this.fireTrack('trackend', inEvent, p); } pointermap.delete(inEvent.pointerId); } } }; dispatcher.registerGesture('track', track); })(window.PolymerGestures); /** * This event is fired when a pointer is held down for 200ms. * * @module PointerGestures * @submodule Events * @class hold */ /** * Type of pointer that made the holding event. * @type String * @property pointerType */ /** * Screen X axis position of the held pointer * @type Number * @property clientX */ /** * Screen Y axis position of the held pointer * @type Number * @property clientY */ /** * Type of pointer that made the holding event. * @type String * @property pointerType */ /** * This event is fired every 200ms while a pointer is held down. * * @class holdpulse * @extends hold */ /** * Milliseconds pointer has been held down. * @type Number * @property holdTime */ /** * This event is fired when a held pointer is released or moved. * * @class release */ (function(scope) { var dispatcher = scope.dispatcher; var eventFactory = scope.eventFactory; var hold = { // wait at least HOLD_DELAY ms between hold and pulse events HOLD_DELAY: 200, // pointer can move WIGGLE_THRESHOLD pixels before not counting as a hold WIGGLE_THRESHOLD: 16, events: [ 'down', 'move', 'up', ], exposes: [ 'hold', 'holdpulse', 'release' ], heldPointer: null, holdJob: null, pulse: function() { var hold = Date.now() - this.heldPointer.timeStamp; var type = this.held ? 'holdpulse' : 'hold'; this.fireHold(type, hold); this.held = true; }, cancel: function() { clearInterval(this.holdJob); if (this.held) { this.fireHold('release'); } this.held = false; this.heldPointer = null; this.target = null; this.holdJob = null; }, down: function(inEvent) { if (inEvent.isPrimary && !this.heldPointer) { this.heldPointer = inEvent; this.target = inEvent.target; this.holdJob = setInterval(this.pulse.bind(this), this.HOLD_DELAY); } }, up: function(inEvent) { if (this.heldPointer && this.heldPointer.pointerId === inEvent.pointerId) { this.cancel(); } }, move: function(inEvent) { if (this.heldPointer && this.heldPointer.pointerId === inEvent.pointerId) { var x = inEvent.clientX - this.heldPointer.clientX; var y = inEvent.clientY - this.heldPointer.clientY; if ((x * x + y * y) > this.WIGGLE_THRESHOLD) { this.cancel(); } } }, fireHold: function(inType, inHoldTime) { var p = { bubbles: true, cancelable: true, pointerType: this.heldPointer.pointerType, pointerId: this.heldPointer.pointerId, x: this.heldPointer.clientX, y: this.heldPointer.clientY, _source: 'hold' }; if (inHoldTime) { p.holdTime = inHoldTime; } var e = eventFactory.makeGestureEvent(inType, p); this.target.dispatchEvent(e); } }; dispatcher.registerGesture('hold', hold); })(window.PolymerGestures); /** * This event is fired when a pointer quickly goes down and up, and is used to * denote activation. * * Any gesture event can prevent the tap event from being created by calling * `event.preventTap`. * * Any pointer event can prevent the tap by setting the `tapPrevented` property * on itself. * * @module PointerGestures * @submodule Events * @class tap */ /** * X axis position of the tap. * @property x * @type Number */ /** * Y axis position of the tap. * @property y * @type Number */ /** * Type of the pointer that made the tap. * @property pointerType * @type String */ (function(scope) { var dispatcher = scope.dispatcher; var eventFactory = scope.eventFactory; var pointermap = new scope.PointerMap(); var tap = { events: [ 'down', 'up' ], exposes: [ 'tap' ], down: function(inEvent) { if (inEvent.isPrimary && !inEvent.tapPrevented) { pointermap.set(inEvent.pointerId, { target: inEvent.target, buttons: inEvent.buttons, x: inEvent.clientX, y: inEvent.clientY }); } }, shouldTap: function(e, downState) { var tap = true; if (e.pointerType === 'mouse') { // only allow left click to tap for mouse tap = (e.buttons ^ 1) && (downState.buttons & 1); } return tap && !e.tapPrevented; }, up: function(inEvent) { var start = pointermap.get(inEvent.pointerId); if (start && this.shouldTap(inEvent, start)) { // up.relatedTarget is target currently under finger var t = scope.targetFinding.LCA(start.target, inEvent.relatedTarget); if (t) { var e = eventFactory.makeGestureEvent('tap', { bubbles: true, cancelable: true, x: inEvent.clientX, y: inEvent.clientY, detail: inEvent.detail, pointerType: inEvent.pointerType, pointerId: inEvent.pointerId, altKey: inEvent.altKey, ctrlKey: inEvent.ctrlKey, metaKey: inEvent.metaKey, shiftKey: inEvent.shiftKey, _source: 'tap' }); t.dispatchEvent(e); } } pointermap.delete(inEvent.pointerId); } }; // patch eventFactory to remove id from tap's pointermap for preventTap calls eventFactory.preventTap = function(e) { return function() { e.tapPrevented = true; pointermap.delete(e.pointerId); }; }; dispatcher.registerGesture('tap', tap); })(window.PolymerGestures); /* * Basic strategy: find the farthest apart points, use as diameter of circle * react to size change and rotation of the chord */ /** * @module pointer-gestures * @submodule Events * @class pinch */ /** * Scale of the pinch zoom gesture * @property scale * @type Number */ /** * Center X position of pointers causing pinch * @property centerX * @type Number */ /** * Center Y position of pointers causing pinch * @property centerY * @type Number */ /** * @module pointer-gestures * @submodule Events * @class rotate */ /** * Angle (in degrees) of rotation. Measured from starting positions of pointers. * @property angle * @type Number */ /** * Center X position of pointers causing rotation * @property centerX * @type Number */ /** * Center Y position of pointers causing rotation * @property centerY * @type Number */ (function(scope) { var dispatcher = scope.dispatcher; var eventFactory = scope.eventFactory; var pointermap = new scope.PointerMap(); var RAD_TO_DEG = 180 / Math.PI; var pinch = { events: [ 'down', 'up', 'move', 'cancel' ], exposes: [ 'pinchstart', 'pinch', 'pinchend', 'rotate' ], defaultActions: { 'pinch': 'none', 'rotate': 'none' }, reference: {}, down: function(inEvent) { pointermap.set(inEvent.pointerId, inEvent); if (pointermap.pointers() == 2) { var points = this.calcChord(); var angle = this.calcAngle(points); this.reference = { angle: angle, diameter: points.diameter, target: scope.targetFinding.LCA(points.a.target, points.b.target) }; this.firePinch('pinchstart', points.diameter, points); } }, up: function(inEvent) { var p = pointermap.get(inEvent.pointerId); var num = pointermap.pointers(); if (p) { if (num === 2) { // fire 'pinchend' before deleting pointer var points = this.calcChord(); this.firePinch('pinchend', points.diameter, points); } pointermap.delete(inEvent.pointerId); } }, move: function(inEvent) { if (pointermap.has(inEvent.pointerId)) { pointermap.set(inEvent.pointerId, inEvent); if (pointermap.pointers() > 1) { this.calcPinchRotate(); } } }, cancel: function(inEvent) { this.up(inEvent); }, firePinch: function(type, diameter, points) { var zoom = diameter / this.reference.diameter; var e = eventFactory.makeGestureEvent(type, { bubbles: true, cancelable: true, scale: zoom, centerX: points.center.x, centerY: points.center.y, _source: 'pinch' }); this.reference.target.dispatchEvent(e); }, fireRotate: function(angle, points) { var diff = Math.round((angle - this.reference.angle) % 360); var e = eventFactory.makeGestureEvent('rotate', { bubbles: true, cancelable: true, angle: diff, centerX: points.center.x, centerY: points.center.y, _source: 'pinch' }); this.reference.target.dispatchEvent(e); }, calcPinchRotate: function() { var points = this.calcChord(); var diameter = points.diameter; var angle = this.calcAngle(points); if (diameter != this.reference.diameter) { this.firePinch('pinch', diameter, points); } if (angle != this.reference.angle) { this.fireRotate(angle, points); } }, calcChord: function() { var pointers = []; pointermap.forEach(function(p) { pointers.push(p); }); var dist = 0; // start with at least two pointers var points = {a: pointers[0], b: pointers[1]}; var x, y, d; for (var i = 0; i < pointers.length; i++) { var a = pointers[i]; for (var j = i + 1; j < pointers.length; j++) { var b = pointers[j]; x = Math.abs(a.clientX - b.clientX); y = Math.abs(a.clientY - b.clientY); d = x + y; if (d > dist) { dist = d; points = {a: a, b: b}; } } } x = Math.abs(points.a.clientX + points.b.clientX) / 2; y = Math.abs(points.a.clientY + points.b.clientY) / 2; points.center = { x: x, y: y }; points.diameter = dist; return points; }, calcAngle: function(points) { var x = points.a.clientX - points.b.clientX; var y = points.a.clientY - points.b.clientY; return (360 + Math.atan2(y, x) * RAD_TO_DEG) % 360; } }; dispatcher.registerGesture('pinch', pinch); })(window.PolymerGestures); (function (global) { 'use strict'; var Token, TokenName, Syntax, Messages, source, index, length, delegate, lookahead, state; Token = { BooleanLiteral: 1, EOF: 2, Identifier: 3, Keyword: 4, NullLiteral: 5, NumericLiteral: 6, Punctuator: 7, StringLiteral: 8 }; TokenName = {}; TokenName[Token.BooleanLiteral] = 'Boolean'; TokenName[Token.EOF] = ''; TokenName[Token.Identifier] = 'Identifier'; TokenName[Token.Keyword] = 'Keyword'; TokenName[Token.NullLiteral] = 'Null'; TokenName[Token.NumericLiteral] = 'Numeric'; TokenName[Token.Punctuator] = 'Punctuator'; TokenName[Token.StringLiteral] = 'String'; Syntax = { ArrayExpression: 'ArrayExpression', BinaryExpression: 'BinaryExpression', CallExpression: 'CallExpression', ConditionalExpression: 'ConditionalExpression', EmptyStatement: 'EmptyStatement', ExpressionStatement: 'ExpressionStatement', Identifier: 'Identifier', Literal: 'Literal', LabeledStatement: 'LabeledStatement', LogicalExpression: 'LogicalExpression', MemberExpression: 'MemberExpression', ObjectExpression: 'ObjectExpression', Program: 'Program', Property: 'Property', ThisExpression: 'ThisExpression', UnaryExpression: 'UnaryExpression' }; // Error messages should be identical to V8. Messages = { UnexpectedToken: 'Unexpected token %0', UnknownLabel: 'Undefined label \'%0\'', Redeclaration: '%0 \'%1\' has already been declared' }; // Ensure the condition is true, otherwise throw an error. // This is only to have a better contract semantic, i.e. another safety net // to catch a logic error. The condition shall be fulfilled in normal case. // Do NOT use this to enforce a certain condition on any user input. function assert(condition, message) { if (!condition) { throw new Error('ASSERT: ' + message); } } function isDecimalDigit(ch) { return (ch >= 48 && ch <= 57); // 0..9 } // 7.2 White Space function isWhiteSpace(ch) { return (ch === 32) || // space (ch === 9) || // tab (ch === 0xB) || (ch === 0xC) || (ch === 0xA0) || (ch >= 0x1680 && '\u1680\u180E\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200A\u202F\u205F\u3000\uFEFF'.indexOf(String.fromCharCode(ch)) > 0); } // 7.3 Line Terminators function isLineTerminator(ch) { return (ch === 10) || (ch === 13) || (ch === 0x2028) || (ch === 0x2029); } // 7.6 Identifier Names and Identifiers function isIdentifierStart(ch) { return (ch === 36) || (ch === 95) || // $ (dollar) and _ (underscore) (ch >= 65 && ch <= 90) || // A..Z (ch >= 97 && ch <= 122); // a..z } function isIdentifierPart(ch) { return (ch === 36) || (ch === 95) || // $ (dollar) and _ (underscore) (ch >= 65 && ch <= 90) || // A..Z (ch >= 97 && ch <= 122) || // a..z (ch >= 48 && ch <= 57); // 0..9 } // 7.6.1.1 Keywords function isKeyword(id) { return (id === 'this') } // 7.4 Comments function skipWhitespace() { while (index < length && isWhiteSpace(source.charCodeAt(index))) { ++index; } } function getIdentifier() { var start, ch; start = index++; while (index < length) { ch = source.charCodeAt(index); if (isIdentifierPart(ch)) { ++index; } else { break; } } return source.slice(start, index); } function scanIdentifier() { var start, id, type; start = index; id = getIdentifier(); // There is no keyword or literal with only one character. // Thus, it must be an identifier. if (id.length === 1) { type = Token.Identifier; } else if (isKeyword(id)) { type = Token.Keyword; } else if (id === 'null') { type = Token.NullLiteral; } else if (id === 'true' || id === 'false') { type = Token.BooleanLiteral; } else { type = Token.Identifier; } return { type: type, value: id, range: [start, index] }; } // 7.7 Punctuators function scanPunctuator() { var start = index, code = source.charCodeAt(index), code2, ch1 = source[index], ch2; switch (code) { // Check for most common single-character punctuators. case 46: // . dot case 40: // ( open bracket case 41: // ) close bracket case 59: // ; semicolon case 44: // , comma case 123: // { open curly brace case 125: // } close curly brace case 91: // [ case 93: // ] case 58: // : case 63: // ? ++index; return { type: Token.Punctuator, value: String.fromCharCode(code), range: [start, index] }; default: code2 = source.charCodeAt(index + 1); // '=' (char #61) marks an assignment or comparison operator. if (code2 === 61) { switch (code) { case 37: // % case 38: // & case 42: // *: case 43: // + case 45: // - case 47: // / case 60: // < case 62: // > case 124: // | index += 2; return { type: Token.Punctuator, value: String.fromCharCode(code) + String.fromCharCode(code2), range: [start, index] }; case 33: // ! case 61: // = index += 2; // !== and === if (source.charCodeAt(index) === 61) { ++index; } return { type: Token.Punctuator, value: source.slice(start, index), range: [start, index] }; default: break; } } break; } // Peek more characters. ch2 = source[index + 1]; // Other 2-character punctuators: && || if (ch1 === ch2 && ('&|'.indexOf(ch1) >= 0)) { index += 2; return { type: Token.Punctuator, value: ch1 + ch2, range: [start, index] }; } if ('<>=!+-*%&|^/'.indexOf(ch1) >= 0) { ++index; return { type: Token.Punctuator, value: ch1, range: [start, index] }; } throwError({}, Messages.UnexpectedToken, 'ILLEGAL'); } // 7.8.3 Numeric Literals function scanNumericLiteral() { var number, start, ch; ch = source[index]; assert(isDecimalDigit(ch.charCodeAt(0)) || (ch === '.'), 'Numeric literal must start with a decimal digit or a decimal point'); start = index; number = ''; if (ch !== '.') { number = source[index++]; ch = source[index]; // Hex number starts with '0x'. // Octal number starts with '0'. if (number === '0') { // decimal number starts with '0' such as '09' is illegal. if (ch && isDecimalDigit(ch.charCodeAt(0))) { throwError({}, Messages.UnexpectedToken, 'ILLEGAL'); } } while (isDecimalDigit(source.charCodeAt(index))) { number += source[index++]; } ch = source[index]; } if (ch === '.') { number += source[index++]; while (isDecimalDigit(source.charCodeAt(index))) { number += source[index++]; } ch = source[index]; } if (ch === 'e' || ch === 'E') { number += source[index++]; ch = source[index]; if (ch === '+' || ch === '-') { number += source[index++]; } if (isDecimalDigit(source.charCodeAt(index))) { while (isDecimalDigit(source.charCodeAt(index))) { number += source[index++]; } } else { throwError({}, Messages.UnexpectedToken, 'ILLEGAL'); } } if (isIdentifierStart(source.charCodeAt(index))) { throwError({}, Messages.UnexpectedToken, 'ILLEGAL'); } return { type: Token.NumericLiteral, value: parseFloat(number), range: [start, index] }; } // 7.8.4 String Literals function scanStringLiteral() { var str = '', quote, start, ch, octal = false; quote = source[index]; assert((quote === '\'' || quote === '"'), 'String literal must starts with a quote'); start = index; ++index; while (index < length) { ch = source[index++]; if (ch === quote) { quote = ''; break; } else if (ch === '\\') { ch = source[index++]; if (!ch || !isLineTerminator(ch.charCodeAt(0))) { switch (ch) { case 'n': str += '\n'; break; case 'r': str += '\r'; break; case 't': str += '\t'; break; case 'b': str += '\b'; break; case 'f': str += '\f'; break; case 'v': str += '\x0B'; break; default: str += ch; break; } } else { if (ch === '\r' && source[index] === '\n') { ++index; } } } else if (isLineTerminator(ch.charCodeAt(0))) { break; } else { str += ch; } } if (quote !== '') { throwError({}, Messages.UnexpectedToken, 'ILLEGAL'); } return { type: Token.StringLiteral, value: str, octal: octal, range: [start, index] }; } function isIdentifierName(token) { return token.type === Token.Identifier || token.type === Token.Keyword || token.type === Token.BooleanLiteral || token.type === Token.NullLiteral; } function advance() { var ch; skipWhitespace(); if (index >= length) { return { type: Token.EOF, range: [index, index] }; } ch = source.charCodeAt(index); // Very common: ( and ) and ; if (ch === 40 || ch === 41 || ch === 58) { return scanPunctuator(); } // String literal starts with single quote (#39) or double quote (#34). if (ch === 39 || ch === 34) { return scanStringLiteral(); } if (isIdentifierStart(ch)) { return scanIdentifier(); } // Dot (.) char #46 can also start a floating-point number, hence the need // to check the next character. if (ch === 46) { if (isDecimalDigit(source.charCodeAt(index + 1))) { return scanNumericLiteral(); } return scanPunctuator(); } if (isDecimalDigit(ch)) { return scanNumericLiteral(); } return scanPunctuator(); } function lex() { var token; token = lookahead; index = token.range[1]; lookahead = advance(); index = token.range[1]; return token; } function peek() { var pos; pos = index; lookahead = advance(); index = pos; } // Throw an exception function throwError(token, messageFormat) { var error, args = Array.prototype.slice.call(arguments, 2), msg = messageFormat.replace( /%(\d)/g, function (whole, index) { assert(index < args.length, 'Message reference must be in range'); return args[index]; } ); error = new Error(msg); error.index = index; error.description = msg; throw error; } // Throw an exception because of the token. function throwUnexpected(token) { throwError(token, Messages.UnexpectedToken, token.value); } // Expect the next token to match the specified punctuator. // If not, an exception will be thrown. function expect(value) { var token = lex(); if (token.type !== Token.Punctuator || token.value !== value) { throwUnexpected(token); } } // Return true if the next token matches the specified punctuator. function match(value) { return lookahead.type === Token.Punctuator && lookahead.value === value; } // Return true if the next token matches the specified keyword function matchKeyword(keyword) { return lookahead.type === Token.Keyword && lookahead.value === keyword; } function consumeSemicolon() { // Catch the very common case first: immediately a semicolon (char #59). if (source.charCodeAt(index) === 59) { lex(); return; } skipWhitespace(); if (match(';')) { lex(); return; } if (lookahead.type !== Token.EOF && !match('}')) { throwUnexpected(lookahead); } } // 11.1.4 Array Initialiser function parseArrayInitialiser() { var elements = []; expect('['); while (!match(']')) { if (match(',')) { lex(); elements.push(null); } else { elements.push(parseExpression()); if (!match(']')) { expect(','); } } } expect(']'); return delegate.createArrayExpression(elements); } // 11.1.5 Object Initialiser function parseObjectPropertyKey() { var token; skipWhitespace(); token = lex(); // Note: This function is called only from parseObjectProperty(), where // EOF and Punctuator tokens are already filtered out. if (token.type === Token.StringLiteral || token.type === Token.NumericLiteral) { return delegate.createLiteral(token); } return delegate.createIdentifier(token.value); } function parseObjectProperty() { var token, key; token = lookahead; skipWhitespace(); if (token.type === Token.EOF || token.type === Token.Punctuator) { throwUnexpected(token); } key = parseObjectPropertyKey(); expect(':'); return delegate.createProperty('init', key, parseExpression()); } function parseObjectInitialiser() { var properties = []; expect('{'); while (!match('}')) { properties.push(parseObjectProperty()); if (!match('}')) { expect(','); } } expect('}'); return delegate.createObjectExpression(properties); } // 11.1.6 The Grouping Operator function parseGroupExpression() { var expr; expect('('); expr = parseExpression(); expect(')'); return expr; } // 11.1 Primary Expressions function parsePrimaryExpression() { var type, token, expr; if (match('(')) { return parseGroupExpression(); } type = lookahead.type; if (type === Token.Identifier) { expr = delegate.createIdentifier(lex().value); } else if (type === Token.StringLiteral || type === Token.NumericLiteral) { expr = delegate.createLiteral(lex()); } else if (type === Token.Keyword) { if (matchKeyword('this')) { lex(); expr = delegate.createThisExpression(); } } else if (type === Token.BooleanLiteral) { token = lex(); token.value = (token.value === 'true'); expr = delegate.createLiteral(token); } else if (type === Token.NullLiteral) { token = lex(); token.value = null; expr = delegate.createLiteral(token); } else if (match('[')) { expr = parseArrayInitialiser(); } else if (match('{')) { expr = parseObjectInitialiser(); } if (expr) { return expr; } throwUnexpected(lex()); } // 11.2 Left-Hand-Side Expressions function parseArguments() { var args = []; expect('('); if (!match(')')) { while (index < length) { args.push(parseExpression()); if (match(')')) { break; } expect(','); } } expect(')'); return args; } function parseNonComputedProperty() { var token; token = lex(); if (!isIdentifierName(token)) { throwUnexpected(token); } return delegate.createIdentifier(token.value); } function parseNonComputedMember() { expect('.'); return parseNonComputedProperty(); } function parseComputedMember() { var expr; expect('['); expr = parseExpression(); expect(']'); return expr; } function parseLeftHandSideExpression() { var expr, args, property; expr = parsePrimaryExpression(); while (true) { if (match('[')) { property = parseComputedMember(); expr = delegate.createMemberExpression('[', expr, property); } else if (match('.')) { property = parseNonComputedMember(); expr = delegate.createMemberExpression('.', expr, property); } else if (match('(')) { args = parseArguments(); expr = delegate.createCallExpression(expr, args); } else { break; } } return expr; } // 11.3 Postfix Expressions var parsePostfixExpression = parseLeftHandSideExpression; // 11.4 Unary Operators function parseUnaryExpression() { var token, expr; if (lookahead.type !== Token.Punctuator && lookahead.type !== Token.Keyword) { expr = parsePostfixExpression(); } else if (match('+') || match('-') || match('!')) { token = lex(); expr = parseUnaryExpression(); expr = delegate.createUnaryExpression(token.value, expr); } else if (matchKeyword('delete') || matchKeyword('void') || matchKeyword('typeof')) { throwError({}, Messages.UnexpectedToken); } else { expr = parsePostfixExpression(); } return expr; } function binaryPrecedence(token) { var prec = 0; if (token.type !== Token.Punctuator && token.type !== Token.Keyword) { return 0; } switch (token.value) { case '||': prec = 1; break; case '&&': prec = 2; break; case '==': case '!=': case '===': case '!==': prec = 6; break; case '<': case '>': case '<=': case '>=': case 'instanceof': prec = 7; break; case 'in': prec = 7; break; case '+': case '-': prec = 9; break; case '*': case '/': case '%': prec = 11; break; default: break; } return prec; } // 11.5 Multiplicative Operators // 11.6 Additive Operators // 11.7 Bitwise Shift Operators // 11.8 Relational Operators // 11.9 Equality Operators // 11.10 Binary Bitwise Operators // 11.11 Binary Logical Operators function parseBinaryExpression() { var expr, token, prec, stack, right, operator, left, i; left = parseUnaryExpression(); token = lookahead; prec = binaryPrecedence(token); if (prec === 0) { return left; } token.prec = prec; lex(); right = parseUnaryExpression(); stack = [left, token, right]; while ((prec = binaryPrecedence(lookahead)) > 0) { // Reduce: make a binary expression from the three topmost entries. while ((stack.length > 2) && (prec <= stack[stack.length - 2].prec)) { right = stack.pop(); operator = stack.pop().value; left = stack.pop(); expr = delegate.createBinaryExpression(operator, left, right); stack.push(expr); } // Shift. token = lex(); token.prec = prec; stack.push(token); expr = parseUnaryExpression(); stack.push(expr); } // Final reduce to clean-up the stack. i = stack.length - 1; expr = stack[i]; while (i > 1) { expr = delegate.createBinaryExpression(stack[i - 1].value, stack[i - 2], expr); i -= 2; } return expr; } // 11.12 Conditional Operator function parseConditionalExpression() { var expr, consequent, alternate; expr = parseBinaryExpression(); if (match('?')) { lex(); consequent = parseConditionalExpression(); expect(':'); alternate = parseConditionalExpression(); expr = delegate.createConditionalExpression(expr, consequent, alternate); } return expr; } // Simplification since we do not support AssignmentExpression. var parseExpression = parseConditionalExpression; // Polymer Syntax extensions // Filter :: // Identifier // Identifier "(" ")" // Identifier "(" FilterArguments ")" function parseFilter() { var identifier, args; identifier = lex(); if (identifier.type !== Token.Identifier) { throwUnexpected(identifier); } args = match('(') ? parseArguments() : []; return delegate.createFilter(identifier.value, args); } // Filters :: // "|" Filter // Filters "|" Filter function parseFilters() { while (match('|')) { lex(); parseFilter(); } } // TopLevel :: // LabelledExpressions // AsExpression // InExpression // FilterExpression // AsExpression :: // FilterExpression as Identifier // InExpression :: // Identifier, Identifier in FilterExpression // Identifier in FilterExpression // FilterExpression :: // Expression // Expression Filters function parseTopLevel() { skipWhitespace(); peek(); var expr = parseExpression(); if (expr) { if (lookahead.value === ',' || lookahead.value == 'in' && expr.type === Syntax.Identifier) { parseInExpression(expr); } else { parseFilters(); if (lookahead.value === 'as') { parseAsExpression(expr); } else { delegate.createTopLevel(expr); } } } if (lookahead.type !== Token.EOF) { throwUnexpected(lookahead); } } function parseAsExpression(expr) { lex(); // as var identifier = lex().value; delegate.createAsExpression(expr, identifier); } function parseInExpression(identifier) { var indexName; if (lookahead.value === ',') { lex(); if (lookahead.type !== Token.Identifier) throwUnexpected(lookahead); indexName = lex().value; } lex(); // in var expr = parseExpression(); parseFilters(); delegate.createInExpression(identifier.name, indexName, expr); } function parse(code, inDelegate) { delegate = inDelegate; source = code; index = 0; length = source.length; lookahead = null; state = { labelSet: {} }; return parseTopLevel(); } global.esprima = { parse: parse }; })(this); // Copyright (c) 2014 The Polymer Project Authors. All rights reserved. // This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt // The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt // The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt // Code distributed by Google as part of the polymer project is also // subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt (function (global) { 'use strict'; function prepareBinding(expressionText, name, node, filterRegistry) { var expression; try { expression = getExpression(expressionText); if (expression.scopeIdent && (node.nodeType !== Node.ELEMENT_NODE || node.tagName !== 'TEMPLATE' || (name !== 'bind' && name !== 'repeat'))) { throw Error('as and in can only be used within