Skip to content

Commit

Permalink
fix(tap-click): do not dispatch click events if tap-click blocks them
Browse files Browse the repository at this point in the history
- do not dispatch clicks if gesture controller captured a gesture
  • Loading branch information
manucorporat committed Nov 28, 2016
1 parent 907191b commit 8f8185b
Show file tree
Hide file tree
Showing 2 changed files with 60 additions and 32 deletions.
90 changes: 59 additions & 31 deletions src/components/tap-click/tap-click.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { assert, runInDev } from '../../util/util';
import { hasPointerMoved, pointerCoord } from '../../util/dom';
import { RippleActivator } from './ripple';
import { UIEventManager, PointerEvents, PointerEventType } from '../../util/ui-event-manager';
import { GestureController } from '../../gestures/gesture-controller';

/**
* @private
Expand All @@ -21,11 +22,13 @@ export class TapClick {
private events: UIEventManager = new UIEventManager(false);
private pointerEvents: PointerEvents;
private lastTouchEnd: number;
private dispatchClick: boolean;

constructor(
config: Config,
private app: App,
zone: NgZone
zone: NgZone,
private gestureCtrl: GestureController
) {
let activator = config.get('activator');
if (activator === 'ripple') {
Expand Down Expand Up @@ -53,27 +56,33 @@ export class TapClick {
if (this.startCoord) {
return false;
}

let activatableEle = getActivatableTarget(ev.target);
if (!activatableEle) {
this.startCoord = null;
return false;
}

this.lastTouchEnd = 0;
this.dispatchClick = true;
this.startCoord = pointerCoord(ev);
this.activator && this.activator.downAction(ev, activatableEle, this.startCoord);
return true;
}

pointerMove(ev: UIEvent) {
if (!this.startCoord ||
hasPointerMoved(POINTER_TOLERANCE, this.startCoord, pointerCoord(ev)) ||
this.app.isScrolling()) {
assert(this.startCoord, 'startCoord must be valid');
assert(this.dispatchClick === true, 'disableClick must be true');

if (this.shouldCancelEvent(ev)) {
this.pointerCancel(ev);
}
}

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());

if (!this.startCoord) {
Expand All @@ -94,48 +103,67 @@ export class TapClick {
pointerCancel(ev: UIEvent) {
console.debug(`pointerCancel from ${ev.type} ${Date.now()}`);
this.startCoord = null;
this.dispatchClick = false;
this.activator && this.activator.clearState();
this.pointerEvents.stop();
}

click(ev: any) {
let preventReason: string = null;

if (!this.app.isEnabled()) {
preventReason = 'appDisabled';

} else if (this.usePolyfill && !ev.isIonicTap && this.isDisabledNativeClick()) {
preventReason = 'nativeClick';
}
shouldCancelEvent(ev: UIEvent): boolean {
return (
this.app.isScrolling() ||
this.gestureCtrl.isCaptured() ||
hasPointerMoved(POINTER_TOLERANCE, this.startCoord, pointerCoord(ev))
);
}

if (preventReason !== null) {
// darn, there was a reason to prevent this click, let's not allow it
console.debug(`click prevent ${preventReason} ${Date.now()}`);
click(ev: any) {
if (this.shouldCancelClick(ev)) {
ev.preventDefault();
ev.stopPropagation();
return;
}

} else if (this.activator) {
if (this.activator) {
// cool, a click is gonna happen, let's tell the activator
// so the element can get the given "active" style
const activatableEle = getActivatableTarget(ev.target);
if (activatableEle) {
this.activator.clickAction(ev, activatableEle, this.startCoord);
}
}
runInDev(() => {
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;

runInDev(() => this.profileClickDelay(ev));
}

private shouldCancelClick(ev: any): boolean {
if (this.usePolyfill) {
if (!ev.isIonicTap && this.isDisabledNativeClick()) {
console.debug('click prevent: nativeClick');
return true;
}
} 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.debug('Click dispatched. Unknown delay');
console.warn(`SLOW click dispatched. Delay(ms):`, diff, ev);
}
});
this.lastTouchEnd = null;
} else {
console.debug('Click dispatched. Unknown delay');
}
}

handleTapPolyfill(ev: any) {
Expand Down Expand Up @@ -202,11 +230,11 @@ export const isActivatable = function (ele: HTMLElement) {

const ACTIVATABLE_ELEMENTS = ['A', 'BUTTON'];
const ACTIVATABLE_ATTRIBUTES = ['tappable', 'ion-button'];
const POINTER_TOLERANCE = 60;
const POINTER_TOLERANCE = 100;
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 new TapClick(config, app, zone);
return new TapClick(config, app, zone, gestureCtrl);
};
}
2 changes: 1 addition & 1 deletion src/module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ export class IonicModule {
{ provide: APP_INITIALIZER, useFactory: registerModeConfigs, 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: setupTapClick, deps: [ Config, App, NgZone ], multi: true },
{ provide: APP_INITIALIZER, useFactory: setupTapClick, deps: [ Config, App, NgZone, GestureController ], multi: true },

// useClass
{ provide: HAMMER_GESTURE_CONFIG, useClass: IonicGestureConfig },
Expand Down

0 comments on commit 8f8185b

Please sign in to comment.