diff --git a/apps/dashboard-app/pom.xml b/apps/dashboard-app/pom.xml index 4fa27f5b2..4049442f0 100644 --- a/apps/dashboard-app/pom.xml +++ b/apps/dashboard-app/pom.xml @@ -1,200 +1,200 @@ shrine-base net.shrine 1.23.1.0-SNAPSHOT ../../pom.xml 4.0.0 dashboard-app 1.23.1.0-SNAPSHOT Dashboard App jar net.alchim31.maven scala-maven-plugin com.github.eirslett frontend-maven-plugin 1.3 src/main/js install node and npm install-node-and-npm v7.1.0 3.10.9 npm install npm generate-resources install bower install bower - install + install --force-latest grunt default grunt --no-color log4j log4j io.spray spray-routing_2.11 ${spray-version} io.spray spray-servlet_2.11 ${spray-version} io.spray spray-util_2.11 ${spray-version} io.spray spray-testkit_2.11 ${spray-version} test com.typesafe.akka akka-actor_2.11 ${akka-version} com.typesafe.akka akka-slf4j_2.11 ${akka-version} com.typesafe.akka akka-testkit_2.11 ${akka-testkit-version} test org.json4s json4s-native_2.11 ${json4s-version} com.typesafe.slick slick_2.11 ${slick-version} org.slf4j slf4j-log4j12 ${slf4j-version} com.h2database h2 ${h2-version} test net.shrine shrine-protocol ${project.version} net.shrine shrine-utility-commons ${project.version} net.shrine shrine-crypto ${project.version} test-jar test net.shrine shrine-auth ${project.version} net.shrine shrine-data-commons ${project.version} mysql mysql-connector-java ${mysql-version} io.jsonwebtoken jjwt 0.6.0 net.sourceforge.jtds jtds 1.3.1 net.shrine shrine-adapter-client-api ${project.version} com.typesafe config ${typesafe-config-version} diff --git a/apps/dashboard-app/src/main/js/bower.json b/apps/dashboard-app/src/main/js/bower.json index e2db61607..cd8af3821 100644 --- a/apps/dashboard-app/src/main/js/bower.json +++ b/apps/dashboard-app/src/main/js/bower.json @@ -1,66 +1,67 @@ { "name": "SHRINE Dashboard ", "version": 1, "authors": [ "Ben Carmen " ], "description": "Frontend diagnostic interface for SHRINE", "main": "app.js", "keywords": [ "Harvard", "Shrine", "Dashboard" ], "license": "MIT", "ignore": [ "**/.*", "node_modules", "bower_components", "test", "tests" ], "dependencies": { "angular": "^1.3.15", "angular-cookies": "^1.3.13", "angular-foundation": "^0.5.1", "angular-mocks": "^1.3.15", "angular-route": "^1.3.15", "angular-ui-router": "^0.2.13", "angular-loading-bar": "~0.7.1", "d3": "^3.5.5", "fastclick": "^1.0.3", "foundation": "^5.5.0", "foundation-datepicker": "1.3.0", "jquery.cookie": "^1.4.1", "jquery-placeholder": "^2.0.9", "jquery": "^2.1.3", "lodash": "^3.2.0", "modernizr": "^2.8.3" }, "resolutions": { - "angular": "^1.3.15" + "angular": "^1.3.15", + "webcomponentsjs": "0.7.23" }, "devDependencies": { "angular": "~1.4.0", "json3": "~3.3.2", "es5-shim": "~4.1.5", "angular-chart.js": "~0.7.2", "angular-resource": "~1.4.0", "angular-cookies": "~1.4.0", "angular-sanitize": "~1.4.0", "angular-animate": "~1.4.0", "angular-touch": "~1.4.0", "angular-route": "~1.4.0", "font-awesome": "~4.3.0", "angular-bootstrap": "~0.13.0", "oclazyload": "~1.0.1", "angular-loading-bar": "~0.7.1", "angular-ui-router": "~0.2.15", "angular-toggle-switch": "~1.3.0", "metisMenu": "~2.0.2", "sb-admin-2": "*", "bootstrap": "~3.3.5", "Chart.js": "~1.0.2", "x2js": "~1.1.7" } } diff --git a/apps/dashboard-app/src/main/js/index.html b/apps/dashboard-app/src/main/js/index.html index 85f24422a..c40c99224 100644 --- a/apps/dashboard-app/src/main/js/index.html +++ b/apps/dashboard-app/src/main/js/index.html @@ -1,78 +1,78 @@ Shrine Dashboard
- + \ No newline at end of file diff --git a/apps/dashboard-app/src/main/js/src/vendor/core-component-page/.bower.json b/apps/dashboard-app/src/main/js/src/vendor/core-component-page/.bower.json new file mode 100644 index 000000000..cfb707eed --- /dev/null +++ b/apps/dashboard-app/src/main/js/src/vendor/core-component-page/.bower.json @@ -0,0 +1,19 @@ +{ + "name": "core-component-page", + "private": true, + "dependencies": { + "webcomponentsjs": "Polymer/webcomponentsjs", + "polymer": "Polymer/polymer#^0.5" + }, + "version": "0.5.6", + "homepage": "https://github.com/Polymer/core-component-page", + "_release": "0.5.6", + "_resolution": { + "type": "version", + "tag": "0.5.6", + "commit": "01a1d3968d7ece144783bcd03bc87b18344265e3" + }, + "_source": "https://github.com/Polymer/core-component-page.git", + "_target": "^0.5", + "_originalSource": "Polymer/core-component-page" +} \ No newline at end of file diff --git a/apps/dashboard-app/src/main/js/src/vendor/core-component-page/README.md b/apps/dashboard-app/src/main/js/src/vendor/core-component-page/README.md new file mode 100644 index 000000000..9e2384294 --- /dev/null +++ b/apps/dashboard-app/src/main/js/src/vendor/core-component-page/README.md @@ -0,0 +1,9 @@ +core-component-page +=================== + +**This element is compatible with Polymer 0.5 and lower only, and will be deprecated.** +You can check out a similar 0.8-compatible version of this element at [https://github.com/polymerelements/iron-components-page](https://github.com/polymerelements/iron-component-page) + +See the [component page](https://www.polymer-project.org/0.5/docs/elements/core-component-page.html) for more information. + +Note: this is the vulcanized version of [`core-component-page-dev`](https://github.com/Polymer/core-component-page-dev) (the source). diff --git a/apps/dashboard-app/src/main/js/src/vendor/core-component-page/bowager-logo.png b/apps/dashboard-app/src/main/js/src/vendor/core-component-page/bowager-logo.png new file mode 100644 index 000000000..76be9fb04 Binary files /dev/null and b/apps/dashboard-app/src/main/js/src/vendor/core-component-page/bowager-logo.png differ diff --git a/apps/dashboard-app/src/main/js/src/vendor/core-component-page/bower.json b/apps/dashboard-app/src/main/js/src/vendor/core-component-page/bower.json new file mode 100644 index 000000000..df2d599bd --- /dev/null +++ b/apps/dashboard-app/src/main/js/src/vendor/core-component-page/bower.json @@ -0,0 +1,9 @@ +{ + "name": "core-component-page", + "private": true, + "dependencies": { + "webcomponentsjs": "Polymer/webcomponentsjs", + "polymer": "Polymer/polymer#^0.5" + }, + "version": "0.5.6" +} \ No newline at end of file diff --git a/apps/dashboard-app/src/main/js/src/vendor/core-component-page/core-component-page.html b/apps/dashboard-app/src/main/js/src/vendor/core-component-page/core-component-page.html new file mode 100644 index 000000000..82a87e7ea --- /dev/null +++ b/apps/dashboard-app/src/main/js/src/vendor/core-component-page/core-component-page.html @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/dashboard-app/src/main/js/src/vendor/core-component-page/demo.html b/apps/dashboard-app/src/main/js/src/vendor/core-component-page/demo.html new file mode 100644 index 000000000..3c414d852 --- /dev/null +++ b/apps/dashboard-app/src/main/js/src/vendor/core-component-page/demo.html @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + diff --git a/apps/dashboard-app/src/main/js/src/vendor/core-component-page/index.html b/apps/dashboard-app/src/main/js/src/vendor/core-component-page/index.html new file mode 100644 index 000000000..294215a73 --- /dev/null +++ b/apps/dashboard-app/src/main/js/src/vendor/core-component-page/index.html @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + diff --git a/apps/dashboard-app/src/main/js/src/vendor/polymer/.bower.json b/apps/dashboard-app/src/main/js/src/vendor/polymer/.bower.json new file mode 100644 index 000000000..960d05cd5 --- /dev/null +++ b/apps/dashboard-app/src/main/js/src/vendor/polymer/.bower.json @@ -0,0 +1,32 @@ +{ + "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", + "webcomponentsjs": "^0.6.0" + }, + "devDependencies": { + "tools": "Polymer/tools#master", + "web-component-tester": "*" + }, + "version": "0.5.6", + "_release": "0.5.6", + "_resolution": { + "type": "version", + "tag": "0.5.6", + "commit": "bf567ab71feecd6e98adce658132c65ffbeb721f" + }, + "_source": "https://github.com/Polymer/polymer.git", + "_target": "^0.5", + "_originalSource": "Polymer/polymer" +} \ No newline at end of file diff --git a/apps/dashboard-app/src/main/js/src/vendor/polymer/README.md b/apps/dashboard-app/src/main/js/src/vendor/polymer/README.md new file mode 100644 index 000000000..2c03674f3 --- /dev/null +++ b/apps/dashboard-app/src/main/js/src/vendor/polymer/README.md @@ -0,0 +1,21 @@ +# 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) diff --git a/apps/dashboard-app/src/main/js/src/vendor/polymer/bower.json b/apps/dashboard-app/src/main/js/src/vendor/polymer/bower.json new file mode 100644 index 000000000..51102c83e --- /dev/null +++ b/apps/dashboard-app/src/main/js/src/vendor/polymer/bower.json @@ -0,0 +1,23 @@ +{ + "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", + "webcomponentsjs": "^0.6.0" + }, + "devDependencies": { + "tools": "Polymer/tools#master", + "web-component-tester": "*" + }, + "version": "0.5.6" +} \ No newline at end of file diff --git a/apps/dashboard-app/src/main/js/src/vendor/polymer/docs/index.html b/apps/dashboard-app/src/main/js/src/vendor/polymer/docs/index.html new file mode 100644 index 000000000..b2fa01610 --- /dev/null +++ b/apps/dashboard-app/src/main/js/src/vendor/polymer/docs/index.html @@ -0,0 +1,48 @@ + + + + + + x-doc-viewer + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/dashboard-app/src/main/js/src/vendor/polymer/explainer/data-bind.png b/apps/dashboard-app/src/main/js/src/vendor/polymer/explainer/data-bind.png new file mode 100644 index 000000000..c55816df3 Binary files /dev/null and b/apps/dashboard-app/src/main/js/src/vendor/polymer/explainer/data-bind.png differ diff --git a/apps/dashboard-app/src/main/js/src/vendor/polymer/explainer/data-bind.vsdx b/apps/dashboard-app/src/main/js/src/vendor/polymer/explainer/data-bind.vsdx new file mode 100644 index 000000000..e699a26bb Binary files /dev/null and b/apps/dashboard-app/src/main/js/src/vendor/polymer/explainer/data-bind.vsdx differ diff --git a/apps/dashboard-app/src/main/js/src/vendor/polymer/explainer/samples.html b/apps/dashboard-app/src/main/js/src/vendor/polymer/explainer/samples.html new file mode 100644 index 000000000..7a22c3108 --- /dev/null +++ b/apps/dashboard-app/src/main/js/src/vendor/polymer/explainer/samples.html @@ -0,0 +1,50 @@ + + + + + + Explainer Samples + + + + + + + + + + + + + + + + + diff --git a/apps/dashboard-app/src/main/js/src/vendor/polymer/layout.html b/apps/dashboard-app/src/main/js/src/vendor/polymer/layout.html new file mode 100644 index 000000000..55d4d2f0b --- /dev/null +++ b/apps/dashboard-app/src/main/js/src/vendor/polymer/layout.html @@ -0,0 +1,286 @@ + + \ No newline at end of file diff --git a/apps/dashboard-app/src/main/js/src/vendor/polymer/polymer.html b/apps/dashboard-app/src/main/js/src/vendor/polymer/polymer.html new file mode 100644 index 000000000..7e3d8f1e4 --- /dev/null +++ b/apps/dashboard-app/src/main/js/src/vendor/polymer/polymer.html @@ -0,0 +1,12 @@ + + + + + diff --git a/apps/dashboard-app/src/main/js/src/vendor/polymer/polymer.js b/apps/dashboard-app/src/main/js/src/vendor/polymer/polymer.js new file mode 100644 index 000000000..aa947c0b8 --- /dev/null +++ b/apps/dashboard-app/src/main/js/src/vendor/polymer/polymer.js @@ -0,0 +1,11856 @@ +/** + * @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.5 +window.PolymerGestures = {}; + +(function(scope) { + var hasFullPath = 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 + hasFullPath = 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 (hasFullPath && 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 (hasFullPath && 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 (hasFullPath && 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]; + if( k !== 'bubbles' && k !== 'cancelable' ) { + 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 = 2, 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 currentButtons = 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.nodeType === Node.DOCUMENT_NODE) { + 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') { + currentButtons |= bit; + } else if (type === 'mouseup') { + currentButtons &= ~bit; + } + e.buttons = currentButtons; + } + 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) { + currentButtons = 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.nodeType === Node.DOCUMENT_NODE) { + 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.nodeType === Node.DOCUMENT_NODE) { + 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