mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2025-08-22 05:21:52 +08:00
feat(gesture): Introducing new gesture controller
This commit is contained in:
0
src/components/app/test/gesture-collision/e2e.ts
Normal file
0
src/components/app/test/gesture-collision/e2e.ts
Normal file
70
src/components/app/test/gesture-collision/index.ts
Normal file
70
src/components/app/test/gesture-collision/index.ts
Normal file
@ -0,0 +1,70 @@
|
||||
import { Component, ViewChild } from '@angular/core';
|
||||
import { ionicBootstrap, MenuController, NavController, AlertController, Nav, Refresher } from '../../../../../src';
|
||||
|
||||
|
||||
@Component({
|
||||
templateUrl: 'page1.html'
|
||||
})
|
||||
class Page1 {
|
||||
constructor(private nav: NavController, private alertCtrl: AlertController) {}
|
||||
|
||||
presentAlert() {
|
||||
let alert = this.alertCtrl.create({
|
||||
title: 'New Friend!',
|
||||
message: 'Your friend, Obi wan Kenobi, just accepted your friend request!',
|
||||
cssClass: 'my-alert',
|
||||
buttons: ['Ok']
|
||||
});
|
||||
alert.present();
|
||||
}
|
||||
|
||||
goToPage1() {
|
||||
this.nav.push(Page1);
|
||||
}
|
||||
|
||||
doRefresh(refresher: Refresher) {
|
||||
setTimeout(() => {
|
||||
refresher.complete();
|
||||
}, 1000);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Component({
|
||||
templateUrl: 'main.html'
|
||||
})
|
||||
class E2EPage {
|
||||
rootPage: any;
|
||||
changeDetectionCount: number = 0;
|
||||
pages: Array<{title: string, component: any}>;
|
||||
@ViewChild(Nav) nav: Nav;
|
||||
|
||||
constructor(private menu: MenuController) {
|
||||
this.rootPage = Page1;
|
||||
|
||||
this.pages = [
|
||||
{ title: 'Page 1', component: Page1 },
|
||||
{ title: 'Page 2', component: Page1 },
|
||||
{ title: 'Page 3', component: Page1 },
|
||||
];
|
||||
}
|
||||
|
||||
openPage(page: any) {
|
||||
// Reset the content nav to have just this page
|
||||
// we wouldn't want the back button to show in this scenario
|
||||
this.nav.setRoot(page.component).then(() => {
|
||||
// wait for the root page to be completely loaded
|
||||
// then close the menu
|
||||
this.menu.close();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Component({
|
||||
template: '<ion-nav [root]="rootPage"></ion-nav>'
|
||||
})
|
||||
class E2EApp {
|
||||
rootPage = E2EPage;
|
||||
}
|
||||
|
||||
ionicBootstrap(E2EApp);
|
159
src/components/app/test/gesture-collision/main.html
Normal file
159
src/components/app/test/gesture-collision/main.html
Normal file
@ -0,0 +1,159 @@
|
||||
<ion-menu [content]="content" side="left" persistent="true">
|
||||
|
||||
<ion-header>
|
||||
<ion-toolbar secondary>
|
||||
<ion-title>Left Menu</ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
|
||||
<ion-content>
|
||||
|
||||
<ion-list>
|
||||
|
||||
<ion-item-sliding *ngFor="let p of pages">
|
||||
<button ion-item (click)="openPage(p)">
|
||||
{{p.title}}
|
||||
</button>
|
||||
<ion-item-options side="left">
|
||||
<button>Test</button>
|
||||
</ion-item-options>
|
||||
<ion-item-options>
|
||||
<button>Test</button>
|
||||
</ion-item-options>
|
||||
</ion-item-sliding>
|
||||
|
||||
<button ion-item menuClose="left" class="e2eCloseLeftMenu" detail-none>
|
||||
Close Menu
|
||||
</button>
|
||||
|
||||
<button ion-item menuClose="left" detail-none>
|
||||
Close Menu
|
||||
</button>
|
||||
|
||||
<button ion-item menuClose="left" detail-none>
|
||||
Close Menu
|
||||
</button>
|
||||
|
||||
<button ion-item menuClose="left" detail-none>
|
||||
Close Menu
|
||||
</button>
|
||||
|
||||
<button ion-item menuClose="left" detail-none>
|
||||
Close Menu
|
||||
</button>
|
||||
|
||||
<button ion-item menuClose="left" detail-none>
|
||||
Close Menu
|
||||
</button>
|
||||
|
||||
<button ion-item menuClose="left" detail-none>
|
||||
Close Menu
|
||||
</button>
|
||||
|
||||
<button ion-item menuClose="left" detail-none>
|
||||
Close Menu
|
||||
</button>
|
||||
|
||||
<button ion-item menuClose="left" detail-none>
|
||||
Close Menu
|
||||
</button>
|
||||
|
||||
<button ion-item menuClose="left" detail-none>
|
||||
Close Menu
|
||||
</button>
|
||||
|
||||
<button ion-item menuClose="left" detail-none>
|
||||
Close Menu
|
||||
</button>
|
||||
|
||||
<button ion-item menuClose="left" detail-none>
|
||||
Close Menu
|
||||
</button>
|
||||
|
||||
</ion-list>
|
||||
</ion-content>
|
||||
|
||||
<ion-footer>
|
||||
<ion-toolbar secondary>
|
||||
<ion-title>Footer</ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-footer>
|
||||
|
||||
</ion-menu>
|
||||
|
||||
|
||||
<ion-menu [content]="content" side="right">
|
||||
|
||||
<ion-header>
|
||||
<ion-toolbar danger>
|
||||
<ion-title>Right Menu</ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
|
||||
<ion-content>
|
||||
|
||||
<ion-list>
|
||||
|
||||
<button ion-item *ngFor="let p of pages" (click)="openPage(p)">
|
||||
{{p.title}}
|
||||
</button>
|
||||
|
||||
<button ion-item menuClose="right" class="e2eCloseRightMenu" detail-none>
|
||||
Close Menu
|
||||
</button>
|
||||
|
||||
<button ion-item menuClose="right" detail-none>
|
||||
Close Menu
|
||||
</button>
|
||||
|
||||
<button ion-item menuClose="right" detail-none>
|
||||
Close Menu
|
||||
</button>
|
||||
|
||||
<button ion-item menuClose="right" detail-none>
|
||||
Close Menu
|
||||
</button>
|
||||
|
||||
<button ion-item menuClose="right" detail-none>
|
||||
Close Menu
|
||||
</button>
|
||||
|
||||
<button ion-item menuClose="right" detail-none>
|
||||
Close Menu
|
||||
</button>
|
||||
|
||||
<button ion-item menuClose="right" detail-none>
|
||||
Close Menu
|
||||
</button>
|
||||
|
||||
<button ion-item menuClose="right" detail-none>
|
||||
Close Menu
|
||||
</button>
|
||||
|
||||
<button ion-item menuClose="right" detail-none>
|
||||
Close Menu
|
||||
</button>
|
||||
|
||||
<button ion-item menuClose="right" detail-none>
|
||||
Close Menu
|
||||
</button>
|
||||
|
||||
<button ion-item menuClose="right" detail-none>
|
||||
Close Menu
|
||||
</button>
|
||||
|
||||
<button ion-item menuClose="right" detail-none>
|
||||
Close Menu
|
||||
</button>
|
||||
|
||||
<button ion-item menuClose="right" detail-none>
|
||||
Close Menu
|
||||
</button>
|
||||
|
||||
</ion-list>
|
||||
|
||||
</ion-content>
|
||||
|
||||
</ion-menu>
|
||||
|
||||
<ion-nav [root]="rootPage" #content swipeBackEnabled="true"></ion-nav>
|
84
src/components/app/test/gesture-collision/page1.html
Normal file
84
src/components/app/test/gesture-collision/page1.html
Normal file
@ -0,0 +1,84 @@
|
||||
<ion-header>
|
||||
|
||||
<ion-navbar>
|
||||
|
||||
<button menuToggle="left">
|
||||
<ion-icon name="menu"></ion-icon>
|
||||
</button>
|
||||
|
||||
<ion-title>
|
||||
Menu
|
||||
</ion-title>
|
||||
|
||||
<button menuToggle="right" right secondary>
|
||||
<ion-icon name="menu"></ion-icon>
|
||||
</button>
|
||||
|
||||
</ion-navbar>
|
||||
|
||||
</ion-header>
|
||||
|
||||
|
||||
<ion-content>
|
||||
<ion-refresher (ionRefresh)="doRefresh($event)">
|
||||
|
||||
<ion-refresher-content
|
||||
pullingText="Pull to refresh..."
|
||||
refreshingSpinner="bubbles"
|
||||
refreshingText="Refreshing...">
|
||||
</ion-refresher-content>
|
||||
|
||||
</ion-refresher>
|
||||
|
||||
<h3>Page 1</h3>
|
||||
|
||||
<ion-list padding>
|
||||
<ion-item-sliding>
|
||||
<button ion-item class="e2eContentToggleLeftMenu" menuToggle="left">Toggle Left Menu</button>
|
||||
|
||||
<ion-item-options side="left">
|
||||
<button>Test</button>
|
||||
</ion-item-options>
|
||||
<ion-item-options>
|
||||
<button>Test</button>
|
||||
</ion-item-options>
|
||||
</ion-item-sliding>
|
||||
|
||||
</ion-list>
|
||||
<ion-list>
|
||||
|
||||
<ion-item-sliding>
|
||||
<button ion-item class="e2eContentToggleRightMenu" menuToggle="right">Toggle Right Menu</button>
|
||||
|
||||
<ion-item-options side="left">
|
||||
<button>Test</button>
|
||||
</ion-item-options>
|
||||
<ion-item-options>
|
||||
<button>Test</button>
|
||||
</ion-item-options>
|
||||
</ion-item-sliding>
|
||||
|
||||
<button ion-item (click)="goToPage1()">Push same page</button>
|
||||
|
||||
<ion-item>
|
||||
<ion-label>RANGE</ion-label>
|
||||
<ion-range></ion-range>
|
||||
</ion-item>
|
||||
|
||||
<ion-item-sliding>
|
||||
<ion-item>
|
||||
<ion-label>SLIDING ITEM + RANGE</ion-label>
|
||||
<ion-range></ion-range>
|
||||
</ion-item>
|
||||
<ion-item-options side="left">
|
||||
<button>Test</button>
|
||||
</ion-item-options>
|
||||
<ion-item-options>
|
||||
<button>Test</button>
|
||||
</ion-item-options>
|
||||
</ion-item-sliding>
|
||||
</ion-list>
|
||||
|
||||
<f></f><f></f><f></f><f></f><f></f><f></f><f></f><f></f><f></f><f></f><f></f><f></f><f></f><f></f><f></f><f></f><f></f><f></f><f></f><f></f>
|
||||
|
||||
</ion-content>
|
@ -25,10 +25,9 @@ export class ItemReorderGesture {
|
||||
|
||||
private events: UIEventManager = new UIEventManager(false);
|
||||
|
||||
constructor(public list: ItemReorder) {
|
||||
let element = this.list.getNativeElement();
|
||||
constructor(public reorderList: ItemReorder) {
|
||||
this.events.pointerEvents({
|
||||
element: element,
|
||||
element: this.reorderList.getNativeElement(),
|
||||
pointerDown: this.onDragStart.bind(this),
|
||||
pointerMove: this.onDragMove.bind(this),
|
||||
pointerUp: this.onDragEnd.bind(this)
|
||||
@ -46,7 +45,7 @@ export class ItemReorderGesture {
|
||||
console.error('ion-reorder does not contain $ionComponent');
|
||||
return false;
|
||||
}
|
||||
this.list.reorderPrepare();
|
||||
this.reorderList.reorderPrepare();
|
||||
|
||||
let item = reorderMark.getReorderNode();
|
||||
if (!item) {
|
||||
@ -62,13 +61,13 @@ export class ItemReorderGesture {
|
||||
this.lastToIndex = indexForItem(item);
|
||||
|
||||
this.windowHeight = window.innerHeight - AUTO_SCROLL_MARGIN;
|
||||
this.lastScrollPosition = this.list.scrollContent(0);
|
||||
this.lastScrollPosition = this.reorderList.scrollContent(0);
|
||||
|
||||
this.offset = pointerCoord(ev);
|
||||
this.offset.y += this.lastScrollPosition;
|
||||
|
||||
item.classList.add(ITEM_REORDER_ACTIVE);
|
||||
this.list.reorderStart();
|
||||
this.reorderList.reorderStart();
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -96,7 +95,7 @@ export class ItemReorderGesture {
|
||||
this.lastToIndex = toIndex;
|
||||
this.lastYcoord = posY;
|
||||
this.emptyZone = false;
|
||||
this.list.reorderMove(fromIndex, toIndex, this.selectedItemHeight);
|
||||
this.reorderList.reorderMove(fromIndex, toIndex, this.selectedItemHeight);
|
||||
}
|
||||
} else {
|
||||
this.emptyZone = true;
|
||||
@ -127,7 +126,7 @@ export class ItemReorderGesture {
|
||||
} else {
|
||||
reorderInactive();
|
||||
}
|
||||
this.list.reorderEmit(fromIndex, toIndex);
|
||||
this.reorderList.reorderEmit(fromIndex, toIndex);
|
||||
}
|
||||
|
||||
private itemForCoord(coord: Coordinates): HTMLElement {
|
||||
@ -136,9 +135,9 @@ export class ItemReorderGesture {
|
||||
|
||||
private scroll(posY: number): number {
|
||||
if (posY < AUTO_SCROLL_MARGIN) {
|
||||
this.lastScrollPosition = this.list.scrollContent(-SCROLL_JUMP);
|
||||
this.lastScrollPosition = this.reorderList.scrollContent(-SCROLL_JUMP);
|
||||
} else if (posY > this.windowHeight) {
|
||||
this.lastScrollPosition = this.list.scrollContent(SCROLL_JUMP);
|
||||
this.lastScrollPosition = this.reorderList.scrollContent(SCROLL_JUMP);
|
||||
}
|
||||
return this.lastScrollPosition;
|
||||
}
|
||||
@ -150,7 +149,7 @@ export class ItemReorderGesture {
|
||||
this.onDragEnd();
|
||||
this.events.unlistenAll();
|
||||
this.events = null;
|
||||
this.list = null;
|
||||
this.reorderList = null;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3,6 +3,7 @@ import { List } from '../list/list';
|
||||
|
||||
import { closest, Coordinates, pointerCoord } from '../../util/dom';
|
||||
import { PointerEvents, UIEventManager } from '../../util/ui-event-manager';
|
||||
import { GestureDelegate, GestureOptions, GesturePriority } from '../../gestures/gesture-controller';
|
||||
|
||||
const DRAG_THRESHOLD = 10;
|
||||
const MAX_ATTACK_ANGLE = 20;
|
||||
@ -16,8 +17,13 @@ export class ItemSlidingGesture {
|
||||
private pointerEvents: PointerEvents;
|
||||
private firstCoordX: number;
|
||||
private firstTimestamp: number;
|
||||
private gesture: GestureDelegate;
|
||||
|
||||
constructor(public list: List) {
|
||||
this.gesture = list.gestureCtrl.create('item-sliding', {
|
||||
priority: GesturePriority.Interactive,
|
||||
});
|
||||
|
||||
this.pointerEvents = this.events.pointerEvents({
|
||||
element: list.getNativeElement(),
|
||||
pointerDown: this.pointerStart.bind(this),
|
||||
@ -36,11 +42,18 @@ export class ItemSlidingGesture {
|
||||
this.closeOpened();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Close open container if it is not the selected one.
|
||||
if (container !== this.openContainer && this.closeOpened()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Try to start gesture
|
||||
if (!this.gesture.start()) {
|
||||
this.gesture.release();
|
||||
return false;
|
||||
}
|
||||
|
||||
let coord = pointerCoord(ev);
|
||||
this.preSelectedContainer = container;
|
||||
this.panDetector.start(coord);
|
||||
@ -56,16 +69,19 @@ export class ItemSlidingGesture {
|
||||
}
|
||||
let coord = pointerCoord(ev);
|
||||
if (this.panDetector.detect(coord)) {
|
||||
if (!this.panDetector.isPanX()) {
|
||||
this.pointerEvents.stop();
|
||||
this.closeOpened();
|
||||
} else {
|
||||
this.onDragStart(ev, coord);
|
||||
if (this.panDetector.isPanX() && this.gesture.capture()) {
|
||||
this.onDragStart(ev, coord);
|
||||
return;
|
||||
}
|
||||
|
||||
// Detection/capturing was not successful, aborting!
|
||||
this.closeOpened();
|
||||
this.pointerEvents.stop();
|
||||
}
|
||||
}
|
||||
|
||||
private pointerEnd(ev: any) {
|
||||
this.gesture.release();
|
||||
if (this.selectedContainer) {
|
||||
this.onDragEnd(ev);
|
||||
} else {
|
||||
@ -103,18 +119,21 @@ export class ItemSlidingGesture {
|
||||
}
|
||||
|
||||
closeOpened(): boolean {
|
||||
if (!this.openContainer) {
|
||||
return false;
|
||||
}
|
||||
this.openContainer.close();
|
||||
this.openContainer = null;
|
||||
this.selectedContainer = null;
|
||||
return true;
|
||||
this.gesture.release();
|
||||
|
||||
if (this.openContainer) {
|
||||
this.openContainer.close();
|
||||
this.openContainer = null;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
unlisten() {
|
||||
this.closeOpened();
|
||||
destroy() {
|
||||
this.gesture.destroy();
|
||||
this.events.unlistenAll();
|
||||
this.closeOpened();
|
||||
|
||||
this.list = null;
|
||||
this.preSelectedContainer = null;
|
||||
|
@ -4,6 +4,7 @@ import { Content } from '../content/content';
|
||||
import { Ion } from '../ion';
|
||||
import { isTrueProperty } from '../../util/util';
|
||||
import { ItemSlidingGesture } from '../item/item-sliding-gesture';
|
||||
import { GestureController } from '../../gestures/gesture-controller';
|
||||
|
||||
/**
|
||||
* The List is a widely used interface element in almost any mobile app,
|
||||
@ -29,7 +30,10 @@ export class List extends Ion {
|
||||
private _containsSlidingItems: boolean = false;
|
||||
private _slidingGesture: ItemSlidingGesture;
|
||||
|
||||
constructor(elementRef: ElementRef, private _rendered: Renderer) {
|
||||
constructor(
|
||||
elementRef: ElementRef,
|
||||
private _rendered: Renderer,
|
||||
public gestureCtrl: GestureController) {
|
||||
super(elementRef);
|
||||
}
|
||||
|
||||
@ -78,11 +82,11 @@ export class List extends Ion {
|
||||
this._updateSlidingState();
|
||||
}
|
||||
|
||||
|
||||
|
||||
private _updateSlidingState() {
|
||||
let shouldSlide = this._enableSliding && this._containsSlidingItems;
|
||||
if (!shouldSlide) {
|
||||
this._slidingGesture && this._slidingGesture.unlisten();
|
||||
this._slidingGesture && this._slidingGesture.destroy();
|
||||
this._slidingGesture = null;
|
||||
|
||||
} else if (!this._slidingGesture) {
|
||||
|
@ -1,27 +1,39 @@
|
||||
import {Menu} from './menu';
|
||||
import {SlideEdgeGesture} from '../../gestures/slide-edge-gesture';
|
||||
import {SlideData} from '../../gestures/slide-gesture';
|
||||
import {assign} from '../../util/util';
|
||||
import { Menu } from './menu';
|
||||
import { SlideEdgeGesture } from '../../gestures/slide-edge-gesture';
|
||||
import { SlideData } from '../../gestures/slide-gesture';
|
||||
import { assign } from '../../util/util';
|
||||
import { GestureDelegate, GesturePriority } from '../../gestures/gesture-controller';
|
||||
|
||||
|
||||
/**
|
||||
* Gesture attached to the content which the menu is assigned to
|
||||
*/
|
||||
export class MenuContentGesture extends SlideEdgeGesture {
|
||||
gesture: GestureDelegate;
|
||||
|
||||
constructor(public menu: Menu, contentEle: HTMLElement, options: any = {}) {
|
||||
|
||||
super(contentEle, assign({
|
||||
direction: 'x',
|
||||
edge: menu.side,
|
||||
threshold: 0,
|
||||
maxEdgeStart: menu.maxEdgeStart || 75
|
||||
}, options));
|
||||
|
||||
this.gesture = menu.gestureCtrl.create('menu-swipe', {
|
||||
priority: GesturePriority.NavigationOptional,
|
||||
});
|
||||
}
|
||||
|
||||
canStart(ev: any) {
|
||||
let menu = this.menu;
|
||||
canStart(ev: any): boolean {
|
||||
if (this.shouldStart(ev)) {
|
||||
return this.gesture.capture();
|
||||
}
|
||||
this.gesture.release();
|
||||
return false;
|
||||
}
|
||||
|
||||
shouldStart(ev: any): boolean {
|
||||
let menu = this.menu;
|
||||
if (!menu.enabled || !menu.swipeEnabled) {
|
||||
console.debug('menu can not start, isEnabled:', menu.enabled, 'isSwipeEnabled:', menu.swipeEnabled, 'side:', menu.side);
|
||||
return false;
|
||||
@ -33,40 +45,23 @@ export class MenuContentGesture extends SlideEdgeGesture {
|
||||
return false;
|
||||
}
|
||||
|
||||
console.debug('menu canStart,', menu.side, 'isOpen', menu.isOpen, 'angle', ev.angle, 'distance', ev.distance);
|
||||
|
||||
if (menu.side === 'right') {
|
||||
// right side
|
||||
if (menu.isOpen) {
|
||||
// right side, opened
|
||||
return true;
|
||||
|
||||
} else {
|
||||
// right side, closed
|
||||
if ((ev.angle > 140 && ev.angle <= 180) || (ev.angle > -140 && ev.angle <= -180)) {
|
||||
return super.canStart(ev);
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
// left side
|
||||
if (menu.isOpen) {
|
||||
// left side, opened
|
||||
return true;
|
||||
|
||||
} else {
|
||||
// left side, closed
|
||||
if (ev.angle > -40 && ev.angle < 40) {
|
||||
return super.canStart(ev);
|
||||
}
|
||||
}
|
||||
console.debug('menu shouldCapture,', menu.side, 'isOpen', menu.isOpen, 'angle', ev.angle, 'distance', ev.distance);
|
||||
|
||||
if (menu.isOpen) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// didn't pass the test, don't open this menu
|
||||
if (menu.side === 'right') {
|
||||
if ((ev.angle > 140 && ev.angle <= 180) || (ev.angle > -140 && ev.angle <= -180)) {
|
||||
return super.canStart(ev);
|
||||
}
|
||||
} else {
|
||||
if (ev.angle > -40 && ev.angle < 40) {
|
||||
return super.canStart(ev);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Set CSS, then wait one frame for it to apply before sliding starts
|
||||
onSlideBeforeStart(slide: SlideData, ev: any) {
|
||||
console.debug('menu gesture, onSlideBeforeStart', this.menu.side);
|
||||
@ -83,16 +78,18 @@ export class MenuContentGesture extends SlideEdgeGesture {
|
||||
}
|
||||
|
||||
onSlideEnd(slide: SlideData, ev: any) {
|
||||
this.gesture.release();
|
||||
|
||||
let z = (this.menu.side === 'right' ? slide.min : slide.max);
|
||||
let currentStepValue = (slide.distance / z);
|
||||
|
||||
z = Math.abs(z * 0.5);
|
||||
let shouldCompleteRight = (ev.velocityX >= 0)
|
||||
&& (ev.velocityX > 0.2 || slide.delta > z);
|
||||
|
||||
|
||||
let shouldCompleteLeft = (ev.velocityX <= 0)
|
||||
&& (ev.velocityX < -0.2 || slide.delta < -z);
|
||||
|
||||
|
||||
console.debug(
|
||||
'menu gesture, onSlide', this.menu.side,
|
||||
'distance', slide.distance,
|
||||
@ -103,7 +100,6 @@ export class MenuContentGesture extends SlideEdgeGesture {
|
||||
'shouldCompleteLeft', shouldCompleteLeft,
|
||||
'shouldCompleteRight', shouldCompleteRight,
|
||||
'currentStepValue', currentStepValue);
|
||||
|
||||
this.menu.swipeEnd(shouldCompleteLeft, shouldCompleteRight, currentStepValue);
|
||||
}
|
||||
|
||||
@ -132,6 +128,16 @@ export class MenuContentGesture extends SlideEdgeGesture {
|
||||
max: this.menu.width()
|
||||
};
|
||||
}
|
||||
|
||||
unlisten() {
|
||||
this.gesture.release();
|
||||
super.unlisten();
|
||||
}
|
||||
|
||||
destroy() {
|
||||
this.gesture.destroy();
|
||||
super.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -143,5 +149,6 @@ export class MenuTargetGesture extends MenuContentGesture {
|
||||
super(menu, menuEle, {
|
||||
maxEdgeStart: 0
|
||||
});
|
||||
this.gesture.priority++;
|
||||
}
|
||||
}
|
||||
|
@ -9,6 +9,7 @@ import { MenuContentGesture, MenuTargetGesture } from './menu-gestures';
|
||||
import { MenuController } from './menu-controller';
|
||||
import { MenuType } from './menu-types';
|
||||
import { Platform } from '../../platform/platform';
|
||||
import { GestureController } from '../../gestures/gesture-controller';
|
||||
|
||||
|
||||
/**
|
||||
@ -302,7 +303,8 @@ export class Menu extends Ion {
|
||||
private _platform: Platform,
|
||||
private _renderer: Renderer,
|
||||
private _keyboard: Keyboard,
|
||||
private _zone: NgZone
|
||||
private _zone: NgZone,
|
||||
public gestureCtrl: GestureController
|
||||
) {
|
||||
super(_elementRef);
|
||||
}
|
||||
|
@ -148,6 +148,6 @@
|
||||
|
||||
</ion-menu>
|
||||
|
||||
<ion-nav [root]="rootPage" #content></ion-nav>
|
||||
<ion-nav [root]="rootPage" #content swipeBackEnabled="true"></ion-nav>
|
||||
|
||||
<div [hidden]="isChangeDetecting()"></div>
|
||||
|
@ -35,9 +35,19 @@
|
||||
|
||||
<h3>Page 1</h3>
|
||||
|
||||
<p>
|
||||
<button class="e2eContentToggleLeftMenu" menuToggle="left">Toggle Left Menu</button>
|
||||
</p>
|
||||
<ion-list>
|
||||
<ion-item-sliding>
|
||||
<button ion-item class="e2eContentToggleLeftMenu" menuToggle="left">Toggle Left Menu</button>
|
||||
|
||||
<ion-item-options side="left">
|
||||
<button>Test</button>
|
||||
</ion-item-options>
|
||||
<ion-item-options>
|
||||
<button>Test</button>
|
||||
</ion-item-options>
|
||||
</ion-item-sliding>
|
||||
</ion-list>
|
||||
|
||||
|
||||
<p>
|
||||
<button class="e2eContentToggleRightMenu" menuToggle="right">Toggle Right Menu</button>
|
||||
|
@ -3,10 +3,10 @@ import { ComponentResolver, ElementRef, EventEmitter, NgZone, provide, Reflectiv
|
||||
import { addSelector } from '../../config/bootstrap';
|
||||
import { App } from '../app/app';
|
||||
import { Config } from '../../config/config';
|
||||
import { GestureController } from '../../gestures/gesture-controller';
|
||||
import { Ion } from '../ion';
|
||||
import { isBlank, pascalCaseToDashCase } from '../../util/util';
|
||||
import { Keyboard } from '../../util/keyboard';
|
||||
import { MenuController } from '../menu/menu-controller';
|
||||
import { NavOptions } from './nav-interfaces';
|
||||
import { NavParams } from './nav-params';
|
||||
import { SwipeBackGesture } from './swipe-back';
|
||||
@ -247,7 +247,7 @@ export class NavController extends Ion {
|
||||
protected _zone: NgZone,
|
||||
protected _renderer: Renderer,
|
||||
protected _compiler: ComponentResolver,
|
||||
protected _menuCtrl: MenuController
|
||||
private _gestureCtrl: GestureController
|
||||
) {
|
||||
super(elementRef);
|
||||
|
||||
@ -1379,7 +1379,7 @@ export class NavController extends Ion {
|
||||
edge: 'left',
|
||||
threshold: this._sbThreshold
|
||||
};
|
||||
this._sbGesture = new SwipeBackGesture(this.getNativeElement(), opts, this, this._menuCtrl);
|
||||
this._sbGesture = new SwipeBackGesture(this.getNativeElement(), opts, this, this._gestureCtrl);
|
||||
}
|
||||
|
||||
if (this.canSwipeBack()) {
|
||||
|
@ -2,8 +2,8 @@ import { ComponentResolver, Directive, ElementRef, forwardRef, Inject, NgZone, O
|
||||
|
||||
import { App } from '../app/app';
|
||||
import { Config } from '../../config/config';
|
||||
import { GestureController } from '../../gestures/gesture-controller';
|
||||
import { Keyboard } from '../../util/keyboard';
|
||||
import { MenuController } from '../menu/menu-controller';
|
||||
import { NavController } from '../nav/nav-controller';
|
||||
|
||||
/**
|
||||
@ -21,10 +21,10 @@ export class NavPortal extends NavController {
|
||||
zone: NgZone,
|
||||
renderer: Renderer,
|
||||
compiler: ComponentResolver,
|
||||
menuCtrl: MenuController,
|
||||
gestureCtrl: GestureController,
|
||||
viewPort: ViewContainerRef
|
||||
) {
|
||||
super(null, app, config, keyboard, elementRef, zone, renderer, compiler, menuCtrl);
|
||||
super(null, app, config, keyboard, elementRef, zone, renderer, compiler, gestureCtrl);
|
||||
this.isPortal = true;
|
||||
this.setViewport(viewPort);
|
||||
app.setPortal(this);
|
||||
|
@ -3,8 +3,8 @@ import { AfterViewInit, Component, ComponentResolver, ElementRef, Input, Optiona
|
||||
import { App } from '../app/app';
|
||||
import { Config } from '../../config/config';
|
||||
import { Keyboard } from '../../util/keyboard';
|
||||
import { GestureController } from '../../gestures/gesture-controller';
|
||||
import { isTrueProperty } from '../../util/util';
|
||||
import { MenuController } from '../menu/menu-controller';
|
||||
import { NavController } from './nav-controller';
|
||||
import { ViewController } from './view-controller';
|
||||
|
||||
@ -128,9 +128,9 @@ export class Nav extends NavController implements AfterViewInit {
|
||||
zone: NgZone,
|
||||
renderer: Renderer,
|
||||
compiler: ComponentResolver,
|
||||
menuCtrl: MenuController
|
||||
gestureCtrl: GestureController
|
||||
) {
|
||||
super(parent, app, config, keyboard, elementRef, zone, renderer, compiler, menuCtrl);
|
||||
super(parent, app, config, keyboard, elementRef, zone, renderer, compiler, gestureCtrl);
|
||||
|
||||
if (viewCtrl) {
|
||||
// an ion-nav can also act as an ion-page within a parent ion-nav
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { assign } from '../../util/util';
|
||||
import { GestureController, GestureDelegate, GesturePriority } from '../../gestures/gesture-controller';
|
||||
import { MenuController } from '../menu/menu-controller';
|
||||
import { NavController } from './nav-controller';
|
||||
import { SlideData } from '../../gestures/slide-gesture';
|
||||
@ -7,39 +8,43 @@ import { SlideEdgeGesture } from '../../gestures/slide-edge-gesture';
|
||||
|
||||
export class SwipeBackGesture extends SlideEdgeGesture {
|
||||
|
||||
private gesture: GestureDelegate;
|
||||
|
||||
constructor(
|
||||
element: HTMLElement,
|
||||
options: any,
|
||||
private _nav: NavController,
|
||||
private _menuCtrl: MenuController
|
||||
gestureCtlr: GestureController
|
||||
) {
|
||||
super(element, assign({
|
||||
direction: 'x',
|
||||
maxEdgeStart: 75
|
||||
}, options));
|
||||
|
||||
this.gesture = gestureCtlr.create('goback-swipe', {
|
||||
priority: GesturePriority.Navigation,
|
||||
});
|
||||
}
|
||||
|
||||
canStart(ev: any) {
|
||||
canStart(ev: any): boolean {
|
||||
this.gesture.release();
|
||||
|
||||
// the gesture swipe angle must be mainly horizontal and the
|
||||
// gesture distance would be relatively short for a swipe back
|
||||
// and swipe back must be possible on this nav controller
|
||||
if (ev.angle > -40 &&
|
||||
ev.angle < 40 &&
|
||||
ev.distance < 50 &&
|
||||
this._nav.canSwipeBack()) {
|
||||
// passed the tests, now see if the super says it's cool or not
|
||||
return super.canStart(ev);
|
||||
}
|
||||
|
||||
// nerp, not today
|
||||
return false;
|
||||
return (
|
||||
ev.angle > -40 &&
|
||||
ev.angle < 40 &&
|
||||
ev.distance < 50 &&
|
||||
this._nav.canSwipeBack() &&
|
||||
super.canStart(ev) &&
|
||||
this.gesture.capture()
|
||||
);
|
||||
}
|
||||
|
||||
onSlideBeforeStart(slideData: SlideData, ev: any) {
|
||||
console.debug('swipeBack, onSlideBeforeStart', ev.srcEvent.type);
|
||||
this._nav.swipeBackStart();
|
||||
|
||||
this._menuCtrl.tempDisable(true);
|
||||
}
|
||||
|
||||
onSlide(slide: SlideData) {
|
||||
@ -57,7 +62,17 @@ export class SwipeBackGesture extends SlideEdgeGesture {
|
||||
|
||||
this._nav.swipeBackEnd(shouldComplete, currentStepValue);
|
||||
|
||||
this._menuCtrl.tempDisable(false);
|
||||
this.gesture.release();
|
||||
}
|
||||
|
||||
unlisten() {
|
||||
this.gesture.release();
|
||||
super.unlisten();
|
||||
}
|
||||
|
||||
destroy() {
|
||||
this.gesture.destroy();
|
||||
super.destroy();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -431,18 +431,12 @@ export class Range implements AfterViewInit, ControlValueAccessor, OnDestroy {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
|
||||
if (this._start !== null && this._active !== null) {
|
||||
// only use pointer move if it's a valid pointer
|
||||
// and we already have start coordinates
|
||||
// update the ratio for the active knob
|
||||
this.updateKnob(pointerCoord(ev), this._rect);
|
||||
|
||||
// update the ratio for the active knob
|
||||
this.updateKnob(pointerCoord(ev), this._rect);
|
||||
|
||||
// update the active knob's position
|
||||
this._active.position();
|
||||
this._pressed = this._active.pressed = true;
|
||||
|
||||
}
|
||||
// update the active knob's position
|
||||
this._active.position();
|
||||
this._pressed = this._active.pressed = true;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -2,6 +2,7 @@ import { Directive, EventEmitter, Host, Input, Output, NgZone } from '@angular/c
|
||||
|
||||
import { Content } from '../content/content';
|
||||
import { CSS, pointerCoord } from '../../util/dom';
|
||||
import { GestureController, GestureDelegate, GesturePriority } from '../../gestures/gesture-controller';
|
||||
import { isTrueProperty } from '../../util/util';
|
||||
import { PointerEvents, UIEventManager } from '../../util/ui-event-manager';
|
||||
|
||||
@ -98,6 +99,7 @@ export class Refresher {
|
||||
private _didStart: boolean;
|
||||
private _lastCheck: number = 0;
|
||||
private _isEnabled: boolean = true;
|
||||
private _gesture: GestureDelegate;
|
||||
private _events: UIEventManager = new UIEventManager(false);
|
||||
private _pointerEvents: PointerEvents;
|
||||
private _top: string = '';
|
||||
@ -196,8 +198,11 @@ export class Refresher {
|
||||
@Output() ionStart: EventEmitter<Refresher> = new EventEmitter<Refresher>();
|
||||
|
||||
|
||||
constructor(@Host() private _content: Content, private _zone: NgZone) {
|
||||
constructor(@Host() private _content: Content, private _zone: NgZone, gestureCtrl: GestureController) {
|
||||
_content.addCssClass('has-refresher');
|
||||
this._gesture = gestureCtrl.create('refresher', {
|
||||
priority: GesturePriority.Interactive,
|
||||
});
|
||||
}
|
||||
|
||||
private _onStart(ev: TouchEvent): any {
|
||||
@ -216,6 +221,10 @@ export class Refresher {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!this._gesture.canStart()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let coord = pointerCoord(ev);
|
||||
console.debug('Pull-to-refresh, onStart', ev.type, 'y:', coord.y);
|
||||
|
||||
@ -228,7 +237,7 @@ export class Refresher {
|
||||
|
||||
this.startY = this.currentY = coord.y;
|
||||
this.progress = 0;
|
||||
this.state = STATE_PULLING;
|
||||
this.state = STATE_INACTIVE;
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -242,6 +251,10 @@ export class Refresher {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (!this._gesture.canStart()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// do nothing if it's actively refreshing
|
||||
// or it's in the process of closing
|
||||
// or this was never a startY
|
||||
@ -484,6 +497,7 @@ export class Refresher {
|
||||
* @private
|
||||
*/
|
||||
ngOnDestroy() {
|
||||
this._gesture.destroy();
|
||||
this._setListeners(false);
|
||||
}
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
import {Refresher, Content, Config, Ion} from '../../../../src';
|
||||
import { Refresher, Content, Config, GestureController, Ion } from '../../../../src';
|
||||
|
||||
export function run() {
|
||||
|
||||
@ -218,17 +218,19 @@ describe('Refresher', () => {
|
||||
let refresher: Refresher;
|
||||
let content: Content;
|
||||
let contentElementRef;
|
||||
let gestureController: GestureController;
|
||||
let zone = {
|
||||
run: function(cb) {cb()},
|
||||
runOutsideAngular: function(cb) {cb()}
|
||||
run: function (cb) { cb(); },
|
||||
runOutsideAngular: function (cb) { cb(); }
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
contentElementRef = mockElementRef();
|
||||
content = new Content(contentElementRef, config, null, null, null);
|
||||
gestureController = new GestureController();
|
||||
content = new Content(contentElementRef, config, null, null, zone, null, null);
|
||||
content._scrollEle = document.createElement('scroll-content');
|
||||
|
||||
refresher = new Refresher(content, zone, mockElementRef());
|
||||
refresher = new Refresher(content, zone, gestureController);
|
||||
});
|
||||
|
||||
function touchEv(y: number) {
|
||||
|
@ -14,16 +14,16 @@ class E2EApp {
|
||||
constructor() {
|
||||
this.slides = [
|
||||
{
|
||||
name: "Slide 1",
|
||||
class: "yellow"
|
||||
name: 'Slide 1',
|
||||
class: 'yellow'
|
||||
},
|
||||
{
|
||||
name: "Slide 2",
|
||||
class: "red"
|
||||
name: 'Slide 2',
|
||||
class: 'red'
|
||||
},
|
||||
{
|
||||
name: "Slide 3",
|
||||
class: "blue"
|
||||
name: 'Slide 3',
|
||||
class: 'blue'
|
||||
}
|
||||
];
|
||||
|
||||
|
@ -2,9 +2,9 @@ import { ChangeDetectorRef, Component, ComponentResolver, ElementRef, EventEmitt
|
||||
|
||||
import { App } from '../app/app';
|
||||
import { Config } from '../../config/config';
|
||||
import { GestureController } from '../../gestures/gesture-controller';
|
||||
import { isTrueProperty} from '../../util/util';
|
||||
import { Keyboard} from '../../util/keyboard';
|
||||
import { MenuController } from '../menu/menu-controller';
|
||||
import { NavController } from '../nav/nav-controller';
|
||||
import { NavOptions} from '../nav/nav-interfaces';
|
||||
import { TabButton} from './tab-button';
|
||||
@ -229,10 +229,10 @@ export class Tab extends NavController {
|
||||
renderer: Renderer,
|
||||
compiler: ComponentResolver,
|
||||
private _cd: ChangeDetectorRef,
|
||||
menuCtrl: MenuController
|
||||
gestureCtrl: GestureController
|
||||
) {
|
||||
// A Tab is a NavController for its child pages
|
||||
super(parent, app, config, keyboard, elementRef, zone, renderer, compiler, menuCtrl);
|
||||
super(parent, app, config, keyboard, elementRef, zone, renderer, compiler, gestureCtrl);
|
||||
|
||||
parent.add(this);
|
||||
|
||||
|
@ -10,6 +10,7 @@ import { closest, nativeTimeout } from '../util/dom';
|
||||
import { Events } from '../util/events';
|
||||
import { FeatureDetect } from '../util/feature-detect';
|
||||
import { Form } from '../util/form';
|
||||
import { GestureController } from '../gestures/gesture-controller';
|
||||
import { IONIC_DIRECTIVES } from './directives';
|
||||
import { isPresent } from '../util/util';
|
||||
import { Keyboard } from '../util/keyboard';
|
||||
@ -77,6 +78,7 @@ export function ionicProviders(customProviders?: Array<any>, config?: any): any[
|
||||
TapClick,
|
||||
ToastController,
|
||||
Translate,
|
||||
GestureController,
|
||||
];
|
||||
|
||||
if (isPresent(customProviders)) {
|
||||
|
@ -1,5 +1,5 @@
|
||||
import {Gesture} from './gesture';
|
||||
import {defaults} from '../util';
|
||||
import { Gesture } from './gesture';
|
||||
import { defaults } from '../util';
|
||||
|
||||
/**
|
||||
* @private
|
||||
|
215
src/gestures/gesture-controller.ts
Normal file
215
src/gestures/gesture-controller.ts
Normal file
@ -0,0 +1,215 @@
|
||||
|
||||
import { Injectable } from '@angular/core';
|
||||
|
||||
import { App } from '../components/app/app';
|
||||
|
||||
export const enum GesturePriority {
|
||||
Minimun = -10000,
|
||||
NavigationOptional = -20,
|
||||
Navigation = -10,
|
||||
Normal = 0,
|
||||
Interactive = 10,
|
||||
Input = 20,
|
||||
}
|
||||
|
||||
export const enum DisableScroll {
|
||||
Never,
|
||||
DuringCapture,
|
||||
Always,
|
||||
}
|
||||
|
||||
export interface GestureOptions {
|
||||
disable?: string[];
|
||||
disableScroll?: DisableScroll;
|
||||
priority?: number;
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class GestureController {
|
||||
private id: number = 1;
|
||||
private requestedStart: { [eventId: number]: number } = {};
|
||||
private disabledGestures: { [eventName: string]: Set<number> } = {};
|
||||
private disabledScroll: Set<number> = new Set<number>();
|
||||
private appRoot: App;
|
||||
private capturedID: number = null;
|
||||
|
||||
create(name: string, opts: GestureOptions = {}): GestureDelegate {
|
||||
let id = this.id; this.id++;
|
||||
return new GestureDelegate(name, id, this, opts);
|
||||
}
|
||||
|
||||
start(gestureName: string, id: number, priority: number): boolean {
|
||||
if (!this.canStart(gestureName)) {
|
||||
delete this.requestedStart[id];
|
||||
return false;
|
||||
}
|
||||
|
||||
this.requestedStart[id] = priority;
|
||||
return true;
|
||||
}
|
||||
|
||||
capture(gestureName: string, id: number, priority: number): boolean {
|
||||
if (!this.start(gestureName, id, priority)) {
|
||||
return false;
|
||||
}
|
||||
let requestedStart = this.requestedStart;
|
||||
let maxPriority = GesturePriority.Minimun;
|
||||
for (let gestureID in requestedStart) {
|
||||
maxPriority = Math.max(maxPriority, requestedStart[gestureID]);
|
||||
}
|
||||
|
||||
if (maxPriority === priority) {
|
||||
this.capturedID = id;
|
||||
this.requestedStart = {};
|
||||
return true;
|
||||
}
|
||||
delete requestedStart[id];
|
||||
console.debug(`${gestureName} can not start because it is has lower priority`);
|
||||
return false;
|
||||
}
|
||||
|
||||
release(id: number) {
|
||||
delete this.requestedStart[id];
|
||||
if (this.capturedID && id === this.capturedID) {
|
||||
this.capturedID = null;
|
||||
}
|
||||
}
|
||||
|
||||
disableGesture(gestureName: string, id: number) {
|
||||
let set = this.disabledGestures[gestureName];
|
||||
if (!set) {
|
||||
set = new Set<number>();
|
||||
this.disabledGestures[gestureName] = set;
|
||||
}
|
||||
set.add(id);
|
||||
}
|
||||
|
||||
enableGesture(gestureName: string, id: number) {
|
||||
let set = this.disabledGestures[gestureName];
|
||||
if (set) {
|
||||
set.delete(id);
|
||||
}
|
||||
}
|
||||
|
||||
disableScroll(id: number) {
|
||||
let isEnabled = !this.isScrollDisabled();
|
||||
this.disabledScroll.add(id);
|
||||
if (isEnabled && this.isScrollDisabled()) {
|
||||
// this.appRoot.disableScroll = true;
|
||||
}
|
||||
}
|
||||
|
||||
enableScroll(id: number) {
|
||||
let isDisabled = this.isScrollDisabled();
|
||||
this.disabledScroll.delete(id);
|
||||
if (isDisabled && !this.isScrollDisabled()) {
|
||||
// this.appRoot.disableScroll = false;
|
||||
}
|
||||
}
|
||||
|
||||
canStart(gestureName: string): boolean {
|
||||
if (this.capturedID) {
|
||||
// a gesture already captured
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this.isDisabled(gestureName)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
isCaptured(): boolean {
|
||||
return !!this.capturedID;
|
||||
}
|
||||
|
||||
isScrollDisabled(): boolean {
|
||||
return this.disabledScroll.size > 0;
|
||||
}
|
||||
|
||||
isDisabled(gestureName: string): boolean {
|
||||
let disabled = this.disabledGestures[gestureName];
|
||||
if (disabled && disabled.size > 0) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
canStart(): boolean {
|
||||
if (!this.controller) {
|
||||
return false;
|
||||
}
|
||||
return this.controller.canStart(this.name);
|
||||
}
|
||||
|
||||
start(): boolean {
|
||||
if (!this.controller) {
|
||||
return false;
|
||||
}
|
||||
return this.controller.start(this.name, this.id, this.priority);
|
||||
}
|
||||
|
||||
capture(): boolean {
|
||||
if (!this.controller) {
|
||||
return false;
|
||||
}
|
||||
let captured = this.controller.capture(this.name, this.id, this.priority);
|
||||
if (captured && this.disableScroll === DisableScroll.DuringCapture) {
|
||||
this.controller.disableScroll(this.id);
|
||||
}
|
||||
return captured;
|
||||
}
|
||||
|
||||
release() {
|
||||
if (!this.controller) {
|
||||
return;
|
||||
}
|
||||
this.controller.release(this.id);
|
||||
if (this.disableScroll === DisableScroll.DuringCapture) {
|
||||
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;
|
||||
}
|
||||
}
|
314
src/gestures/test/gesture-controller.spec.ts
Normal file
314
src/gestures/test/gesture-controller.spec.ts
Normal file
@ -0,0 +1,314 @@
|
||||
import { GestureController, DisableScroll } from '../../../src';
|
||||
|
||||
export function run() {
|
||||
|
||||
it('should create an instance of GestureController', () => {
|
||||
let c = new GestureController();
|
||||
expect(c.isCaptured()).toEqual(false);
|
||||
expect(c.isScrollDisabled()).toEqual(false);
|
||||
});
|
||||
|
||||
it('should test scrolling enable/disable stack', () => {
|
||||
let c = new GestureController();
|
||||
c.enableScroll(1);
|
||||
expect(c.isScrollDisabled()).toEqual(false);
|
||||
|
||||
c.disableScroll(1);
|
||||
expect(c.isScrollDisabled()).toEqual(true);
|
||||
c.disableScroll(1);
|
||||
c.disableScroll(1);
|
||||
expect(c.isScrollDisabled()).toEqual(true);
|
||||
|
||||
c.enableScroll(1);
|
||||
expect(c.isScrollDisabled()).toEqual(false);
|
||||
|
||||
for (var i = 0; i < 100; i++) {
|
||||
for (var j = 0; j < 100; j++) {
|
||||
c.disableScroll(j);
|
||||
}
|
||||
}
|
||||
|
||||
for (var i = 0; i < 100; i++) {
|
||||
expect(c.isScrollDisabled()).toEqual(true);
|
||||
c.enableScroll(50 - i);
|
||||
c.enableScroll(i);
|
||||
}
|
||||
expect(c.isScrollDisabled()).toEqual(false);
|
||||
});
|
||||
|
||||
it('should test gesture enable/disable stack', () => {
|
||||
let c = new GestureController();
|
||||
c.enableGesture('swipe', 1);
|
||||
expect(c.isDisabled('swipe')).toEqual(false);
|
||||
|
||||
c.disableGesture('swipe', 1);
|
||||
expect(c.isDisabled('swipe')).toEqual(true);
|
||||
c.disableGesture('swipe', 1);
|
||||
c.disableGesture('swipe', 1);
|
||||
expect(c.isDisabled('swipe')).toEqual(true);
|
||||
|
||||
c.enableGesture('swipe', 1);
|
||||
expect(c.isDisabled('swipe')).toEqual(false);
|
||||
|
||||
// Disabling gestures multiple times
|
||||
for (var gestureName = 0; gestureName < 10; gestureName++) {
|
||||
for (var i = 0; i < 50; i++) {
|
||||
for (var j = 0; j < 50; j++) {
|
||||
c.disableGesture(gestureName.toString(), j);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (var gestureName = 0; gestureName < 10; gestureName++) {
|
||||
for (var i = 0; i < 49; i++) {
|
||||
c.enableGesture(gestureName.toString(), i);
|
||||
}
|
||||
expect(c.isDisabled(gestureName.toString())).toEqual(true);
|
||||
c.enableGesture(gestureName.toString(), 49);
|
||||
expect(c.isDisabled(gestureName.toString())).toEqual(false);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
it('should test if canStart', () => {
|
||||
let c = new GestureController();
|
||||
expect(c.canStart('event')).toEqual(true);
|
||||
expect(c.canStart('event1')).toEqual(true);
|
||||
expect(c.canStart('event')).toEqual(true);
|
||||
expect(c['requestedStart']).toEqual({});
|
||||
expect(c.isCaptured()).toEqual(false);
|
||||
});
|
||||
|
||||
|
||||
|
||||
it('should initialize a delegate without options', () => {
|
||||
let c = new GestureController();
|
||||
let g = c.create('event');
|
||||
expect(g['name']).toEqual('event');
|
||||
expect(g.priority).toEqual(0);
|
||||
expect(g['disable']).toEqual([]);
|
||||
expect(g['disableScroll']).toEqual(DisableScroll.Never);
|
||||
expect(g['controller']).toEqual(c);
|
||||
expect(g['id']).toEqual(1);
|
||||
|
||||
let g2 = c.create('event2');
|
||||
expect(g2['id']).toEqual(2);
|
||||
});
|
||||
|
||||
|
||||
it('should initialize a delegate with options', () => {
|
||||
let c = new GestureController();
|
||||
let g = c.create('swipe', {
|
||||
priority: -123,
|
||||
disableScroll: DisableScroll.DuringCapture,
|
||||
disable: ['event2']
|
||||
});
|
||||
expect(g['name']).toEqual('swipe');
|
||||
expect(g.priority).toEqual(-123);
|
||||
expect(g['disable']).toEqual(['event2']);
|
||||
expect(g['disableScroll']).toEqual(DisableScroll.DuringCapture);
|
||||
expect(g['controller']).toEqual(c);
|
||||
expect(g['id']).toEqual(1);
|
||||
});
|
||||
|
||||
it('should test if several gestures can be started', () => {
|
||||
let c = new GestureController();
|
||||
let g1 = c.create('swipe');
|
||||
let g2 = c.create('swipe1', {priority: 3});
|
||||
let g3 = c.create('swipe2', {priority: 4});
|
||||
|
||||
for (var i = 0; i < 10; i++) {
|
||||
expect(g1.start()).toEqual(true);
|
||||
expect(g2.start()).toEqual(true);
|
||||
expect(g3.start()).toEqual(true);
|
||||
}
|
||||
expect(c['requestedStart']).toEqual({
|
||||
1: 0,
|
||||
2: 3,
|
||||
3: 4
|
||||
});
|
||||
|
||||
g1.release();
|
||||
g1.release();
|
||||
|
||||
expect(c['requestedStart']).toEqual({
|
||||
2: 3,
|
||||
3: 4
|
||||
});
|
||||
expect(g1.start()).toEqual(true);
|
||||
expect(g2.start()).toEqual(true);
|
||||
g3.destroy();
|
||||
|
||||
expect(c['requestedStart']).toEqual({
|
||||
1: 0,
|
||||
2: 3,
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
it('should test if several gestures try to capture at the same time', () => {
|
||||
let c = new GestureController();
|
||||
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 });
|
||||
|
||||
// Low priority capture() returns false
|
||||
expect(g2.start()).toEqual(true);
|
||||
expect(g3.start()).toEqual(true);
|
||||
expect(g1.capture()).toEqual(false);
|
||||
expect(c['requestedStart']).toEqual({
|
||||
2: 2,
|
||||
3: 3
|
||||
});
|
||||
|
||||
// Low priority start() + capture() returns false
|
||||
expect(g2.capture()).toEqual(false);
|
||||
expect(c['requestedStart']).toEqual({
|
||||
3: 3
|
||||
});
|
||||
|
||||
// Higher priority capture() return true
|
||||
expect(g4.capture()).toEqual(true);
|
||||
expect(c.isScrollDisabled()).toEqual(false);
|
||||
expect(c.isCaptured()).toEqual(true);
|
||||
expect(c['requestedStart']).toEqual({});
|
||||
|
||||
// Higher priority can not capture because it is already capture
|
||||
expect(g5.capture()).toEqual(false);
|
||||
expect(g5.canStart()).toEqual(false);
|
||||
expect(g5.start()).toEqual(false);
|
||||
expect(c['requestedStart']).toEqual({});
|
||||
|
||||
// Only captured gesture can release
|
||||
g1.release();
|
||||
g2.release();
|
||||
g3.release();
|
||||
g5.release();
|
||||
expect(c.isCaptured()).toEqual(true);
|
||||
|
||||
// G4 releases
|
||||
g4.release();
|
||||
expect(c.isCaptured()).toEqual(false);
|
||||
|
||||
// Once it was release, any gesture can capture
|
||||
expect(g1.start()).toEqual(true);
|
||||
expect(g1.capture()).toEqual(true);
|
||||
});
|
||||
|
||||
|
||||
it('should destroy correctly', () => {
|
||||
let c = new GestureController();
|
||||
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();
|
||||
|
||||
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();
|
||||
let g = c.create('goback', {
|
||||
disableScroll: DisableScroll.DuringCapture,
|
||||
});
|
||||
let g1 = c.create('swipe');
|
||||
|
||||
g.start();
|
||||
expect(c.isScrollDisabled()).toEqual(false);
|
||||
|
||||
g1.capture();
|
||||
g.capture();
|
||||
expect(c.isScrollDisabled()).toEqual(false);
|
||||
|
||||
g1.release();
|
||||
expect(c.isScrollDisabled()).toEqual(false);
|
||||
|
||||
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);
|
||||
});
|
||||
|
||||
}
|
@ -12,6 +12,7 @@ export * from './gestures/drag-gesture';
|
||||
export * from './gestures/gesture';
|
||||
export * from './gestures/slide-edge-gesture';
|
||||
export * from './gestures/slide-gesture';
|
||||
export * from './gestures/gesture-controller';
|
||||
|
||||
export * from './platform/platform';
|
||||
export * from './platform/storage';
|
||||
|
Reference in New Issue
Block a user