mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2026-03-13 10:22:08 +08:00
refactor(TapClick): create as injectable, outside zone listeners
This commit is contained in:
@@ -1,184 +1,168 @@
|
||||
import {pointerCoord, hasPointerMoved, transitionEnd} from '../../util/dom';
|
||||
import {Injectable, NgZone} from 'angular2/angular2';
|
||||
|
||||
import {IonicApp} from '../app/app';
|
||||
import {Config} from '../../config/config';
|
||||
import {pointerCoord, hasPointerMoved} from '../../util/dom';
|
||||
import {Activator} from './activator';
|
||||
import {RippleActivator} from './ripple';
|
||||
|
||||
|
||||
let startCoord = null;
|
||||
let pointerTolerance = 4;
|
||||
let lastTouch = 0;
|
||||
let lastActivated = 0;
|
||||
let disableNativeClickUntil = 0;
|
||||
let disableNativeClickAmount = 3000;
|
||||
let activator = null;
|
||||
let isTapPolyfill = false;
|
||||
let app = null;
|
||||
let win = null;
|
||||
let doc = null;
|
||||
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
export function initTapClick(windowInstance, documentInstance, appInstance, config) {
|
||||
win = windowInstance;
|
||||
doc = documentInstance;
|
||||
app = appInstance;
|
||||
@Injectable()
|
||||
export class TapClick {
|
||||
constructor(app: IonicApp, config: Config, zone: NgZone) {
|
||||
this.app = app;
|
||||
this.zone = zone;
|
||||
|
||||
if (config.get('activator') == 'ripple') {
|
||||
activator = new RippleActivator(app, config);
|
||||
this.lastTouch = 0;
|
||||
this.disableClick = 0;
|
||||
this.lastActivated = 0;
|
||||
|
||||
} else if (config.get('activator') == 'highlight') {
|
||||
activator = new Activator(app, config);
|
||||
if (config.get('activator') == 'ripple') {
|
||||
this.activator = new RippleActivator(app, config);
|
||||
|
||||
} else if (config.get('activator') == 'highlight') {
|
||||
this.activator = new Activator(app, config);
|
||||
}
|
||||
|
||||
this.usePolyfill = (config.get('tapPolyfill') === true);
|
||||
|
||||
zone.runOutsideAngular(() => {
|
||||
addListener('click', this.click.bind(this), true);
|
||||
|
||||
addListener('touchstart', this.touchStart.bind(this));
|
||||
addListener('touchend', this.touchEnd.bind(this));
|
||||
addListener('touchcancel', this.pointerCancel.bind(this));
|
||||
|
||||
addListener('mousedown', this.mouseDown.bind(this), true);
|
||||
addListener('mouseup', this.mouseUp.bind(this), true);
|
||||
});
|
||||
}
|
||||
|
||||
isTapPolyfill = (config.get('tapPolyfill') === true);
|
||||
touchStart(ev) {
|
||||
this.lastTouch = Date.now();
|
||||
this.pointerStart(ev);
|
||||
}
|
||||
|
||||
addListener('click', click, true);
|
||||
touchEnd(ev) {
|
||||
this.lastTouch = Date.now();
|
||||
|
||||
addListener('touchstart', touchStart);
|
||||
addListener('touchend', touchEnd);
|
||||
addListener('touchcancel', touchCancel);
|
||||
if (this.usePolyfill && this.startCoord && this.app.isEnabled()) {
|
||||
let endCoord = pointerCoord(ev);
|
||||
|
||||
addListener('mousedown', mouseDown, true);
|
||||
addListener('mouseup', mouseUp, true);
|
||||
}
|
||||
if (!hasPointerMoved(POINTER_TOLERANCE, this.startCoord, endCoord)) {
|
||||
console.debug('create click from touch');
|
||||
|
||||
// prevent native mouse click events for XX amount of time
|
||||
this.disableClick = this.lastTouch + DISABLE_NATIVE_CLICK_AMOUNT;
|
||||
|
||||
function touchStart(ev) {
|
||||
touchAction();
|
||||
pointerStart(ev);
|
||||
}
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
|
||||
function touchEnd(ev) {
|
||||
touchAction();
|
||||
this.pointerEnd(ev);
|
||||
}
|
||||
|
||||
if (isTapPolyfill && startCoord && app.isEnabled()) {
|
||||
let endCoord = pointerCoord(ev);
|
||||
mouseDown(ev) {
|
||||
if (this.isDisabledNativeClick()) {
|
||||
console.debug('mouseDown prevent', ev.target.tagName);
|
||||
// does not prevent default on purpose
|
||||
// so native blur events from inputs can happen
|
||||
ev.stopPropagation();
|
||||
|
||||
if (!hasPointerMoved(pointerTolerance, startCoord, endCoord)) {
|
||||
console.debug('create click from touch');
|
||||
|
||||
disableNativeClickUntil = Date.now() + disableNativeClickAmount;
|
||||
|
||||
let clickEvent = doc.createEvent('MouseEvents');
|
||||
clickEvent.initMouseEvent('click', true, true, win, 1, 0, 0, endCoord.x, endCoord.y, false, false, false, false, 0, null);
|
||||
clickEvent.isIonicTap = true;
|
||||
ev.target.dispatchEvent(clickEvent);
|
||||
} else if (this.lastTouch + DISABLE_NATIVE_CLICK_AMOUNT < Date.now()) {
|
||||
this.pointerStart(ev);
|
||||
}
|
||||
}
|
||||
|
||||
pointerEnd(ev);
|
||||
}
|
||||
|
||||
function touchCancel(ev) {
|
||||
touchAction();
|
||||
pointerCancel(ev);
|
||||
}
|
||||
|
||||
function mouseDown(ev) {
|
||||
if (isDisabledNativeClick()) {
|
||||
console.debug('mouseDown prevent', ev.target.tagName);
|
||||
// does not prevent default on purpose
|
||||
// so native blur events from inputs can happen
|
||||
ev.stopPropagation();
|
||||
|
||||
} else if (lastTouch + disableNativeClickAmount < Date.now()) {
|
||||
pointerStart(ev);
|
||||
}
|
||||
}
|
||||
|
||||
function mouseUp(ev) {
|
||||
if (isDisabledNativeClick()) {
|
||||
console.debug('mouseUp prevent', ev.target.tagName);
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
}
|
||||
|
||||
if (lastTouch + disableNativeClickAmount < Date.now()) {
|
||||
pointerEnd(ev);
|
||||
}
|
||||
}
|
||||
|
||||
function pointerStart(ev) {
|
||||
let activatableEle = getActivatableTarget(ev.target);
|
||||
|
||||
if (activatableEle) {
|
||||
startCoord = pointerCoord(ev);
|
||||
|
||||
let now = Date.now();
|
||||
if (lastActivated + 150 < now) {
|
||||
activator && activator.downAction(ev, activatableEle, startCoord.x, startCoord.y);
|
||||
lastActivated = now;
|
||||
mouseUp(ev) {
|
||||
if (this.isDisabledNativeClick()) {
|
||||
console.debug('mouseUp prevent', ev.target.tagName);
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
}
|
||||
|
||||
moveListeners(true);
|
||||
|
||||
} else {
|
||||
startCoord = null;
|
||||
}
|
||||
}
|
||||
|
||||
function pointerEnd(ev) {
|
||||
moveListeners(false);
|
||||
activator && activator.upAction();
|
||||
}
|
||||
|
||||
function pointerMove(ev) {
|
||||
let moveCoord = pointerCoord(ev);
|
||||
|
||||
if ( hasPointerMoved(10, startCoord, moveCoord) ) {
|
||||
pointerCancel(ev);
|
||||
}
|
||||
}
|
||||
|
||||
function pointerCancel(ev) {
|
||||
console.debug('pointerCancel from', ev.type);
|
||||
activator && activator.clearState();
|
||||
moveListeners(false);
|
||||
}
|
||||
|
||||
function moveListeners(shouldAdd) {
|
||||
if (shouldAdd) {
|
||||
if (isTapPolyfill) {
|
||||
addListener('touchmove', pointerMove);
|
||||
if (this.lastTouch + DISABLE_NATIVE_CLICK_AMOUNT < Date.now()) {
|
||||
this.pointerEnd(ev);
|
||||
}
|
||||
addListener('mousemove', pointerMove);
|
||||
}
|
||||
|
||||
} else {
|
||||
if (isTapPolyfill) {
|
||||
removeListener('touchmove', pointerMove);
|
||||
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;
|
||||
}
|
||||
removeListener('mousemove', pointerMove);
|
||||
}
|
||||
}
|
||||
|
||||
function setDisableNativeClick() {
|
||||
if (isTapPolyfill) {
|
||||
disableNativeClickTime = Date.now() + disableNativeClickLimit;
|
||||
}
|
||||
}
|
||||
|
||||
function isDisabledNativeClick() {
|
||||
return disableNativeClickUntil > Date.now();
|
||||
}
|
||||
|
||||
function click(ev) {
|
||||
let preventReason = null;
|
||||
|
||||
if (!app.isEnabled()) {
|
||||
preventReason = 'appDisabled';
|
||||
|
||||
} else if (!ev.isIonicTap && isDisabledNativeClick()) {
|
||||
preventReason = 'nativeClick';
|
||||
}
|
||||
|
||||
if (preventReason !== null) {
|
||||
console.debug('click prevent', preventReason);
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
pointerEnd(ev) {
|
||||
this.moveListeners(false);
|
||||
this.activator && this.activator.upAction();
|
||||
}
|
||||
|
||||
pointerMove(ev) {
|
||||
if ( hasPointerMoved(POINTER_MOVE_UNTIL_CANCEL, this.startCoord, pointerCoord(ev)) ) {
|
||||
this.pointerCancel(ev);
|
||||
}
|
||||
}
|
||||
|
||||
pointerCancel(ev) {
|
||||
console.debug('pointerCancel from', ev.type);
|
||||
this.activator && this.activator.clearState();
|
||||
this.moveListeners(false);
|
||||
}
|
||||
|
||||
moveListeners(shouldAdd) {
|
||||
this.zone.runOutsideAngular(() => {
|
||||
if (shouldAdd) {
|
||||
addListener(this.usePolyfill ? 'touchmove' : 'mousemove', this.pointerMove.bind(this));
|
||||
} else {
|
||||
removeListener(this.usePolyfill ? 'touchmove' : 'mousemove', this.pointerMove.bind(this));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
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);
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
}
|
||||
}
|
||||
|
||||
isDisabledNativeClick() {
|
||||
return this.disableClick > Date.now();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
function getActivatableTarget(ele) {
|
||||
let targetEle = ele;
|
||||
for (let x = 0; x < 4; x++) {
|
||||
@@ -207,17 +191,16 @@ export function isActivatable(ele) {
|
||||
return false;
|
||||
}
|
||||
|
||||
function touchAction() {
|
||||
lastTouch = Date.now();
|
||||
}
|
||||
|
||||
function addListener(type, listener, useCapture) {
|
||||
doc.addEventListener(type, listener, useCapture);
|
||||
document.addEventListener(type, listener, useCapture);
|
||||
}
|
||||
|
||||
function removeListener(type, listener) {
|
||||
doc.removeEventListener(type, listener);
|
||||
document.removeEventListener(type, listener);
|
||||
}
|
||||
|
||||
const ACTIVATABLE_ELEMENTS = /^(A|BUTTON)$/;
|
||||
const ACTIVATABLE_ATTRIBUTES = /tappable/;
|
||||
const POINTER_TOLERANCE = 4;
|
||||
const POINTER_MOVE_UNTIL_CANCEL = 10;
|
||||
const DISABLE_NATIVE_CLICK_AMOUNT = 2500;
|
||||
|
||||
@@ -16,7 +16,7 @@ import {NavRegistry} from '../components/nav/nav-registry';
|
||||
import {Translate} from '../translation/translate';
|
||||
import {ClickBlock} from '../util/click-block';
|
||||
import {FeatureDetect} from '../util/feature-detect';
|
||||
import {initTapClick} from '../components/tap-click/tap-click';
|
||||
import {TapClick} from '../components/tap-click/tap-click';
|
||||
import {ClickBlock} from '../util/click-block';
|
||||
import {ready, closest} from '../util/dom';
|
||||
|
||||
@@ -42,7 +42,6 @@ export function ionicProviders(args={}) {
|
||||
let app = new IonicApp(config, clickBlock);
|
||||
|
||||
let events = new Events();
|
||||
initTapClick(window, document, app, config);
|
||||
let featureDetect = new FeatureDetect();
|
||||
|
||||
setupDom(window, document, config, platform, clickBlock, featureDetect);
|
||||
@@ -58,6 +57,7 @@ export function ionicProviders(args={}) {
|
||||
provide(FeatureDetect, {useValue: featureDetect}),
|
||||
provide(Events, {useValue: events}),
|
||||
provide(NavRegistry, {useValue: navRegistry}),
|
||||
TapClick,
|
||||
Form,
|
||||
Keyboard,
|
||||
OverlayController,
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import {Component, bootstrap} from 'angular2/angular2'
|
||||
import {wtfLeave, wtfCreateScope} from 'angular2/angular2';
|
||||
|
||||
import * as util from 'ionic/util';
|
||||
import {TapClick} from '../components/tap-click/tap-click';
|
||||
import {pascalCaseToDashCase} from '../util/util';
|
||||
import {ionicProviders} from './bootstrap';
|
||||
import {IONIC_DIRECTIVES} from './directives';
|
||||
|
||||
@@ -105,7 +107,7 @@ function appendConfig(cls, config) {
|
||||
|
||||
// set the component "hostProperties", so the instance's
|
||||
// input value will be used to set the element's attribute
|
||||
config.host['[attr.' + util.pascalCaseToDashCase(prop) + ']'] = prop;
|
||||
config.host['[attr.' + pascalCaseToDashCase(prop) + ']'] = prop;
|
||||
}
|
||||
|
||||
cls.delegates = config.delegates;
|
||||
@@ -137,9 +139,10 @@ export function App(args={}) {
|
||||
// redefine with added annotations
|
||||
Reflect.defineMetadata('annotations', annotations, cls);
|
||||
|
||||
console.time('bootstrap');
|
||||
bootstrap(cls, ionicProviders(args)).then(() => {
|
||||
console.timeEnd('bootstrap');
|
||||
let wtfScope = wtfCreateScope('IonicBootstrap')();
|
||||
bootstrap(cls, ionicProviders(args)).then(appRef => {
|
||||
let tc = appRef.injector.get(TapClick);
|
||||
wtfLeave(wtfScope);
|
||||
});
|
||||
|
||||
return cls;
|
||||
|
||||
Reference in New Issue
Block a user