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) {