diff --git a/ionic/components/action-menu/action-menu.ts b/ionic/components/action-menu/action-menu.ts
index c675a5649a..257b27f05a 100644
--- a/ionic/components/action-menu/action-menu.ts
+++ b/ionic/components/action-menu/action-menu.ts
@@ -8,7 +8,6 @@
import {View, Injectable, NgFor, NgIf} from 'angular2/angular2';
-import {TapClick} from '../button/button';
import {Icon} from '../icon/icon';
import {Overlay} from '../overlay/overlay';
import {Animation} from '../../animations/animation';
@@ -75,7 +74,7 @@ import * as util from 'ionic/util';
'' +
'' +
'',
- directives: [NgFor, NgIf, TapClick, Icon]
+ directives: [NgFor, NgIf, Icon]
})
class ActionMenuDirective {
diff --git a/ionic/components/app/app.ts b/ionic/components/app/app.ts
index ee1bdb8153..61283f11d9 100644
--- a/ionic/components/app/app.ts
+++ b/ionic/components/app/app.ts
@@ -6,6 +6,7 @@ import {Platform} from '../../platform/platform';
import * as util from '../../util/util';
// injectables
+import {Activator} from '../../util/activator';
import {ActionMenu} from '../action-menu/action-menu';
import {Modal} from '../modal/modal';
import {Popup} from '../popup/popup';
@@ -272,6 +273,7 @@ export function ionicBootstrap(rootComponentType, config) {
Platform.prepareReady(config);
// TODO: probs need a better way to inject global injectables
+ let activator = new Activator(app, config, window, document);
let actionMenu = new ActionMenu(app, config);
let modal = new Modal(app, config);
let popup = new Popup(app, config);
@@ -280,6 +282,7 @@ export function ionicBootstrap(rootComponentType, config) {
let appBindings = Injector.resolve([
bind(IonicApp).toValue(app),
bind(IonicConfig).toValue(config),
+ bind(Activator).toValue(activator),
bind(ActionMenu).toValue(actionMenu),
bind(Modal).toValue(modal),
bind(Popup).toValue(popup),
diff --git a/ionic/components/app/test/tap/index.ts b/ionic/components/app/test/tap/index.ts
index 170ff78341..683bf1bc13 100644
--- a/ionic/components/app/test/tap/index.ts
+++ b/ionic/components/app/test/tap/index.ts
@@ -4,7 +4,13 @@ import {App} from 'ionic/ionic';
@App({
templateUrl: 'main.html'
})
-class E2EApp {}
+class E2EApp {
+
+ tapTest(eleType) {
+ console.debug('test click', eleType);
+ }
+
+}
function onEvent(ev) {
@@ -74,6 +80,8 @@ console.debug = function() {
if(arguments[0] === 'click') msg = '' + msg + '';
+ if(arguments[0] === 'test click') msg = '' + msg + '';
+
msgs.unshift( msg );
if(msgs.length > 25) {
diff --git a/ionic/components/app/test/tap/main.html b/ionic/components/app/test/tap/main.html
index af748eb855..7c90b37702 100644
--- a/ionic/components/app/test/tap/main.html
+++ b/ionic/components/app/test/tap/main.html
@@ -1,6 +1,41 @@
-
+
+
+
+
+
+
+
+
+ Link
+
+
+
+
+
+
+
+
+
+ Div w/ (click)
+
+
+
+
+
+ Div w/out (click)
+
+
+
+
+
+
+
diff --git a/ionic/components/button/button.ts b/ionic/components/button/button.ts
index a3d466990c..917f99d78d 100644
--- a/ionic/components/button/button.ts
+++ b/ionic/components/button/button.ts
@@ -1,9 +1,5 @@
-import {Directive, ElementRef, Optional, Host, onDestroy, NgZone, Query, QueryList} from 'angular2/angular2';
+import {Directive} from 'angular2/angular2';
-import {Icon} from '../icon/icon';
-import {IonicConfig} from '../../config/config';
-import {Activator} from '../../util/activator';
-import * as dom from '../../util/dom';
/**
* TODO
@@ -17,9 +13,7 @@ import * as dom from '../../util/dom';
}
})
export class Button {
- /**
- * TODO
- */
+
constructor() {
this.iconLeft = this.iconRight = this.iconOnly = false;
}
@@ -34,190 +28,3 @@ export class Button {
this.iconOnly = icon.iconOnly;
}
}
-
-/**
- * TODO
- */
-@Directive({
- selector: '[tap-disabled]'
-})
-export class TapDisabled {}
-
-/**
- * TODO
- */
-@Directive({
- selector: 'button,[button],[tappable],ion-checkbox,ion-radio',
- host: {
- '(^touchstart)': 'touchStart($event)',
- '(^touchend)': 'touchEnd($event)',
- '(^touchcancel)': 'pointerCancel()',
- '(^mousedown)': 'mouseDown($event)',
- '(^mouseup)': 'mouseUp($event)',
- '(^click)': 'click($event)',
- }
-})
-export class TapClick {
- /**
- * TODO
- * @param {ElementRef} elementRef TODO
- * @param {IonicConfig} config TODO
- * @param {NgZone} ngZone TODO
- * @param {TapDisabled=} tapDisabled TODO
- */
- constructor(
- elementRef: ElementRef,
- config: IonicConfig,
- ngZone: NgZone,
- @Optional() @Host() tapDisabled: TapDisabled
- ) {
- this.ele = elementRef.nativeElement;
- this.tapEnabled = !tapDisabled;
- this.tapPolyfill = config.setting('tapPolyfill');
- this.zone = ngZone;
-
- 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();
- }
- };
- }
-
- /**
- * TODO
- * @param {TODO} ev TODO
- */
- touchStart(ev) {
- this.pointerStart(ev);
- }
-
- /**
- * TODO
- * @param {TODO} ev TODO
- */
- 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();
- }
-
- /**
- * TODO
- * @param {TODO} ev TODO
- */
- mouseDown(ev) {
- if (this.disableClick) {
- ev.preventDefault();
- ev.stopPropagation();
-
- } else {
- this.pointerStart(ev);
- }
- }
-
- /**
- * TODO
- * @param {TODO} ev TODO
- */
- mouseUp(ev) {
- if (this.disableClick) {
- ev.preventDefault();
- ev.stopPropagation();
- }
-
- this.pointerEnd();
- }
-
- /**
- * TODO
- * @param {TODO} ev TODO
- */
- pointerStart(ev) {
- this.start = dom.pointerCoord(ev);
-
- this.zone.runOutsideAngular(() => {
- Activator.start(ev.currentTarget);
- Activator.moveListeners(this.pointerMove, true);
- });
- }
-
- /**
- * TODO
- */
- pointerEnd() {
- this.zone.runOutsideAngular(() => {
- Activator.end();
- Activator.moveListeners(this.pointerMove, false);
- });
- }
-
- /**
- * TODO
- */
- pointerCancel() {
- this.start = null;
-
- this.zone.runOutsideAngular(() => {
- Activator.clear();
- Activator.moveListeners(this.pointerMove, false);
- });
- }
-
- /**
- * Whether the supplied click event should be allowed or not.
- * @param {MouseEvent} ev The click event.
- * @return {boolean} True if click event should be allowed, otherwise false.
- */
- allowClick(ev) {
- if (!ev.isIonicTap) {
- if (this.disableClick || !this.start) {
- return false;
- }
- }
- return true;
- }
-
- /**
- * TODO
- * @param {MouseEvent} ev TODO
- */
- click(ev) {
- if (!this.allowClick(ev)) {
- ev.preventDefault();
- ev.stopPropagation();
- }
- }
-
- /**
- * TODO
- */
- onDestroy() {
- this.ele = null;
- }
-
-}
diff --git a/ionic/components/checkbox/checkbox.ts b/ionic/components/checkbox/checkbox.ts
index 45367b92e0..1893946d40 100644
--- a/ionic/components/checkbox/checkbox.ts
+++ b/ionic/components/checkbox/checkbox.ts
@@ -10,7 +10,6 @@ import {Ion} from '../ion';
import {IonInput} from '../form/input';
import {IonicConfig} from '../../config/config';
import {IonicComponent, IonicView} from '../../config/annotations';
-import {TapClick} from '../button/button';
/**
* @name ionCheckbox
@@ -37,6 +36,7 @@ import {TapClick} from '../button/button';
host: {
'class': 'item',
'role': 'checkbox',
+ 'tappable': 'true',
'[attr.tab-index]': 'tabIndex',
'[attr.aria-checked]': 'checked',
'[attr.aria-disabled]': 'disabled',
@@ -59,16 +59,13 @@ export class Checkbox extends Ion {
* @param {ElementRef} elementRef TODO
* @param {IonicConfig} ionicConfig TODO
* @param {NgControl=} ngControl TODO
- * @param {TapClick=} tapClick TODO
*/
constructor(
elementRef: ElementRef,
config: IonicConfig,
- @Optional() ngControl: NgControl,
- tapClick: TapClick
+ @Optional() ngControl: NgControl
) {
super(elementRef, config);
- this.tapClick = tapClick;
this.tabIndex = 0;
this.id = IonInput.nextId();
@@ -102,11 +99,9 @@ export class Checkbox extends Ion {
* @param {MouseEvent} ev The click event.
*/
click(ev) {
- if (this.tapClick.allowClick(ev)) {
- ev.preventDefault();
- ev.stopPropagation();
- this.toggle();
- }
+ ev.preventDefault();
+ ev.stopPropagation();
+ this.toggle();
}
/**
diff --git a/ionic/components/radio/radio.ts b/ionic/components/radio/radio.ts
index b3bd25fb61..70f5ecab50 100644
--- a/ionic/components/radio/radio.ts
+++ b/ionic/components/radio/radio.ts
@@ -3,7 +3,6 @@ import {ElementRef, Host, Optional, NgControl, Query, QueryList} from 'angular2/
import {IonicDirective, IonicComponent, IonicView} from '../../config/annotations';
import {IonicConfig} from '../../config/config';
import {Ion} from '../ion';
-import {TapClick} from '../button/button';
import {ListHeader} from '../list/list';
@@ -11,37 +10,37 @@ import {ListHeader} from '../list/list';
* @name ionRadioGroup
* @classdesc
* A radio group is a group of radio components.
- *
+ *
* Selecting a radio button in the group unselects all others in the group.
- *
- * New radios can be registered dynamically.
+ *
+ * New radios can be registered dynamically.
*
* See the [Angular 2 Docs](https://angular.io/docs/js/latest/api/forms/) for more info on forms and input.
- *
+ *
* @example
* ```html
*
- *
+ *
*
* Clientside
*
- *
+ *
*
* Ember
*
- *
+ *
*
* Angular 1
*
- *
+ *
*
* Angular 2
*
- *
+ *
*
* React
*
- *
+ *
*
* ```
*/
@@ -153,7 +152,7 @@ export class RadioGroup extends Ion {
/**
* @name ionRadio
* @classdesc
- * A single radio component.
+ * A single radio component.
*
* See the [Angular 2 Docs](https://angular.io/docs/js/latest/api/forms/) for more info on forms and input.
*
@@ -176,6 +175,7 @@ export class RadioGroup extends Ion {
host: {
'class': 'item',
'role': 'radio',
+ 'tappable': 'true',
'[attr.id]': 'id',
'[attr.tab-index]': 'tabIndex',
'[attr.aria-checked]': 'checked',
@@ -199,16 +199,13 @@ export class RadioButton extends Ion {
* @param {RadioGroup=} group The parent radio group, if any.
* @param {ElementRef} elementRef TODO
* @param {IonicConfig} config TODO
- * @param {TapClick} tapClick TODO
*/
constructor(
@Host() @Optional() group: RadioGroup,
elementRef: ElementRef,
- config: IonicConfig,
- tapClick: TapClick
+ config: IonicConfig
) {
- super(elementRef, config);
- this.tapClick = tapClick;
+ super(elementRef, config)
this.group = group;
this.tabIndex = 0;
}
@@ -220,11 +217,9 @@ export class RadioButton extends Ion {
}
click(ev) {
- if (this.tapClick.allowClick(ev)) {
- ev.preventDefault();
- ev.stopPropagation();
- this.check();
- }
+ ev.preventDefault();
+ ev.stopPropagation();
+ this.check();
}
/**
diff --git a/ionic/components/switch/switch.ts b/ionic/components/switch/switch.ts
index 687eb1df51..0d6efeb9f0 100644
--- a/ionic/components/switch/switch.ts
+++ b/ionic/components/switch/switch.ts
@@ -23,6 +23,7 @@ import {pointerCoord} from '../../util/dom';
@Directive({
selector: '.media-switch',
host: {
+ 'tappable': 'true',
'(^touchstart)': 'swtch.pointerDown($event)',
'(^mousedown)': 'swtch.pointerDown($event)',
'[class.activated]': 'swtch.isActivated'
diff --git a/ionic/config/annotations.ts b/ionic/config/annotations.ts
index 3ff092ee81..e2168bd114 100644
--- a/ionic/config/annotations.ts
+++ b/ionic/config/annotations.ts
@@ -15,7 +15,6 @@ import {
Segment, SegmentButton, SegmentControlValueAccessor,
RadioGroup, RadioButton, SearchBar,
Nav, NavbarTemplate, Navbar, NavPush, NavPop, NavRouter,
- TapClick, TapDisabled,
IdRef,
ShowWhen, HideWhen,
@@ -87,10 +86,6 @@ export const IonicDirectives = [
forwardRef(() => ShowWhen),
forwardRef(() => HideWhen),
- // Gestures
- forwardRef(() => TapClick),
- forwardRef(() => TapDisabled),
-
// Material
forwardRef(() => MaterialButton)
];
diff --git a/ionic/util/activator.ts b/ionic/util/activator.ts
index 6025ca3a4b..2adb03c28c 100644
--- a/ionic/util/activator.ts
+++ b/ionic/util/activator.ts
@@ -1,61 +1,289 @@
-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;
+import {raf, pointerCoord, hasPointerMoved} from './dom';
export class Activator {
- static start(ele) {
- queueElements[++keyId] = ele;
- if (keyId > 9) keyId = 0;
- raf(Activator.activate);
+ constructor(app: IonicApp, config: IonicConfig, window, document) {
+ const self = this;
+ self.app = app;
+ self.config = config;
+ self.win = window;
+ self.doc = document;
+
+ self.id = 0;
+ self.queue = {};
+ self.active = {};
+ self.activatedClass = 'activated';
+ self.deactivateTimeout = 180;
+ self.pointerTolerance = 4;
+ self.isTouch = false;
+ self.disableClick = 0;
+ self.disableClickLimit = 2500;
+
+ self.tapPolyfill = config.setting('tapPolyfill');
+
+ function bindDom(type, listener, useCapture) {
+ document.addEventListener(type, listener, useCapture);
+ }
+
+ bindDom('click', function(ev) {
+ self.click(ev);
+ }, true);
+
+ bindDom('touchstart', function(ev) {
+ self.isTouch = true;
+ self.pointerStart(ev);
+ });
+
+ bindDom('touchend', function(ev) {
+ self.isTouch = true;
+ self.touchEnd(ev);
+ });
+
+ bindDom('touchcancel', function(ev) {
+ self.isTouch = true;
+ self.touchCancel(ev);
+ });
+
+ bindDom('mousedown', function(ev) {
+ self.mouseDown(ev);
+ }, true);
+
+ bindDom('mouseup', function(ev) {
+ self.mouseUp(ev);
+ }, true);
+
+
+ self.pointerMove = function(ev) {
+ let moveCoord = pointerCoord(ev);
+ console.log('pointerMove', moveCoord, self.start)
+
+ if ( hasPointerMoved(10, self.start, moveCoord) ) {
+ self.pointerCancel();
+ }
+ };
+
+
+ self.moveListeners = function(shouldAdd) {
+ document.removeEventListener('touchmove', self.pointerMove);
+ document.removeEventListener('mousemove', self.pointerMove);
+
+ if (shouldAdd) {
+ bindDom('touchmove', self.pointerMove);
+ bindDom('mousemove', self.pointerMove);
+ }
+ };
+
}
- 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];
+
+ /**
+ * TODO
+ * @param {TODO} ev TODO
+ */
+ touchEnd(ev) {
+ let self = this;
+
+ if (self.tapPolyfill && self.start) {
+ let endCoord = pointerCoord(ev);
+
+ if (!hasPointerMoved(self.pointerTolerance, self.start, endCoord)) {
+ console.debug('create click');
+
+ self.disableClick = Date.now();
+
+ let clickEvent = self.doc.createEvent('MouseEvents');
+ clickEvent.initMouseEvent('click', true, true, self.win, 1, 0, 0, endCoord.x, endCoord.y, false, false, false, false, 0, null);
+ clickEvent.isIonicTap = true;
+ ev.target.dispatchEvent(clickEvent);
}
}
- queueElements = {};
+
+ self.pointerEnd(ev);
}
- static end() {
- setTimeout(Activator.clear, DEACTIVATE_TIMEOUT);
+ /**
+ * TODO
+ * @param {TODO} ev TODO
+ */
+ mouseDown(ev) {
+ if (this.isDisabledClick()) {
+ console.debug('mouseDown prevent');
+ preventEvent(ev);
+
+ } else if (!self.isTouch) {
+ this.pointerStart(ev);
+ }
}
- static clear() {
+ /**
+ * TODO
+ * @param {TODO} ev TODO
+ */
+ mouseUp(ev) {
+ if (this.isDisabledClick()) {
+ console.debug('mouseUp prevent');
+ preventEvent(ev);
+ }
+
+ if (!self.isTouch) {
+ this.pointerEnd(ev);
+ }
+ }
+
+ /**
+ * TODO
+ * @param {TODO} ev TODO
+ */
+ pointerStart(ev) {
+ let targetEle = this.getActivatableTarget(ev.target);
+
+ if (targetEle) {
+ this.start = pointerCoord(ev);
+
+ this.queueActivate(targetEle);
+ this.moveListeners(true);
+
+ } else {
+ this.start = null;
+ }
+ }
+
+ /**
+ * TODO
+ */
+ pointerEnd(ev) {
+ this.endActive();
+ this.moveListeners(false);
+ }
+
+ /**
+ * TODO
+ */
+ pointerCancel() {
+ console.debug('pointerCancel')
+ this.clearActive();
+ this.moveListeners(false);
+ this.disableClick = Date.now();
+ }
+
+ isDisabledClick() {
+ return this.disableClick + this.disableClickLimit > Date.now();
+ }
+
+ /**
+ * Whether the supplied click event should be allowed or not.
+ * @param {MouseEvent} ev The click event.
+ * @return {boolean} True if click event should be allowed, otherwise false.
+ */
+ allowClick(ev) {
+ if (!ev.isIonicTap) {
+ if (this.isDisabledClick()) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * TODO
+ * @param {MouseEvent} ev TODO
+ */
+ click(ev) {
+ if (!this.allowClick(ev)) {
+ console.debug('click prevent');
+ preventEvent(ev);
+ }
+ this.isTouch = false;
+ }
+
+ getActivatableTarget(ele) {
+ var targetEle = ele;
+ for (var x = 0; x < 4; x++) {
+ if (!targetEle) break;
+ if (this.isActivatable(targetEle)) return targetEle;
+ targetEle = targetEle.parentElement;
+ }
+ return null;
+ }
+
+ isActivatable(ele) {
+ if (/^(A|BUTTON)$/.test(ele.tagName)) {
+ return true;
+ }
+
+ let attributes = ele.attributes;
+ for (let i = 0, l = attributes.length; i < l; i++) {
+ if (/click|tappable/.test(attributes[i].name)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ queueActivate(ele) {
+ const self = this;
+
+ self.queue[++self.id] = ele;
+ if (self.id > 19) self.id = 0;
+
+ raf(function(){
+ // activate all elements in the queue
+ for (var key in self.queue) {
+ if (self.queue[key]) {
+ self.queue[key].classList.add(self.activatedClass);
+ self.active[key] = self.queue[key];
+ }
+ }
+ self.queue = {};
+ });
+ }
+
+ endActive() {
+ const self = this;
+
+ setTimeout(function() {
+ self.clearActive();
+ }, this.deactivateTimeout);
+ }
+
+ clearActive() {
+ const self = this;
+
// clear out any elements that are queued to be set to active
- queueElements = {};
+ self.queue = {};
// 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);
+ raf(function() {
+ for (var key in self.active) {
+ if (self.active[key]) {
+ self.active[key].classList.remove(self.activatedClass);
+ }
+ delete self.active[key];
}
- delete activeElements[key];
- }
+ });
}
- static moveListeners(pointerMove, shouldAdd) {
- document.removeEventListener('touchmove', pointerMove);
- document.removeEventListener('mousemove', pointerMove);
+ clickBlock(enable) {
+ console.log('clickBlock', enable);
- if (shouldAdd) {
- document.addEventListener('touchmove', pointerMove);
- document.addEventListener('mousemove', pointerMove);
+ this.doc.removeEventListener('click', preventEvent, true);
+ this.doc.removeEventListener('touchmove', preventEvent, true);
+ this.doc.removeEventListener('touchstart', preventEvent, true);
+ this.doc.removeEventListener('touchend', preventEvent, true);
+
+ if (enable) {
+ this.doc.addEventListener('click', preventEvent, true);
+ this.doc.addEventListener('touchmove', preventEvent, true);
+ this.doc.addEventListener('touchstart', preventEvent, true);
+ this.doc.addEventListener('touchend', preventEvent, true);
}
}
}
+
+function preventEvent(ev) {
+ ev.preventDefault();
+ ev.stopPropagation();
+}