mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2025-08-22 21:48:42 +08:00
fix(tapclick): several improvements
- refactors code using UIEventManager - improved performance by using passive event listeners - fixes isScrolling() - click tolerance has been increased to match native behavior - click is immediately prevented if the content is scrolled.
This commit is contained in:
@ -22,6 +22,7 @@ export class App {
|
|||||||
private _title: string = '';
|
private _title: string = '';
|
||||||
private _titleSrv: Title = new Title();
|
private _titleSrv: Title = new Title();
|
||||||
private _rootNav: NavController = null;
|
private _rootNav: NavController = null;
|
||||||
|
private _canDisableScroll: boolean;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @private
|
* @private
|
||||||
@ -70,6 +71,7 @@ export class App {
|
|||||||
// listen for hardware back button events
|
// listen for hardware back button events
|
||||||
// register this back button action with a default priority
|
// register this back button action with a default priority
|
||||||
_platform.registerBackButtonAction(this.navPop.bind(this));
|
_platform.registerBackButtonAction(this.navPop.bind(this));
|
||||||
|
this._canDisableScroll = this._config.get('canDisableScroll', true);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -122,7 +124,7 @@ export class App {
|
|||||||
* scrolling is enabled. When set to `true`, scrolling is disabled.
|
* scrolling is enabled. When set to `true`, scrolling is disabled.
|
||||||
*/
|
*/
|
||||||
setScrollDisabled(disableScroll: boolean) {
|
setScrollDisabled(disableScroll: boolean) {
|
||||||
if (this._config.get('canDisableScroll', true)) {
|
if (this._canDisableScroll) {
|
||||||
this._appRoot._disableScroll(disableScroll);
|
this._appRoot._disableScroll(disableScroll);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -148,7 +150,7 @@ export class App {
|
|||||||
* @return {boolean} returns true or false
|
* @return {boolean} returns true or false
|
||||||
*/
|
*/
|
||||||
isScrolling(): boolean {
|
isScrolling(): boolean {
|
||||||
return (this._scrollTime + 48 > Date.now());
|
return ((this._scrollTime + ACTIVE_SCROLLING_TIME) > Date.now());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -275,4 +277,5 @@ export class App {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const ACTIVE_SCROLLING_TIME = 100;
|
||||||
const CLICK_BLOCK_BUFFER_IN_MILLIS = 64;
|
const CLICK_BLOCK_BUFFER_IN_MILLIS = 64;
|
||||||
|
@ -52,8 +52,7 @@ ion-content.js-scroll > .scroll-content {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.disable-scroll .ion-page .scroll-content {
|
.disable-scroll .ion-page .scroll-content {
|
||||||
overflow-y: hidden;
|
pointer-events: none;
|
||||||
overflow-x: hidden;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -190,7 +190,7 @@ export class Content extends Ion {
|
|||||||
|
|
||||||
this._zone.runOutsideAngular(() => {
|
this._zone.runOutsideAngular(() => {
|
||||||
this._scroll = new ScrollView(this._scrollEle);
|
this._scroll = new ScrollView(this._scrollEle);
|
||||||
this._scLsn = this.addScrollListener(this._app.setScrolling);
|
this._scLsn = this.addScrollListener(this._app.setScrolling.bind(this._app));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -252,6 +252,9 @@ export class Content extends Ion {
|
|||||||
return this._addListener('mousemove', handler);
|
return this._addListener('mousemove', handler);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
_addListener(type: string, handler: any): Function {
|
_addListener(type: string, handler: any): Function {
|
||||||
assert(handler, 'handler must be valid');
|
assert(handler, 'handler must be valid');
|
||||||
assert(this._scrollEle, '_scrollEle must be valid');
|
assert(this._scrollEle, '_scrollEle must be valid');
|
||||||
|
@ -21,7 +21,7 @@ export class Activator {
|
|||||||
// queue to have this element activated
|
// queue to have this element activated
|
||||||
this._queue.push(activatableEle);
|
this._queue.push(activatableEle);
|
||||||
|
|
||||||
rafFrames(2, () => {
|
rafFrames(6, () => {
|
||||||
let activatableEle: HTMLElement;
|
let activatableEle: HTMLElement;
|
||||||
for (let i = 0; i < this._queue.length; i++) {
|
for (let i = 0; i < this._queue.length; i++) {
|
||||||
activatableEle = this._queue[i];
|
activatableEle = this._queue[i];
|
||||||
@ -30,7 +30,7 @@ export class Activator {
|
|||||||
activatableEle.classList.add(this._css);
|
activatableEle.classList.add(this._css);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this._queue = [];
|
this._queue.length = 0;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -59,7 +59,7 @@ export class Activator {
|
|||||||
|
|
||||||
deactivate() {
|
deactivate() {
|
||||||
// remove the active class from all active elements
|
// remove the active class from all active elements
|
||||||
this._queue = [];
|
this._queue.length = 0;
|
||||||
|
|
||||||
rafFrames(2, () => {
|
rafFrames(2, () => {
|
||||||
for (var i = 0; i < this._active.length; i++) {
|
for (var i = 0; i < this._active.length; i++) {
|
||||||
|
@ -5,156 +5,89 @@ import { App } from '../app/app';
|
|||||||
import { Config } from '../../config/config';
|
import { Config } from '../../config/config';
|
||||||
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';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class TapClick {
|
export class TapClick {
|
||||||
private lastTouch: number = 0;
|
|
||||||
private disableClick: number = 0;
|
private disableClick: number = 0;
|
||||||
private lastActivated: number = 0;
|
|
||||||
private usePolyfill: boolean;
|
private usePolyfill: boolean;
|
||||||
private activator: Activator;
|
private activator: Activator;
|
||||||
private startCoord: any;
|
private startCoord: any;
|
||||||
private pointerMove: any;
|
private events: UIEventManager = new UIEventManager(false);
|
||||||
|
private pointerEvents: PointerEvents;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
config: Config,
|
config: Config,
|
||||||
private app: App,
|
private app: App,
|
||||||
zone: NgZone
|
zone: NgZone
|
||||||
) {
|
) {
|
||||||
if (config.get('activator') === 'ripple') {
|
let activator = config.get('activator');
|
||||||
|
if (activator === 'ripple') {
|
||||||
this.activator = new RippleActivator(app, config);
|
this.activator = new RippleActivator(app, config);
|
||||||
|
|
||||||
} else if (config.get('activator') === 'highlight') {
|
} else if (activator === 'highlight') {
|
||||||
this.activator = new Activator(app, config);
|
this.activator = new Activator(app, config);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.usePolyfill = (config.get('tapPolyfill') === true);
|
this.usePolyfill = (config.get('tapPolyfill') === true);
|
||||||
|
|
||||||
zone.runOutsideAngular(() => {
|
this.events.listen(document, 'click', this.click.bind(this), true);
|
||||||
addListener('click', this.click.bind(this), true);
|
this.pointerEvents = this.events.pointerEvents({
|
||||||
|
element: <any>document,
|
||||||
addListener('touchstart', this.touchStart.bind(this));
|
pointerDown: this.pointerStart.bind(this),
|
||||||
addListener('touchend', this.touchEnd.bind(this));
|
pointerMove: this.pointerMove.bind(this),
|
||||||
addListener('touchcancel', this.pointerCancel.bind(this));
|
pointerUp: this.pointerEnd.bind(this),
|
||||||
|
passive: true
|
||||||
addListener('mousedown', this.mouseDown.bind(this), true);
|
|
||||||
addListener('mouseup', this.mouseUp.bind(this), true);
|
|
||||||
});
|
});
|
||||||
|
this.pointerEvents.mouseWait = DISABLE_NATIVE_CLICK_AMOUNT;
|
||||||
|
}
|
||||||
|
|
||||||
this.pointerMove = (ev: UIEvent) => {
|
pointerStart(ev: any): boolean {
|
||||||
if (!this.startCoord || hasPointerMoved(POINTER_MOVE_UNTIL_CANCEL, this.startCoord, pointerCoord(ev)) ) {
|
if (this.startCoord) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
let activatableEle = getActivatableTarget(ev.target);
|
||||||
|
if (!activatableEle) {
|
||||||
|
this.startCoord = null;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
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()) {
|
||||||
this.pointerCancel(ev);
|
this.pointerCancel(ev);
|
||||||
}
|
}
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
touchStart(ev: UIEvent) {
|
pointerEnd(ev: any, type: PointerEventType) {
|
||||||
this.lastTouch = Date.now();
|
if (!this.startCoord) {
|
||||||
this.pointerStart(ev);
|
return;
|
||||||
}
|
}
|
||||||
|
if (type === PointerEventType.TOUCH && this.usePolyfill && this.app.isEnabled()) {
|
||||||
touchEnd(ev: UIEvent) {
|
this.handleTapPolyfill(ev);
|
||||||
this.lastTouch = Date.now();
|
|
||||||
|
|
||||||
if (this.usePolyfill && this.startCoord && this.app.isEnabled()) {
|
|
||||||
// only dispatch mouse click events from a touchend event
|
|
||||||
// when tapPolyfill config is true, and the startCoordand endCoord
|
|
||||||
// are not too far off from each other
|
|
||||||
let endCoord = pointerCoord(ev);
|
|
||||||
|
|
||||||
if (!hasPointerMoved(POINTER_TOLERANCE, this.startCoord, endCoord)) {
|
|
||||||
// prevent native mouse click events for XX amount of time
|
|
||||||
this.disableClick = this.lastTouch + DISABLE_NATIVE_CLICK_AMOUNT;
|
|
||||||
|
|
||||||
if (this.app.isScrolling()) {
|
|
||||||
// do not fire off a click event while the app was scrolling
|
|
||||||
console.debug('click from touch prevented by scrolling ' + Date.now());
|
|
||||||
|
|
||||||
} else {
|
|
||||||
// dispatch a mouse click event
|
|
||||||
console.debug('create click from touch ' + Date.now());
|
|
||||||
|
|
||||||
let clickEvent: any = 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);
|
|
||||||
}
|
}
|
||||||
}
|
if (this.activator) {
|
||||||
}
|
|
||||||
|
|
||||||
this.pointerEnd(ev);
|
|
||||||
}
|
|
||||||
|
|
||||||
mouseDown(ev: any) {
|
|
||||||
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: any) {
|
|
||||||
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: any) {
|
|
||||||
let activatableEle = getActivatableTarget(ev.target);
|
|
||||||
|
|
||||||
if (activatableEle) {
|
|
||||||
this.startCoord = pointerCoord(ev);
|
|
||||||
|
|
||||||
let now = Date.now();
|
|
||||||
if (this.lastActivated + 150 < now && !this.app.isScrolling()) {
|
|
||||||
this.activator && this.activator.downAction(ev, activatableEle, this.startCoord);
|
|
||||||
this.lastActivated = now;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.moveListeners(true);
|
|
||||||
|
|
||||||
} else {
|
|
||||||
this.startCoord = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pointerEnd(ev: any) {
|
|
||||||
if (this.startCoord && this.activator) {
|
|
||||||
let activatableEle = getActivatableTarget(ev.target);
|
let activatableEle = getActivatableTarget(ev.target);
|
||||||
if (activatableEle) {
|
if (activatableEle) {
|
||||||
this.activator.upAction(ev, activatableEle, this.startCoord);
|
this.activator.upAction(ev, activatableEle, this.startCoord);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
this.startCoord = null;
|
||||||
this.moveListeners(false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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.activator && this.activator.clearState();
|
this.activator && this.activator.clearState();
|
||||||
this.moveListeners(false);
|
this.pointerEvents.stop();
|
||||||
}
|
|
||||||
|
|
||||||
moveListeners(shouldAdd: boolean) {
|
|
||||||
removeListener(this.usePolyfill ? 'touchmove' : 'mousemove', this.pointerMove);
|
|
||||||
|
|
||||||
if (shouldAdd) {
|
|
||||||
addListener(this.usePolyfill ? 'touchmove' : 'mousemove', this.pointerMove);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
click(ev: any) {
|
click(ev: any) {
|
||||||
@ -174,6 +107,34 @@ export class TapClick {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleTapPolyfill(ev: any) {
|
||||||
|
// only dispatch mouse click events from a touchend event
|
||||||
|
// when tapPolyfill config is true, and the startCoordand endCoord
|
||||||
|
// are not too far off from each other
|
||||||
|
let endCoord = pointerCoord(ev);
|
||||||
|
|
||||||
|
if (hasPointerMoved(POINTER_TOLERANCE, this.startCoord, endCoord)) {
|
||||||
|
console.debug('click from touch prevented by pointer moved');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// prevent native mouse click events for XX amount of time
|
||||||
|
this.disableClick = Date.now() + DISABLE_NATIVE_CLICK_AMOUNT;
|
||||||
|
|
||||||
|
if (this.app.isScrolling()) {
|
||||||
|
// do not fire off a click event while the app was scrolling
|
||||||
|
console.debug('click from touch prevented by scrolling ' + Date.now());
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// dispatch a mouse click event
|
||||||
|
console.debug('create click from touch ' + Date.now());
|
||||||
|
|
||||||
|
let clickEvent: any = 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
isDisabledNativeClick() {
|
isDisabledNativeClick() {
|
||||||
return this.disableClick > Date.now();
|
return this.disableClick > Date.now();
|
||||||
}
|
}
|
||||||
@ -194,33 +155,23 @@ function getActivatableTarget(ele: HTMLElement) {
|
|||||||
/**
|
/**
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
export const isActivatable = function(ele: HTMLElement) {
|
export const isActivatable = function (ele: HTMLElement) {
|
||||||
if (ACTIVATABLE_ELEMENTS.test(ele.tagName)) {
|
if (ACTIVATABLE_ELEMENTS.indexOf(ele.tagName) > -1) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
let attributes = ele.attributes;
|
let attributes = ele.attributes;
|
||||||
for (let i = 0, l = attributes.length; i < l; i++) {
|
for (let i = 0, l = attributes.length; i < l; i++) {
|
||||||
if (ACTIVATABLE_ATTRIBUTES.test(attributes[i].name)) {
|
if (ACTIVATABLE_ATTRIBUTES.indexOf(attributes[i].name) > -1) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
function addListener(type: string, listener: any, useCapture?: boolean) {
|
const ACTIVATABLE_ELEMENTS = ['A', 'BUTTON'];
|
||||||
document.addEventListener(type, listener, useCapture);
|
const ACTIVATABLE_ATTRIBUTES = ['tappable', 'button'];
|
||||||
}
|
const POINTER_TOLERANCE = 60;
|
||||||
|
|
||||||
function removeListener(type: string, listener: any) {
|
|
||||||
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;
|
const DISABLE_NATIVE_CLICK_AMOUNT = 2500;
|
||||||
|
|
||||||
export function setupTapClick(config: Config, app: App, zone: NgZone) {
|
export function setupTapClick(config: Config, app: App, zone: NgZone) {
|
||||||
|
@ -110,7 +110,7 @@ export const PLATFORM_CONFIGS: {[key: string]: PlatformConfig} = {
|
|||||||
swipeBackThreshold: 40,
|
swipeBackThreshold: 40,
|
||||||
tapPolyfill: isIOSDevice,
|
tapPolyfill: isIOSDevice,
|
||||||
virtualScrollEventAssist: !(window.indexedDB),
|
virtualScrollEventAssist: !(window.indexedDB),
|
||||||
canDisableScroll: !!(window.indexedDB),
|
canDisableScroll: isIOSDevice,
|
||||||
},
|
},
|
||||||
isMatch(p: Platform) {
|
isMatch(p: Platform) {
|
||||||
return p.isPlatformMatch('ios', ['iphone', 'ipad', 'ipod'], ['windows phone']);
|
return p.isPlatformMatch('ios', ['iphone', 'ipad', 'ipod'], ['windows phone']);
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { ElementRef } from '@angular/core';
|
import { ElementRef } from '@angular/core';
|
||||||
|
import { assert } from './util';
|
||||||
|
|
||||||
export interface PointerEventsConfig {
|
export interface PointerEventsConfig {
|
||||||
element?: HTMLElement;
|
element?: HTMLElement;
|
||||||
@ -6,10 +7,29 @@ export interface PointerEventsConfig {
|
|||||||
pointerDown: (ev: any) => boolean;
|
pointerDown: (ev: any) => boolean;
|
||||||
pointerMove?: (ev: any) => void;
|
pointerMove?: (ev: any) => void;
|
||||||
pointerUp?: (ev: any) => void;
|
pointerUp?: (ev: any) => void;
|
||||||
nativeOptions?: any;
|
|
||||||
zone?: boolean;
|
zone?: boolean;
|
||||||
|
|
||||||
|
capture?: boolean;
|
||||||
|
passive?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const enum PointerEventType {
|
||||||
|
UNDEFINED,
|
||||||
|
MOUSE,
|
||||||
|
TOUCH
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test via a getter in the options object to see if the passive property is accessed
|
||||||
|
var supportsPassive = false;
|
||||||
|
try {
|
||||||
|
var opts = Object.defineProperty({}, 'passive', {
|
||||||
|
get: function() {
|
||||||
|
supportsPassive = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
window.addEventListener('test', null, opts);
|
||||||
|
} catch (e) { }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
@ -29,6 +49,7 @@ export class PointerEvents {
|
|||||||
private lastTouchEvent: number = 0;
|
private lastTouchEvent: number = 0;
|
||||||
|
|
||||||
mouseWait: number = 2 * 1000;
|
mouseWait: number = 2 * 1000;
|
||||||
|
lastEventType: PointerEventType = PointerEventType.UNDEFINED;
|
||||||
|
|
||||||
constructor(private ele: any,
|
constructor(private ele: any,
|
||||||
private pointerDown: any,
|
private pointerDown: any,
|
||||||
@ -37,6 +58,9 @@ export class PointerEvents {
|
|||||||
private zone: boolean,
|
private zone: boolean,
|
||||||
private option: any
|
private option: any
|
||||||
) {
|
) {
|
||||||
|
assert(ele, 'element can not be null');
|
||||||
|
assert(pointerDown, 'pointerDown can not be null');
|
||||||
|
|
||||||
this.bindTouchEnd = this.handleTouchEnd.bind(this);
|
this.bindTouchEnd = this.handleTouchEnd.bind(this);
|
||||||
this.bindMouseUp = this.handleMouseUp.bind(this);
|
this.bindMouseUp = this.handleMouseUp.bind(this);
|
||||||
|
|
||||||
@ -45,8 +69,12 @@ export class PointerEvents {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private handleTouchStart(ev: any) {
|
private handleTouchStart(ev: any) {
|
||||||
|
assert(this.ele, 'element can not be null');
|
||||||
|
assert(this.pointerDown, 'pointerDown can not be null');
|
||||||
|
|
||||||
this.lastTouchEvent = Date.now() + this.mouseWait;
|
this.lastTouchEvent = Date.now() + this.mouseWait;
|
||||||
if (!this.pointerDown(ev)) {
|
this.lastEventType = PointerEventType.TOUCH;
|
||||||
|
if (!this.pointerDown(ev, PointerEventType.TOUCH)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!this.rmTouchMove && this.pointerMove) {
|
if (!this.rmTouchMove && this.pointerMove) {
|
||||||
@ -61,11 +89,15 @@ export class PointerEvents {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private handleMouseDown(ev: any) {
|
private handleMouseDown(ev: any) {
|
||||||
|
assert(this.ele, 'element can not be null');
|
||||||
|
assert(this.pointerDown, 'pointerDown can not be null');
|
||||||
|
|
||||||
if (this.lastTouchEvent > Date.now()) {
|
if (this.lastTouchEvent > Date.now()) {
|
||||||
console.debug('mousedown event dropped because of previous touch');
|
console.debug('mousedown event dropped because of previous touch');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!this.pointerDown(ev)) {
|
this.lastEventType = PointerEventType.MOUSE;
|
||||||
|
if (!this.pointerDown(ev, PointerEventType.MOUSE)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!this.rmMouseMove && this.pointerMove) {
|
if (!this.rmMouseMove && this.pointerMove) {
|
||||||
@ -78,12 +110,12 @@ export class PointerEvents {
|
|||||||
|
|
||||||
private handleTouchEnd(ev: any) {
|
private handleTouchEnd(ev: any) {
|
||||||
this.stopTouch();
|
this.stopTouch();
|
||||||
this.pointerUp && this.pointerUp(ev);
|
this.pointerUp && this.pointerUp(ev, PointerEventType.TOUCH);
|
||||||
}
|
}
|
||||||
|
|
||||||
private handleMouseUp(ev: any) {
|
private handleMouseUp(ev: any) {
|
||||||
this.stopMouse();
|
this.stopMouse();
|
||||||
this.pointerUp && this.pointerUp(ev);
|
this.pointerUp && this.pointerUp(ev, PointerEventType.MOUSE);
|
||||||
}
|
}
|
||||||
|
|
||||||
private stopTouch() {
|
private stopTouch() {
|
||||||
@ -136,10 +168,6 @@ export class UIEventManager {
|
|||||||
|
|
||||||
constructor(public zoneWrapped: boolean = true) {}
|
constructor(public zoneWrapped: boolean = true) {}
|
||||||
|
|
||||||
listenRef(ref: ElementRef, eventName: string, callback: any, option?: any): Function {
|
|
||||||
return this.listen(ref.nativeElement, eventName, callback, option);
|
|
||||||
}
|
|
||||||
|
|
||||||
pointerEvents(config: PointerEventsConfig): PointerEvents {
|
pointerEvents(config: PointerEventsConfig): PointerEvents {
|
||||||
let element = config.element;
|
let element = config.element;
|
||||||
if (!element) {
|
if (!element) {
|
||||||
@ -151,19 +179,39 @@ export class UIEventManager {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let zone = config.zone || this.zoneWrapped;
|
let zone = config.zone || this.zoneWrapped;
|
||||||
let options = config.nativeOptions || false;
|
let opts;
|
||||||
|
if (supportsPassive) {
|
||||||
|
opts = {};
|
||||||
|
if (config.passive === true) {
|
||||||
|
opts['passive'] = true;
|
||||||
|
}
|
||||||
|
if (config.capture === true) {
|
||||||
|
opts['capture'] = true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (config.passive === true) {
|
||||||
|
console.debug('passive event listeners are not supported by this browser');
|
||||||
|
}
|
||||||
|
if (config.capture === true) {
|
||||||
|
opts = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let submanager = new PointerEvents(
|
let pointerEvents = new PointerEvents(
|
||||||
element,
|
element,
|
||||||
config.pointerDown,
|
config.pointerDown,
|
||||||
config.pointerMove,
|
config.pointerMove,
|
||||||
config.pointerUp,
|
config.pointerUp,
|
||||||
zone,
|
zone,
|
||||||
options);
|
opts);
|
||||||
|
|
||||||
let removeFunc = () => submanager.destroy();
|
let removeFunc = () => pointerEvents.destroy();
|
||||||
this.events.push(removeFunc);
|
this.events.push(removeFunc);
|
||||||
return submanager;
|
return pointerEvents;
|
||||||
|
}
|
||||||
|
|
||||||
|
listenRef(ref: ElementRef, eventName: string, callback: any, option?: any): Function {
|
||||||
|
return this.listen(ref.nativeElement, eventName, callback, option);
|
||||||
}
|
}
|
||||||
|
|
||||||
listen(element: any, eventName: string, callback: any, option: any = false): Function {
|
listen(element: any, eventName: string, callback: any, option: any = false): Function {
|
||||||
@ -187,9 +235,10 @@ function listenEvent(ele: any, eventName: string, zoneWrapped: boolean, option:
|
|||||||
let rawEvent = (!zoneWrapped && '__zone_symbol__addEventListener' in ele);
|
let rawEvent = (!zoneWrapped && '__zone_symbol__addEventListener' in ele);
|
||||||
if (rawEvent) {
|
if (rawEvent) {
|
||||||
ele.__zone_symbol__addEventListener(eventName, callback, option);
|
ele.__zone_symbol__addEventListener(eventName, callback, option);
|
||||||
return () => ele.__zone_symbol__removeEventListener(eventName, callback);
|
assert('__zone_symbol__removeEventListener' in ele, 'native removeEventListener does not exist');
|
||||||
|
return () => ele.__zone_symbol__removeEventListener(eventName, callback, option);
|
||||||
} else {
|
} else {
|
||||||
ele.addEventListener(eventName, callback, option);
|
ele.addEventListener(eventName, callback, option);
|
||||||
return () => ele.removeEventListener(eventName, callback);
|
return () => ele.removeEventListener(eventName, callback, option);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user