diff --git a/ionic/components/action-menu/extensions/ios.scss b/ionic/components/action-menu/extensions/ios.scss
index 278b635f28..c93e4f25b1 100644
--- a/ionic/components/action-menu/extensions/ios.scss
+++ b/ionic/components/action-menu/extensions/ios.scss
@@ -36,7 +36,6 @@ $action-menu-ios-icon-font-size: 1.4em !default;
background: transparent;
- &:active,
&.activated {
background: $action-menu-ios-background-active;
}
diff --git a/ionic/components/action-menu/extensions/material.scss b/ionic/components/action-menu/extensions/material.scss
index 55d405d650..1cd6ca1d4c 100644
--- a/ionic/components/action-menu/extensions/material.scss
+++ b/ionic/components/action-menu/extensions/material.scss
@@ -55,7 +55,6 @@ $action-menu-md-icon-font-size: 2.4rem !default;
font-weight: normal;
min-height: $action-menu-md-height;
- &:active,
&.activated {
background: $action-menu-md-background-active;
}
diff --git a/ionic/components/app/test/tap/index.ts b/ionic/components/app/test/tap/index.ts
new file mode 100644
index 0000000000..312140ef6a
--- /dev/null
+++ b/ionic/components/app/test/tap/index.ts
@@ -0,0 +1,94 @@
+import {App} from 'ionic/ionic';
+
+
+@App({
+ templateUrl: 'main.html'
+})
+class IonicApp {}
+
+
+function onEvent(ev) {
+ var c = pointerCoord(ev);
+ var l = '(' + c.x + ',' + c.y + ')';
+ if (ev.isIonicTap) {
+ l += ' isIonicTap';
+ }
+ console.debug(ev.type, l);
+}
+
+function pointerCoord(ev) {
+ // get coordinates for either a mouse click
+ // or a touch depending on the given event
+ let c = { x: 0, y: 0 };
+ if (ev) {
+ const touches = ev.touches && ev.touches.length ? ev.touches : [ev];
+ const e = (ev.changedTouches && ev.changedTouches[0]) || touches[0];
+ if (e) {
+ c.x = e.clientX || e.pageX || 0;
+ c.y = e.clientY || e.pageY || 0;
+ }
+ }
+ return c;
+}
+
+document.addEventListener('touchstart', onEvent);
+document.addEventListener('touchcancel', onEvent);
+document.addEventListener('touchend', onEvent);
+document.addEventListener('mousedown', onEvent);
+document.addEventListener('mouseup', onEvent);
+document.addEventListener('click', onEvent);
+
+
+var msgs = [];
+var index = 0;
+var timeId;
+var winConsoleError = console.error;
+
+console.error = function() {
+ winConsoleError.apply(this, arguments);
+ var args = ['ERROR!'];
+ for (var i = 0, j = arguments.length; i < j; i++){
+ args.push(arguments[i]);
+ }
+ console.debug.apply(this, args);
+};
+
+console.debug = function() {
+ index++;
+ var msg = [];
+ msg.push(index);
+ for (var i = 0, j = arguments.length; i < j; i++){
+ msg.push(arguments[i]);
+ }
+ msg.push(getTime());
+
+ msg = msg.join(', ');
+
+ if(arguments[0] === 'ERROR!') msg = '' + msg + '';
+
+ if(arguments[0] === 'touchstart') msg = '' + msg + '';
+ if(arguments[0] === 'touchend') msg = '' + msg + '';
+
+ if(arguments[0] === 'mousedown') msg = '' + msg + '';
+ if(arguments[0] === 'mouseup') msg = '' + msg + '';
+
+ if(arguments[0] === 'click') msg = '' + msg + '';
+
+ msgs.unshift( msg );
+
+ if(msgs.length > 25) {
+ msgs.splice(25);
+ }
+
+ // do this so we try not to interfere with the device performance
+ clearTimeout(timeId);
+ timeId = setTimeout(function(){
+ document.getElementById('logs').innerHTML = msgs.join('
');
+ }, 100);
+
+}
+
+function getTime() {
+ var d = new Date();
+ return d.getSeconds() + '.' + d.getMilliseconds();
+}
diff --git a/ionic/components/app/test/tap/main.html b/ionic/components/app/test/tap/main.html
new file mode 100644
index 0000000000..70e63501a7
--- /dev/null
+++ b/ionic/components/app/test/tap/main.html
@@ -0,0 +1,6 @@
+
+
+
+
diff --git a/ionic/components/button/button.scss b/ionic/components/button/button.scss
index d6b8bc62a3..3eeb58adca 100644
--- a/ionic/components/button/button.scss
+++ b/ionic/components/button/button.scss
@@ -247,7 +247,6 @@ a[button] {
text-decoration: none;
}
- &:active,
&.activated {
opacity: 1;
background-color: darken($bg-color, 12%);
diff --git a/ionic/components/button/button.ts b/ionic/components/button/button.ts
index 61e24c4fb6..7c023df53a 100644
--- a/ionic/components/button/button.ts
+++ b/ionic/components/button/button.ts
@@ -1,16 +1,15 @@
-import {Directive, ElementRef, Optional, Ancestor, onDestroy} from 'angular2/angular2';
+import {Directive, ElementRef, Optional, Ancestor, onDestroy, NgZone} from 'angular2/angular2';
import {IonicConfig} from '../../config/config';
-import {Gesture} from '../../gestures/gesture';
+import {Activator} from '../../util/activator';
import * as dom from '../../util/dom';
+
@Directive({
selector: 'button,[button]'
})
-export class Button {
- constructor(elementRef: ElementRef, config: IonicConfig) {
- }
-}
+export class Button {}
+
@Directive({
selector: '[tap-disabled]'
@@ -19,43 +18,126 @@ export class TapDisabled {}
@Directive({
- selector: 'a,button,[tappable]'
+ selector: 'a,button,[tappable]',
+ host: {
+ '(^touchstart)': 'touchStart($event)',
+ '(^touchend)': 'touchEnd($event)',
+ '(^touchcancel)': 'pointerCancel()',
+ '(^mousedown)': 'mouseDown($event)',
+ '(^mouseup)': 'mouseUp($event)',
+ '(^click)': 'click($event)',
+ }
})
export class TapClick {
constructor(
elementRef: ElementRef,
config: IonicConfig,
+ ngZone: NgZone,
@Optional() @Ancestor() tapDisabled: TapDisabled
) {
+ this.ele = elementRef.nativeElement;
+ this.tapEnabled = !tapDisabled;
+ this.tapPolyfill = config.setting('tapPolyfill');
+ this.zone = ngZone;
- if (config.setting('tapPolyfill') && !this.tapGesture && !tapDisabled) {
- this.tapGesture = new Gesture(elementRef.nativeElement);
- this.tapGesture.listen();
-
- this.tapGesture.on('tap', (gestureEv) => {
- this.onTap(gestureEv.gesture.srcEvent);
- });
- }
+ let self = this;
+ self.pointerMove = function(ev) {
+ let moveCoord = dom.pointerCoord(ev);
+ console.log('pointerMove', moveCoord, self.start)
+ if ( dom.hasPointerMoved(10, self.start, moveCoord) ) {
+ self.pointerCancel();
+ }
+ };
}
- onTap(ev) {
- if (ev && this.tapGesture) {
+ touchStart(ev) {
+ this.pointerStart(ev);
+ }
+
+ touchEnd(ev) {
+ let self = this;
+
+ if (this.tapPolyfill && this.tapEnabled) {
+
+ let endCoord = dom.pointerCoord(ev);
+
+ this.disableClick = true;
+ this.zone.runOutsideAngular(() => {
+ clearTimeout(self.disableTimer);
+ self.disableTimer = setTimeout(() => {
+ self.disableClick = false;
+ }, 600);
+ });
+
+ if ( this.start && !dom.hasPointerMoved(3, this.start, endCoord) ) {
+ let clickEvent = document.createEvent('MouseEvents');
+ clickEvent.initMouseEvent('click', true, true, window, 1, 0, 0, endCoord.x, endCoord.y, false, false, false, false, 0, null);
+ clickEvent.isIonicTap = true;
+ this.ele.dispatchEvent(clickEvent);
+ }
+
+ }
+
+ this.pointerEnd();
+ }
+
+ mouseDown(ev) {
+ if (this.disableClick) {
ev.preventDefault();
ev.stopPropagation();
- let c = dom.pointerCoord(ev);
+ } else {
+ this.pointerStart(ev);
+ }
+ }
- let clickEvent = document.createEvent("MouseEvents");
- clickEvent.initMouseEvent('click', true, true, window, 1, 0, 0, c.x, c.y, false, false, false, false, 0, null);
- clickEvent.isIonicTap = true;
- this.tapGesture.element.dispatchEvent(clickEvent);
+ mouseUp(ev) {
+ if (this.disableClick) {
+ ev.preventDefault();
+ ev.stopPropagation();
+ }
+
+ this.pointerEnd();
+ }
+
+ pointerStart(ev) {
+ this.start = dom.pointerCoord(ev);
+
+ this.zone.runOutsideAngular(() => {
+ Activator.start(ev.currentTarget);
+ Activator.moveListeners(this.pointerMove, true);
+ });
+ }
+
+ pointerEnd() {
+ this.zone.runOutsideAngular(() => {
+ Activator.end();
+ Activator.moveListeners(this.pointerMove, false);
+ });
+ }
+
+ pointerCancel() {
+ this.start = null;
+
+ this.zone.runOutsideAngular(() => {
+ Activator.clear();
+ Activator.moveListeners(this.pointerMove, false);
+ });
+ }
+
+ click(ev) {
+ if (!ev.isIonicTap) {
+ if (this.disableClick || !this.start) {
+ ev.preventDefault();
+ ev.stopPropagation();
+ }
}
}
onDestroy() {
- this.tapGesture && this.tapGesture.destroy();
+ this.ele = null;
}
}
diff --git a/ionic/components/button/extensions/material.scss b/ionic/components/button/extensions/material.scss
index 4701a1c684..0c2b5cd467 100644
--- a/ionic/components/button/extensions/material.scss
+++ b/ionic/components/button/extensions/material.scss
@@ -28,7 +28,7 @@ $button-material-border-radius: 3px !default;
background-color 0.2s $animation-curve-default,
color 0.2s $animation-curve-default;
- &:active, &.activated {
+ &.activated {
box-shadow: $button-material-box-shadow-active;
}
@@ -68,7 +68,6 @@ $button-material-border-radius: 3px !default;
}
}
- &:active,
&.activated {
opacity: 1;
background-color: get-color($color, base);
@@ -84,7 +83,7 @@ $button-material-border-radius: 3px !default;
background-color: get-color($color, base);
}
}
- &:active,
+
&.activated {
opacity: 1;
background: transparent;
@@ -98,10 +97,11 @@ $button-material-border-radius: 3px !default;
color: get-color($color, inverse);
}
- &:hover, &.hover {
+ &:hover,
+ &.hover {
background-color: rgba(158, 158, 158, 0.2);
}
- &:active, &.active {
+ &:activated {
background-color: rgba(158, 158, 158, 0.4);
}
}
diff --git a/ionic/components/toolbar/extensions/material.scss b/ionic/components/toolbar/extensions/material.scss
index 2ff156d162..ed6d030fd0 100644
--- a/ionic/components/toolbar/extensions/material.scss
+++ b/ionic/components/toolbar/extensions/material.scss
@@ -3,7 +3,7 @@
// --------------------------------------------------
$toolbar-material-title-font-size: 2rem !default;
-$toolbar-material-button-font-size: 1.8rem !default;
+$toolbar-material-button-font-size: 1.4rem !default;
.toolbar[mode="md"] {
diff --git a/ionic/util/activator.ts b/ionic/util/activator.ts
new file mode 100644
index 0000000000..6025ca3a4b
--- /dev/null
+++ b/ionic/util/activator.ts
@@ -0,0 +1,61 @@
+import {raf} from './dom';
+
+var queueElements = {}; // elements that should get an active state in XX milliseconds
+var activeElements = {}; // elements that are currently active
+var keyId = 0; // a counter for unique keys for the above ojects
+var ACTIVATED_CLASS = 'activated';
+var DEACTIVATE_TIMEOUT = 180;
+
+
+export class Activator {
+
+ static start(ele) {
+ queueElements[++keyId] = ele;
+ if (keyId > 9) keyId = 0;
+ raf(Activator.activate);
+ }
+
+ static activate() {
+ // activate all elements in the queue
+ for (var key in queueElements) {
+ if (queueElements[key]) {
+ queueElements[key].classList.add(ACTIVATED_CLASS);
+ activeElements[key] = queueElements[key];
+ }
+ }
+ queueElements = {};
+ }
+
+ static end() {
+ setTimeout(Activator.clear, DEACTIVATE_TIMEOUT);
+ }
+
+ static clear() {
+ // clear out any elements that are queued to be set to active
+ queueElements = {};
+
+ // in the next frame, remove the active class from all active elements
+ raf(Activator.deactivate);
+ }
+
+ static deactivate() {
+
+ for (var key in activeElements) {
+ if (activeElements[key]) {
+ activeElements[key].classList.remove(ACTIVATED_CLASS);
+ }
+ delete activeElements[key];
+ }
+ }
+
+ static moveListeners(pointerMove, shouldAdd) {
+ document.removeEventListener('touchmove', pointerMove);
+ document.removeEventListener('mousemove', pointerMove);
+
+ if (shouldAdd) {
+ document.addEventListener('touchmove', pointerMove);
+ document.addEventListener('mousemove', pointerMove);
+ }
+ }
+
+}
diff --git a/ionic/util/dom.ts b/ionic/util/dom.ts
index eb529c6be4..1db4f6d699 100644
--- a/ionic/util/dom.ts
+++ b/ionic/util/dom.ts
@@ -174,9 +174,9 @@ export function pointerCoord(ev) {
return c;
}
-export function hasPointerMoved(tolerance, startCoord, endCoord) {
+export function hasPointerMoved(threshold, startCoord, endCoord) {
return startCoord && endCoord &&
- (Math.abs(startCoord.x - endCoord.x) > tolerance || Math.abs(startCoord.y - endCoord.y) > tolerance);
+ (Math.abs(startCoord.x - endCoord.x) > threshold || Math.abs(startCoord.y - endCoord.y) > threshold);
}
export function hasFocus(ele) {