mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2025-08-18 19:21:34 +08:00
212 lines
5.4 KiB
TypeScript
212 lines
5.4 KiB
TypeScript
import {Injectable, NgZone} from 'angular2/core';
|
|
|
|
import {IonicApp} from '../app/app';
|
|
import {Config} from '../../config/config';
|
|
import {pointerCoord, hasPointerMoved} from '../../util/dom';
|
|
import {Activator} from './activator';
|
|
import {RippleActivator} from './ripple';
|
|
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
@Injectable()
|
|
export class TapClick {
|
|
constructor(app: IonicApp, config: Config, zone: NgZone) {
|
|
let self = this;
|
|
self.app = app;
|
|
self.zone = zone;
|
|
|
|
self.lastTouch = 0;
|
|
self.disableClick = 0;
|
|
self.lastActivated = 0;
|
|
|
|
if (config.get('activator') == 'ripple') {
|
|
self.activator = new RippleActivator(app, config, zone);
|
|
|
|
} else if (config.get('activator') == 'highlight') {
|
|
self.activator = new Activator(app, config, zone);
|
|
}
|
|
|
|
self.usePolyfill = (config.get('tapPolyfill') === true);
|
|
|
|
zone.runOutsideAngular(() => {
|
|
addListener('click', self.click.bind(self), true);
|
|
|
|
addListener('touchstart', self.touchStart.bind(self));
|
|
addListener('touchend', self.touchEnd.bind(self));
|
|
addListener('touchcancel', self.pointerCancel.bind(self));
|
|
|
|
addListener('mousedown', self.mouseDown.bind(self), true);
|
|
addListener('mouseup', self.mouseUp.bind(self), true);
|
|
});
|
|
|
|
|
|
self.pointerMove = function(ev) {
|
|
if ( hasPointerMoved(POINTER_MOVE_UNTIL_CANCEL, self.startCoord, pointerCoord(ev)) ) {
|
|
self.pointerCancel(ev);
|
|
}
|
|
};
|
|
}
|
|
|
|
touchStart(ev) {
|
|
this.lastTouch = Date.now();
|
|
this.pointerStart(ev);
|
|
}
|
|
|
|
touchEnd(ev) {
|
|
this.lastTouch = Date.now();
|
|
|
|
if (this.usePolyfill && this.startCoord && this.app.isEnabled()) {
|
|
let endCoord = pointerCoord(ev);
|
|
|
|
|
|
if (!hasPointerMoved(POINTER_TOLERANCE, this.startCoord, endCoord)) {
|
|
console.debug('create click from touch ' + Date.now());
|
|
|
|
// prevent native mouse click events for XX amount of time
|
|
this.disableClick = this.lastTouch + DISABLE_NATIVE_CLICK_AMOUNT;
|
|
|
|
// manually dispatch the mouse click event
|
|
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;
|
|
ev.target.dispatchEvent(clickEvent);
|
|
}
|
|
}
|
|
|
|
this.pointerEnd(ev);
|
|
}
|
|
|
|
mouseDown(ev) {
|
|
if (this.isDisabledNativeClick()) {
|
|
console.debug('mouseDown prevent ' + ev.target.tagName + ' ' + Date.now());
|
|
// does not prevent default on purpose
|
|
// so native blur events from inputs can happen
|
|
ev.stopPropagation();
|
|
|
|
} else if (this.lastTouch + DISABLE_NATIVE_CLICK_AMOUNT < Date.now()) {
|
|
this.pointerStart(ev);
|
|
}
|
|
}
|
|
|
|
mouseUp(ev) {
|
|
if (this.isDisabledNativeClick()) {
|
|
console.debug('mouseUp prevent ' + ev.target.tagName + ' ' + Date.now());
|
|
ev.preventDefault();
|
|
ev.stopPropagation();
|
|
}
|
|
|
|
if (this.lastTouch + DISABLE_NATIVE_CLICK_AMOUNT < Date.now()) {
|
|
this.pointerEnd(ev);
|
|
}
|
|
}
|
|
|
|
pointerStart(ev) {
|
|
let activatableEle = getActivatableTarget(ev.target);
|
|
|
|
if (activatableEle) {
|
|
this.startCoord = pointerCoord(ev);
|
|
|
|
let now = Date.now();
|
|
if (this.lastActivated + 150 < now) {
|
|
this.activator && this.activator.downAction(ev, activatableEle, this.startCoord.x, this.startCoord.y);
|
|
this.lastActivated = now;
|
|
}
|
|
|
|
this.moveListeners(true);
|
|
|
|
} else {
|
|
this.startCoord = null;
|
|
}
|
|
}
|
|
|
|
pointerEnd(ev) {
|
|
this.moveListeners(false);
|
|
this.activator && this.activator.upAction();
|
|
}
|
|
|
|
pointerCancel(ev) {
|
|
console.debug('pointerCancel from ' + ev.type + ' ' + Date.now());
|
|
this.activator && this.activator.clearState();
|
|
this.moveListeners(false);
|
|
}
|
|
|
|
moveListeners(shouldAdd) {
|
|
removeListener(this.usePolyfill ? 'touchmove' : 'mousemove', this.pointerMove);
|
|
|
|
//this.zone.runOutsideAngular(() => {
|
|
if (shouldAdd) {
|
|
addListener(this.usePolyfill ? 'touchmove' : 'mousemove', this.pointerMove);
|
|
} else {
|
|
|
|
}
|
|
//});
|
|
}
|
|
|
|
click(ev) {
|
|
let preventReason = null;
|
|
|
|
if (!this.app.isEnabled()) {
|
|
preventReason = 'appDisabled';
|
|
|
|
} else if (!ev.isIonicTap && this.isDisabledNativeClick()) {
|
|
preventReason = 'nativeClick';
|
|
}
|
|
|
|
if (preventReason !== null) {
|
|
console.debug('click prevent ' + preventReason + ' ' + Date.now());
|
|
ev.preventDefault();
|
|
ev.stopPropagation();
|
|
}
|
|
}
|
|
|
|
isDisabledNativeClick() {
|
|
return this.disableClick > Date.now();
|
|
}
|
|
|
|
}
|
|
|
|
|
|
function getActivatableTarget(ele) {
|
|
let targetEle = ele;
|
|
for (let x = 0; x < 4; x++) {
|
|
if (!targetEle) break;
|
|
if (isActivatable(targetEle)) return targetEle;
|
|
targetEle = targetEle.parentElement;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
export function isActivatable(ele) {
|
|
if (ACTIVATABLE_ELEMENTS.test(ele.tagName)) {
|
|
return true;
|
|
}
|
|
|
|
let attributes = ele.attributes;
|
|
for (let i = 0, l = attributes.length; i < l; i++) {
|
|
if (ACTIVATABLE_ATTRIBUTES.test(attributes[i].name)) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
function addListener(type, listener, useCapture) {
|
|
document.addEventListener(type, listener, useCapture);
|
|
}
|
|
|
|
function removeListener(type, listener) {
|
|
document.removeEventListener(type, listener);
|
|
}
|
|
|
|
const ACTIVATABLE_ELEMENTS = /^(A|BUTTON)$/;
|
|
const ACTIVATABLE_ATTRIBUTES = /tappable|button/i;
|
|
const POINTER_TOLERANCE = 4;
|
|
const POINTER_MOVE_UNTIL_CANCEL = 10;
|
|
const DISABLE_NATIVE_CLICK_AMOUNT = 2500;
|