mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2025-08-23 05:58:26 +08:00
fix(gestures): gesture controller handled by components
* fix(gestures): gesture controller is handled by components fixes #9046 * fix(gestures): adds hybrid disable scroll assistance fixes #9130 fixes #9052 fixes #7444
This commit is contained in:

committed by
Adam Bradley

parent
339857af1e
commit
32ab817181
@ -1,6 +1,22 @@
|
||||
import { forwardRef, Inject, Injectable } from '@angular/core';
|
||||
import { App } from '../components/app/app';
|
||||
import { assert } from '../util/util';
|
||||
|
||||
/** @private */
|
||||
export const GESTURE_GO_BACK_SWIPE = 'goback-swipe';
|
||||
|
||||
/** @private */
|
||||
export const GESTURE_MENU_SWIPE = 'menu-swipe';
|
||||
|
||||
/** @private */
|
||||
export const GESTURE_ITEM_SWIPE = 'item-swipe';
|
||||
|
||||
/** @private */
|
||||
export const GESTURE_REFRESHER = 'refresher';
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
export const enum GesturePriority {
|
||||
Minimun = -10000,
|
||||
VeryLow = -20,
|
||||
@ -15,23 +31,37 @@ export const enum GesturePriority {
|
||||
Refresher = Normal,
|
||||
}
|
||||
|
||||
export const enum DisableScroll {
|
||||
Never,
|
||||
DuringCapture,
|
||||
Always,
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
export interface GestureOptions {
|
||||
disable?: string[];
|
||||
disableScroll?: DisableScroll;
|
||||
name: string;
|
||||
disableScroll?: boolean;
|
||||
priority?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
export interface BlockerOptions {
|
||||
disableScroll?: boolean;
|
||||
disable?: string[];
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
export const BLOCK_ALL: BlockerOptions = {
|
||||
disable: [GESTURE_MENU_SWIPE, GESTURE_GO_BACK_SWIPE],
|
||||
disableScroll: true
|
||||
};
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
@Injectable()
|
||||
export class GestureController {
|
||||
|
||||
private id: number = 1;
|
||||
private requestedStart: { [eventId: number]: number } = {};
|
||||
private disabledGestures: { [eventName: string]: Set<number> } = {};
|
||||
@ -40,8 +70,21 @@ export class GestureController {
|
||||
|
||||
constructor(@Inject(forwardRef(() => App)) private _app: App) { }
|
||||
|
||||
create(name: string, opts: GestureOptions = {}): GestureDelegate {
|
||||
return new GestureDelegate(name, this.newID(), this, opts);
|
||||
createGesture(opts: GestureOptions): GestureDelegate {
|
||||
if (!opts.name) {
|
||||
throw new Error('name is undefined');
|
||||
}
|
||||
return new GestureDelegate(opts.name, this.newID(), this,
|
||||
opts.priority || 0,
|
||||
!!opts.disableScroll
|
||||
);
|
||||
}
|
||||
|
||||
createBlocker(opts: BlockerOptions = {}): BlockerDelegate {
|
||||
return new BlockerDelegate(this.newID(), this,
|
||||
opts.disable,
|
||||
!!opts.disableScroll
|
||||
);
|
||||
}
|
||||
|
||||
newID(): number {
|
||||
@ -127,6 +170,7 @@ export class GestureController {
|
||||
}
|
||||
|
||||
if (this.isDisabled(gestureName)) {
|
||||
console.debug('GestureController: Disabled', gestureName);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
@ -154,33 +198,18 @@ export class GestureController {
|
||||
* @private
|
||||
*/
|
||||
export class GestureDelegate {
|
||||
private disable: string[];
|
||||
private disableScroll: DisableScroll;
|
||||
public priority: number = 0;
|
||||
|
||||
constructor(
|
||||
private name: string,
|
||||
private id: number,
|
||||
private controller: GestureController,
|
||||
opts: GestureOptions
|
||||
) {
|
||||
this.disable = opts.disable || [];
|
||||
this.disableScroll = opts.disableScroll || DisableScroll.Never;
|
||||
this.priority = opts.priority || 0;
|
||||
|
||||
// Disable gestures
|
||||
for (let gestureName of this.disable) {
|
||||
controller.disableGesture(gestureName, id);
|
||||
}
|
||||
|
||||
// Disable scrolling (always)
|
||||
if (this.disableScroll === DisableScroll.Always) {
|
||||
controller.disableScroll(id);
|
||||
}
|
||||
}
|
||||
private priority: number,
|
||||
private disableScroll: boolean
|
||||
) { }
|
||||
|
||||
canStart(): boolean {
|
||||
if (!this.controller) {
|
||||
assert(false, 'delegate was destroyed');
|
||||
return false;
|
||||
}
|
||||
return this.controller.canStart(this.name);
|
||||
@ -188,6 +217,7 @@ export class GestureDelegate {
|
||||
|
||||
start(): boolean {
|
||||
if (!this.controller) {
|
||||
assert(false, 'delegate was destroyed');
|
||||
return false;
|
||||
}
|
||||
return this.controller.start(this.name, this.id, this.priority);
|
||||
@ -195,10 +225,11 @@ export class GestureDelegate {
|
||||
|
||||
capture(): boolean {
|
||||
if (!this.controller) {
|
||||
assert(false, 'delegate was destroyed');
|
||||
return false;
|
||||
}
|
||||
let captured = this.controller.capture(this.name, this.id, this.priority);
|
||||
if (captured && this.disableScroll === DisableScroll.DuringCapture) {
|
||||
if (captured && this.disableScroll) {
|
||||
this.controller.disableScroll(this.id);
|
||||
}
|
||||
return captured;
|
||||
@ -206,26 +237,70 @@ export class GestureDelegate {
|
||||
|
||||
release() {
|
||||
if (!this.controller) {
|
||||
assert(false, 'delegate was destroyed');
|
||||
return;
|
||||
}
|
||||
this.controller.release(this.id);
|
||||
if (this.disableScroll === DisableScroll.DuringCapture) {
|
||||
if (this.disableScroll) {
|
||||
this.controller.enableScroll(this.id);
|
||||
}
|
||||
}
|
||||
|
||||
destroy() {
|
||||
if (!this.controller) {
|
||||
return;
|
||||
}
|
||||
this.release();
|
||||
|
||||
for (let disabled of this.disable) {
|
||||
this.controller.enableGesture(disabled, this.id);
|
||||
}
|
||||
if (this.disableScroll === DisableScroll.Always) {
|
||||
this.controller.enableScroll(this.id);
|
||||
}
|
||||
this.controller = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
export class BlockerDelegate {
|
||||
|
||||
blocked: boolean = false;
|
||||
|
||||
constructor(
|
||||
private id: number,
|
||||
private controller: GestureController,
|
||||
private disable: string[],
|
||||
private disableScroll: boolean
|
||||
) { }
|
||||
|
||||
block() {
|
||||
if (!this.controller) {
|
||||
assert(false, 'delegate was destroyed');
|
||||
return;
|
||||
}
|
||||
if (this.disable) {
|
||||
this.disable.forEach(gesture => {
|
||||
this.controller.disableGesture(gesture, this.id);
|
||||
});
|
||||
}
|
||||
|
||||
if (this.disableScroll) {
|
||||
this.controller.disableScroll(this.id);
|
||||
}
|
||||
this.blocked = true;
|
||||
}
|
||||
|
||||
unblock() {
|
||||
if (!this.controller) {
|
||||
assert(false, 'delegate was destroyed');
|
||||
return;
|
||||
}
|
||||
if (this.disable) {
|
||||
this.disable.forEach(gesture => {
|
||||
this.controller.enableGesture(gesture, this.id);
|
||||
});
|
||||
}
|
||||
if (this.disableScroll) {
|
||||
this.controller.enableScroll(this.id);
|
||||
}
|
||||
this.blocked = false;
|
||||
}
|
||||
|
||||
destroy() {
|
||||
this.unblock();
|
||||
this.controller = null;
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { GestureController, DisableScroll } from '../gesture-controller';
|
||||
import { GestureController, GestureOptions } from '../gesture-controller';
|
||||
|
||||
describe('gesture controller', () => {
|
||||
it('should create an instance of GestureController', () => {
|
||||
@ -80,41 +80,51 @@ describe('gesture controller', () => {
|
||||
|
||||
|
||||
|
||||
it('should initialize a delegate without options', () => {
|
||||
it('should throw error if initializing without a name a gesture delegate without options', () => {
|
||||
let c = new GestureController(null);
|
||||
let g = c.create('event');
|
||||
expect(() => {
|
||||
let a = {};
|
||||
c.createGesture(<GestureOptions>a);
|
||||
}).toThrowError();
|
||||
});
|
||||
|
||||
|
||||
it('should initialize without options', () => {
|
||||
let c = new GestureController(null);
|
||||
|
||||
let g = c.createGesture({
|
||||
name: 'event',
|
||||
});
|
||||
expect(g['name']).toEqual('event');
|
||||
expect(g.priority).toEqual(0);
|
||||
expect(g['disable']).toEqual([]);
|
||||
expect(g['disableScroll']).toEqual(DisableScroll.Never);
|
||||
expect(g['priority']).toEqual(0);
|
||||
expect(g['disableScroll']).toEqual(false);
|
||||
expect(g['controller']).toEqual(c);
|
||||
expect(g['id']).toEqual(1);
|
||||
|
||||
let g2 = c.create('event2');
|
||||
let g2 = c.createGesture({ name: 'event2' });
|
||||
expect(g2['id']).toEqual(2);
|
||||
});
|
||||
|
||||
|
||||
it('should initialize a delegate with options', () => {
|
||||
let c = new GestureController(null);
|
||||
let g = c.create('swipe', {
|
||||
let g = c.createGesture({
|
||||
name: 'swipe',
|
||||
priority: -123,
|
||||
disableScroll: DisableScroll.DuringCapture,
|
||||
disable: ['event2']
|
||||
disableScroll: true,
|
||||
});
|
||||
expect(g['name']).toEqual('swipe');
|
||||
expect(g.priority).toEqual(-123);
|
||||
expect(g['disable']).toEqual(['event2']);
|
||||
expect(g['disableScroll']).toEqual(DisableScroll.DuringCapture);
|
||||
expect(g['priority']).toEqual(-123);
|
||||
expect(g['disableScroll']).toEqual(true);
|
||||
expect(g['controller']).toEqual(c);
|
||||
expect(g['id']).toEqual(1);
|
||||
});
|
||||
|
||||
it('should test if several gestures can be started', () => {
|
||||
let c = new GestureController(null);
|
||||
let g1 = c.create('swipe');
|
||||
let g2 = c.create('swipe1', {priority: 3});
|
||||
let g3 = c.create('swipe2', {priority: 4});
|
||||
let g1 = c.createGesture({ name: 'swipe' });
|
||||
let g2 = c.createGesture({name: 'swipe1', priority: 3});
|
||||
let g3 = c.createGesture({name: 'swipe2', priority: 4});
|
||||
|
||||
for (var i = 0; i < 10; i++) {
|
||||
expect(g1.start()).toEqual(true);
|
||||
@ -137,6 +147,7 @@ describe('gesture controller', () => {
|
||||
expect(g1.start()).toEqual(true);
|
||||
expect(g2.start()).toEqual(true);
|
||||
g3.destroy();
|
||||
expect(g3['controller']).toBeNull();
|
||||
|
||||
expect(c['requestedStart']).toEqual({
|
||||
1: 0,
|
||||
@ -147,11 +158,11 @@ describe('gesture controller', () => {
|
||||
|
||||
it('should test if several gestures try to capture at the same time', () => {
|
||||
let c = new GestureController(null);
|
||||
let g1 = c.create('swipe1');
|
||||
let g2 = c.create('swipe2', { priority: 2 });
|
||||
let g3 = c.create('swipe3', { priority: 3 });
|
||||
let g4 = c.create('swipe4', { priority: 4 });
|
||||
let g5 = c.create('swipe5', { priority: 5 });
|
||||
let g1 = c.createGesture({name: 'swipe1'});
|
||||
let g2 = c.createGesture({name: 'swipe2', priority: 2 });
|
||||
let g3 = c.createGesture({name: 'swipe3', priority: 3 });
|
||||
let g4 = c.createGesture({name: 'swipe4', priority: 4 });
|
||||
let g5 = c.createGesture({name: 'swipe5', priority: 5 });
|
||||
|
||||
// Low priority capture() returns false
|
||||
expect(g2.start()).toEqual(true);
|
||||
@ -196,90 +207,13 @@ describe('gesture controller', () => {
|
||||
expect(g1.capture()).toEqual(true);
|
||||
});
|
||||
|
||||
|
||||
it('should destroy correctly', () => {
|
||||
let c = new GestureController(null);
|
||||
let g = c.create('swipe', {
|
||||
priority: 123,
|
||||
disableScroll: DisableScroll.Always,
|
||||
disable: ['event2']
|
||||
});
|
||||
expect(c.isScrollDisabled()).toEqual(true);
|
||||
|
||||
// Capturing
|
||||
expect(g.capture()).toEqual(true);
|
||||
expect(c.isCaptured()).toEqual(true);
|
||||
expect(g.capture()).toEqual(false);
|
||||
expect(c.isScrollDisabled()).toEqual(true);
|
||||
|
||||
// Releasing
|
||||
g.release();
|
||||
expect(c.isCaptured()).toEqual(false);
|
||||
expect(c.isScrollDisabled()).toEqual(true);
|
||||
expect(g.capture()).toEqual(true);
|
||||
expect(c.isCaptured()).toEqual(true);
|
||||
|
||||
// Destroying
|
||||
g.destroy();
|
||||
expect(c.isCaptured()).toEqual(false);
|
||||
expect(g['controller']).toBeNull();
|
||||
|
||||
// it should return false and not crash
|
||||
expect(g.start()).toEqual(false);
|
||||
expect(g.capture()).toEqual(false);
|
||||
g.release();
|
||||
});
|
||||
|
||||
|
||||
it('should disable some events', () => {
|
||||
let c = new GestureController(null);
|
||||
|
||||
let goback = c.create('goback');
|
||||
expect(goback.canStart()).toEqual(true);
|
||||
|
||||
let g2 = c.create('goback2');
|
||||
expect(g2.canStart()).toEqual(true);
|
||||
|
||||
let g3 = c.create('swipe', {
|
||||
disable: ['range', 'goback', 'something']
|
||||
});
|
||||
|
||||
let g4 = c.create('swipe2', {
|
||||
disable: ['range']
|
||||
});
|
||||
|
||||
// it should be noop
|
||||
g3.release();
|
||||
|
||||
// goback is disabled
|
||||
expect(c.isDisabled('range')).toEqual(true);
|
||||
expect(c.isDisabled('goback')).toEqual(true);
|
||||
expect(c.isDisabled('something')).toEqual(true);
|
||||
expect(c.isDisabled('goback2')).toEqual(false);
|
||||
expect(goback.canStart()).toEqual(false);
|
||||
expect(goback.start()).toEqual(false);
|
||||
expect(goback.capture()).toEqual(false);
|
||||
expect(g3.canStart()).toEqual(true);
|
||||
|
||||
// Once g3 is destroyed, goback and something should be enabled
|
||||
g3.destroy();
|
||||
expect(c.isDisabled('range')).toEqual(true);
|
||||
expect(c.isDisabled('goback')).toEqual(false);
|
||||
expect(c.isDisabled('something')).toEqual(false);
|
||||
expect(g3.canStart()).toEqual(false);
|
||||
|
||||
// Once g4 is destroyed, range is also enabled
|
||||
g4.destroy();
|
||||
expect(c.isDisabled('range')).toEqual(false);
|
||||
expect(g4.canStart()).toEqual(false);
|
||||
});
|
||||
|
||||
it('should disable scrolling on capture', () => {
|
||||
let c = new GestureController(null);
|
||||
let g = c.create('goback', {
|
||||
disableScroll: DisableScroll.DuringCapture,
|
||||
let g = c.createGesture({
|
||||
name: 'goback',
|
||||
disableScroll: true,
|
||||
});
|
||||
let g1 = c.create('swipe');
|
||||
let g1 = c.createGesture({ name: 'swipe' });
|
||||
|
||||
g.start();
|
||||
expect(c.isScrollDisabled()).toEqual(false);
|
||||
@ -294,19 +228,116 @@ describe('gesture controller', () => {
|
||||
g.capture();
|
||||
expect(c.isScrollDisabled()).toEqual(true);
|
||||
|
||||
let g2 = c.create('swipe2', {
|
||||
disableScroll: DisableScroll.Always,
|
||||
});
|
||||
g.release();
|
||||
expect(c.isScrollDisabled()).toEqual(true);
|
||||
|
||||
g2.destroy();
|
||||
expect(c.isScrollDisabled()).toEqual(false);
|
||||
|
||||
g.capture();
|
||||
expect(c.isScrollDisabled()).toEqual(true);
|
||||
|
||||
g.destroy();
|
||||
expect(c.isScrollDisabled()).toEqual(false);
|
||||
});
|
||||
|
||||
describe('BlockerDelegate', () => {
|
||||
it('create one', () => {
|
||||
let c = new GestureController(null);
|
||||
let b = c.createBlocker({
|
||||
disableScroll: true,
|
||||
disable: ['event1', 'event2', 'event3', 'event4']
|
||||
});
|
||||
|
||||
expect(b['disable']).toEqual(['event1', 'event2', 'event3', 'event4']);
|
||||
expect(b['disableScroll']).toEqual(true);
|
||||
expect(b['controller']).toEqual(c);
|
||||
expect(b['id']).toEqual(1);
|
||||
|
||||
let b2 = c.createBlocker({
|
||||
disable: ['event2', 'event3', 'event4', 'event5']
|
||||
});
|
||||
|
||||
expect(b2['disable']).toEqual(['event2', 'event3', 'event4', 'event5']);
|
||||
expect(b2['disableScroll']).toEqual(false);
|
||||
expect(b2['controller']).toEqual(c);
|
||||
expect(b2['id']).toEqual(2);
|
||||
|
||||
|
||||
expect(c.isDisabled('event1')).toBeFalsy();
|
||||
expect(c.isDisabled('event2')).toBeFalsy();
|
||||
expect(c.isDisabled('event3')).toBeFalsy();
|
||||
expect(c.isDisabled('event4')).toBeFalsy();
|
||||
expect(c.isDisabled('event5')).toBeFalsy();
|
||||
|
||||
b.block();
|
||||
b.block();
|
||||
|
||||
expect(c.isDisabled('event1')).toBeTruthy();
|
||||
expect(c.isDisabled('event2')).toBeTruthy();
|
||||
expect(c.isDisabled('event3')).toBeTruthy();
|
||||
expect(c.isDisabled('event4')).toBeTruthy();
|
||||
expect(c.isDisabled('event5')).toBeFalsy();
|
||||
|
||||
b2.block();
|
||||
b2.block();
|
||||
b2.block();
|
||||
|
||||
expect(c.isDisabled('event1')).toBeTruthy();
|
||||
expect(c.isDisabled('event2')).toBeTruthy();
|
||||
expect(c.isDisabled('event3')).toBeTruthy();
|
||||
expect(c.isDisabled('event4')).toBeTruthy();
|
||||
expect(c.isDisabled('event5')).toBeTruthy();
|
||||
|
||||
b.unblock();
|
||||
|
||||
expect(c.isDisabled('event1')).toBeFalsy();
|
||||
expect(c.isDisabled('event2')).toBeTruthy();
|
||||
expect(c.isDisabled('event3')).toBeTruthy();
|
||||
expect(c.isDisabled('event4')).toBeTruthy();
|
||||
expect(c.isDisabled('event5')).toBeTruthy();
|
||||
|
||||
b2.destroy();
|
||||
expect(b2['controller']).toBeNull();
|
||||
|
||||
expect(c.isDisabled('event1')).toBeFalsy();
|
||||
expect(c.isDisabled('event2')).toBeFalsy();
|
||||
expect(c.isDisabled('event3')).toBeFalsy();
|
||||
expect(c.isDisabled('event4')).toBeFalsy();
|
||||
expect(c.isDisabled('event5')).toBeFalsy();
|
||||
});
|
||||
|
||||
|
||||
it('should disable some events', () => {
|
||||
let c = new GestureController(null);
|
||||
|
||||
let goback = c.createGesture({ name: 'goback' });
|
||||
expect(goback.canStart()).toEqual(true);
|
||||
|
||||
let g2 = c.createGesture({ name: 'goback2' });
|
||||
expect(g2.canStart()).toEqual(true);
|
||||
|
||||
let g3 = c.createBlocker({
|
||||
disable: ['range', 'goback', 'something']
|
||||
});
|
||||
|
||||
let g4 = c.createBlocker({
|
||||
disable: ['range']
|
||||
});
|
||||
|
||||
g3.block();
|
||||
g4.block();
|
||||
|
||||
// goback is disabled
|
||||
expect(c.isDisabled('range')).toEqual(true);
|
||||
expect(c.isDisabled('goback')).toEqual(true);
|
||||
expect(c.isDisabled('something')).toEqual(true);
|
||||
expect(c.isDisabled('goback2')).toEqual(false);
|
||||
expect(goback.canStart()).toEqual(false);
|
||||
expect(goback.start()).toEqual(false);
|
||||
expect(goback.capture()).toEqual(false);
|
||||
|
||||
// Once g3 is destroyed, goback and something should be enabled
|
||||
g3.destroy();
|
||||
expect(c.isDisabled('range')).toEqual(true);
|
||||
expect(c.isDisabled('goback')).toEqual(false);
|
||||
expect(c.isDisabled('something')).toEqual(false);
|
||||
|
||||
// Once g4 is destroyed, range is also enabled
|
||||
g4.unblock();
|
||||
expect(c.isDisabled('range')).toEqual(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
Reference in New Issue
Block a user