mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2025-08-21 13:01:01 +08:00
fix(tap-click): do not dispatch click events if tap-click blocks them
- do not dispatch clicks if gesture controller captured a gesture
This commit is contained in:
@ -8,6 +8,7 @@ import { assert, runInDev } from '../../util/util';
|
|||||||
import { hasPointerMoved, pointerCoord } from '../../util/dom';
|
import { hasPointerMoved, pointerCoord } from '../../util/dom';
|
||||||
import { RippleActivator } from './ripple';
|
import { RippleActivator } from './ripple';
|
||||||
import { UIEventManager, PointerEvents, PointerEventType } from '../../util/ui-event-manager';
|
import { UIEventManager, PointerEvents, PointerEventType } from '../../util/ui-event-manager';
|
||||||
|
import { GestureController } from '../../gestures/gesture-controller';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @private
|
* @private
|
||||||
@ -21,11 +22,13 @@ export class TapClick {
|
|||||||
private events: UIEventManager = new UIEventManager(false);
|
private events: UIEventManager = new UIEventManager(false);
|
||||||
private pointerEvents: PointerEvents;
|
private pointerEvents: PointerEvents;
|
||||||
private lastTouchEnd: number;
|
private lastTouchEnd: number;
|
||||||
|
private dispatchClick: boolean;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
config: Config,
|
config: Config,
|
||||||
private app: App,
|
private app: App,
|
||||||
zone: NgZone
|
zone: NgZone,
|
||||||
|
private gestureCtrl: GestureController
|
||||||
) {
|
) {
|
||||||
let activator = config.get('activator');
|
let activator = config.get('activator');
|
||||||
if (activator === 'ripple') {
|
if (activator === 'ripple') {
|
||||||
@ -53,6 +56,7 @@ export class TapClick {
|
|||||||
if (this.startCoord) {
|
if (this.startCoord) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
let activatableEle = getActivatableTarget(ev.target);
|
let activatableEle = getActivatableTarget(ev.target);
|
||||||
if (!activatableEle) {
|
if (!activatableEle) {
|
||||||
this.startCoord = null;
|
this.startCoord = null;
|
||||||
@ -60,20 +64,25 @@ export class TapClick {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.lastTouchEnd = 0;
|
this.lastTouchEnd = 0;
|
||||||
|
this.dispatchClick = true;
|
||||||
this.startCoord = pointerCoord(ev);
|
this.startCoord = pointerCoord(ev);
|
||||||
this.activator && this.activator.downAction(ev, activatableEle, this.startCoord);
|
this.activator && this.activator.downAction(ev, activatableEle, this.startCoord);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
pointerMove(ev: UIEvent) {
|
pointerMove(ev: UIEvent) {
|
||||||
if (!this.startCoord ||
|
assert(this.startCoord, 'startCoord must be valid');
|
||||||
hasPointerMoved(POINTER_TOLERANCE, this.startCoord, pointerCoord(ev)) ||
|
assert(this.dispatchClick === true, 'disableClick must be true');
|
||||||
this.app.isScrolling()) {
|
|
||||||
|
if (this.shouldCancelEvent(ev)) {
|
||||||
this.pointerCancel(ev);
|
this.pointerCancel(ev);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pointerEnd(ev: any, type: PointerEventType) {
|
pointerEnd(ev: any, type: PointerEventType) {
|
||||||
|
assert(this.startCoord, 'startCoord must be valid');
|
||||||
|
assert(this.dispatchClick === true, 'disableClick must be true');
|
||||||
|
|
||||||
runInDev(() => this.lastTouchEnd = Date.now());
|
runInDev(() => this.lastTouchEnd = Date.now());
|
||||||
|
|
||||||
if (!this.startCoord) {
|
if (!this.startCoord) {
|
||||||
@ -94,28 +103,27 @@ export class TapClick {
|
|||||||
pointerCancel(ev: UIEvent) {
|
pointerCancel(ev: UIEvent) {
|
||||||
console.debug(`pointerCancel from ${ev.type} ${Date.now()}`);
|
console.debug(`pointerCancel from ${ev.type} ${Date.now()}`);
|
||||||
this.startCoord = null;
|
this.startCoord = null;
|
||||||
|
this.dispatchClick = false;
|
||||||
this.activator && this.activator.clearState();
|
this.activator && this.activator.clearState();
|
||||||
this.pointerEvents.stop();
|
this.pointerEvents.stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
shouldCancelEvent(ev: UIEvent): boolean {
|
||||||
|
return (
|
||||||
|
this.app.isScrolling() ||
|
||||||
|
this.gestureCtrl.isCaptured() ||
|
||||||
|
hasPointerMoved(POINTER_TOLERANCE, this.startCoord, pointerCoord(ev))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
click(ev: any) {
|
click(ev: any) {
|
||||||
let preventReason: string = null;
|
if (this.shouldCancelClick(ev)) {
|
||||||
|
|
||||||
if (!this.app.isEnabled()) {
|
|
||||||
preventReason = 'appDisabled';
|
|
||||||
|
|
||||||
} else if (this.usePolyfill && !ev.isIonicTap && this.isDisabledNativeClick()) {
|
|
||||||
preventReason = 'nativeClick';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (preventReason !== null) {
|
|
||||||
// darn, there was a reason to prevent this click, let's not allow it
|
|
||||||
console.debug(`click prevent ${preventReason} ${Date.now()}`);
|
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
ev.stopPropagation();
|
ev.stopPropagation();
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
} else if (this.activator) {
|
if (this.activator) {
|
||||||
// cool, a click is gonna happen, let's tell the activator
|
// cool, a click is gonna happen, let's tell the activator
|
||||||
// so the element can get the given "active" style
|
// so the element can get the given "active" style
|
||||||
const activatableEle = getActivatableTarget(ev.target);
|
const activatableEle = getActivatableTarget(ev.target);
|
||||||
@ -123,19 +131,39 @@ export class TapClick {
|
|||||||
this.activator.clickAction(ev, activatableEle, this.startCoord);
|
this.activator.clickAction(ev, activatableEle, this.startCoord);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
runInDev(() => {
|
|
||||||
if (this.lastTouchEnd) {
|
runInDev(() => this.profileClickDelay(ev));
|
||||||
let diff = Date.now() - this.lastTouchEnd;
|
}
|
||||||
if (diff < 100) {
|
|
||||||
console.debug(`FAST click dispatched. Delay(ms):`, diff);
|
private shouldCancelClick(ev: any): boolean {
|
||||||
} else {
|
if (this.usePolyfill) {
|
||||||
console.warn(`SLOW click dispatched. Delay(ms):`, diff, ev);
|
if (!ev.isIonicTap && this.isDisabledNativeClick()) {
|
||||||
}
|
console.debug('click prevent: nativeClick');
|
||||||
this.lastTouchEnd = null;
|
return true;
|
||||||
} else {
|
|
||||||
console.debug('Click dispatched. Unknown delay');
|
|
||||||
}
|
}
|
||||||
});
|
} else if (!this.dispatchClick) {
|
||||||
|
console.debug('click prevent: tap-click');
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (!this.app.isEnabled()) {
|
||||||
|
console.debug('click prevent: appDisabled');
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private profileClickDelay(ev: any) {
|
||||||
|
if (this.lastTouchEnd) {
|
||||||
|
let diff = Date.now() - this.lastTouchEnd;
|
||||||
|
if (diff < 100) {
|
||||||
|
console.debug(`FAST click dispatched. Delay(ms):`, diff);
|
||||||
|
} else {
|
||||||
|
console.warn(`SLOW click dispatched. Delay(ms):`, diff, ev);
|
||||||
|
}
|
||||||
|
this.lastTouchEnd = null;
|
||||||
|
} else {
|
||||||
|
console.debug('Click dispatched. Unknown delay');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleTapPolyfill(ev: any) {
|
handleTapPolyfill(ev: any) {
|
||||||
@ -202,11 +230,11 @@ export const isActivatable = function (ele: HTMLElement) {
|
|||||||
|
|
||||||
const ACTIVATABLE_ELEMENTS = ['A', 'BUTTON'];
|
const ACTIVATABLE_ELEMENTS = ['A', 'BUTTON'];
|
||||||
const ACTIVATABLE_ATTRIBUTES = ['tappable', 'ion-button'];
|
const ACTIVATABLE_ATTRIBUTES = ['tappable', 'ion-button'];
|
||||||
const POINTER_TOLERANCE = 60;
|
const POINTER_TOLERANCE = 100;
|
||||||
const DISABLE_NATIVE_CLICK_AMOUNT = 2500;
|
const DISABLE_NATIVE_CLICK_AMOUNT = 2500;
|
||||||
|
|
||||||
export function setupTapClick(config: Config, app: App, zone: NgZone) {
|
export function setupTapClick(config: Config, app: App, zone: NgZone, gestureCtrl: GestureController) {
|
||||||
return function() {
|
return function() {
|
||||||
return new TapClick(config, app, zone);
|
return new TapClick(config, app, zone, gestureCtrl);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -155,7 +155,7 @@ export class IonicModule {
|
|||||||
{ provide: APP_INITIALIZER, useFactory: registerModeConfigs, deps: [ Config ], multi: true },
|
{ provide: APP_INITIALIZER, useFactory: registerModeConfigs, deps: [ Config ], multi: true },
|
||||||
{ provide: APP_INITIALIZER, useFactory: registerTransitions, deps: [ Config ], multi: true },
|
{ provide: APP_INITIALIZER, useFactory: registerTransitions, deps: [ Config ], multi: true },
|
||||||
{ provide: APP_INITIALIZER, useFactory: setupProvideEvents, deps: [ Platform ], multi: true },
|
{ provide: APP_INITIALIZER, useFactory: setupProvideEvents, deps: [ Platform ], multi: true },
|
||||||
{ provide: APP_INITIALIZER, useFactory: setupTapClick, deps: [ Config, App, NgZone ], multi: true },
|
{ provide: APP_INITIALIZER, useFactory: setupTapClick, deps: [ Config, App, NgZone, GestureController ], multi: true },
|
||||||
|
|
||||||
// useClass
|
// useClass
|
||||||
{ provide: HAMMER_GESTURE_CONFIG, useClass: IonicGestureConfig },
|
{ provide: HAMMER_GESTURE_CONFIG, useClass: IonicGestureConfig },
|
||||||
|
Reference in New Issue
Block a user