mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2026-03-13 10:22:08 +08:00
fix(angular): add swipe-to-go-back gesture
This commit is contained in:
committed by
Manu MA
parent
bfbbeca389
commit
108691dc44
@@ -5,7 +5,6 @@ export { RadioValueAccessor } from './control-value-accessors/radio-value-access
|
||||
export { SelectValueAccessor } from './control-value-accessors/select-value-accessor';
|
||||
export { TextValueAccessor } from './control-value-accessors/text-value-accessor';
|
||||
|
||||
export { RouterDirection } from './navigation/router-direction';
|
||||
export { IonBackButton } from './navigation/ion-back-button';
|
||||
export { NavDelegate } from './navigation/nav-delegate';
|
||||
export { TabDelegate } from './navigation/tab-delegate';
|
||||
|
||||
@@ -1,11 +1,16 @@
|
||||
import { Directive, ElementRef, HostListener, Input, Optional } from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
import { NavController, NavIntent } from '../../providers/nav-controller';
|
||||
|
||||
export type RouterDirection = 'forward' | 'back' | 'root' | 'auto';
|
||||
|
||||
@Directive({
|
||||
selector: 'ion-anchor,ion-button,ion-item'
|
||||
selector: '[routerDirection],ion-anchor,ion-button,ion-item'
|
||||
})
|
||||
export class HrefDelegate {
|
||||
|
||||
@Input() routerDirection: RouterDirection = 'forward';
|
||||
|
||||
@Input()
|
||||
set routerLink(_: any) {
|
||||
this.elementRef.nativeElement.button = true;
|
||||
@@ -21,6 +26,7 @@ export class HrefDelegate {
|
||||
|
||||
constructor(
|
||||
@Optional() private router: Router,
|
||||
private navCtrl: NavController,
|
||||
private elementRef: ElementRef
|
||||
) {}
|
||||
|
||||
@@ -29,7 +35,17 @@ export class HrefDelegate {
|
||||
const url = this.href;
|
||||
if (this.router && url != null && url[0] !== '#' && url.indexOf('://') === -1) {
|
||||
ev.preventDefault();
|
||||
this.navCtrl.setIntent(textToIntent(this.routerDirection));
|
||||
this.router.navigateByUrl(url);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function textToIntent(direction: RouterDirection) {
|
||||
switch (direction) {
|
||||
case 'forward': return NavIntent.Forward;
|
||||
case 'back': return NavIntent.Back;
|
||||
case 'root': return NavIntent.Root;
|
||||
default: return NavIntent.Auto;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Attribute, ChangeDetectorRef, ComponentFactoryResolver, ComponentRef, Directive, ElementRef, EventEmitter, Injector, Input, OnDestroy, OnInit, Optional, Output, ViewContainerRef } from '@angular/core';
|
||||
import { Attribute, ChangeDetectorRef, ComponentFactoryResolver, ComponentRef, Directive, ElementRef, EventEmitter, Injector, Input, NgZone, OnDestroy, OnInit, Optional, Output, ViewContainerRef } from '@angular/core';
|
||||
import { ActivatedRoute, ChildrenOutletContexts, OutletContext, PRIMARY_OUTLET, Router } from '@angular/router';
|
||||
import { RouteView, StackController } from './router-controller';
|
||||
import { RouteView, StackController } from './stack-controller';
|
||||
import { NavController } from '../../providers/nav-controller';
|
||||
import { bindLifecycleEvents } from '../../providers/angular-delegate';
|
||||
|
||||
@@ -14,32 +14,47 @@ export class IonRouterOutlet implements OnDestroy, OnInit {
|
||||
private activatedView: RouteView | null = null;
|
||||
|
||||
private _activatedRoute: ActivatedRoute | null = null;
|
||||
private _swipeGesture?: boolean;
|
||||
private name: string;
|
||||
private stackCtrl: StackController;
|
||||
private nativeEl: HTMLIonRouterOutletElement;
|
||||
private hasStack = false;
|
||||
|
||||
@Output('activate') activateEvents = new EventEmitter<any>();
|
||||
@Output('deactivate') deactivateEvents = new EventEmitter<any>();
|
||||
|
||||
@Input()
|
||||
set animated(animated: boolean) {
|
||||
(this.elementRef.nativeElement as HTMLIonRouterOutletElement).animated = animated;
|
||||
this.nativeEl.animated = animated;
|
||||
}
|
||||
|
||||
@Input()
|
||||
set swipeGesture(swipe: boolean) {
|
||||
this._swipeGesture = swipe;
|
||||
this.nativeEl.swipeHandler = (swipe && this.hasStack) ? {
|
||||
canStart: () => this.stackCtrl.canGoBack(1),
|
||||
onStart: () => this.stackCtrl.startBackTransition(),
|
||||
onEnd: shouldContinue => this.stackCtrl.endBackTransition(shouldContinue)
|
||||
} : undefined;
|
||||
}
|
||||
|
||||
constructor(
|
||||
private parentContexts: ChildrenOutletContexts,
|
||||
private location: ViewContainerRef,
|
||||
private resolver: ComponentFactoryResolver,
|
||||
private elementRef: ElementRef,
|
||||
@Attribute('name') name: string,
|
||||
@Optional() @Attribute('stack') stack: any,
|
||||
private changeDetector: ChangeDetectorRef,
|
||||
private navCtrl: NavController,
|
||||
router: Router
|
||||
elementRef: ElementRef,
|
||||
router: Router,
|
||||
zone: NgZone
|
||||
) {
|
||||
this.nativeEl = elementRef.nativeElement;
|
||||
this.name = name || PRIMARY_OUTLET;
|
||||
parentContexts.onChildOutletCreated(this.name, this as any);
|
||||
const hasStack = stack !== 'false' && stack !== false;
|
||||
this.stackCtrl = new StackController(hasStack, elementRef.nativeElement, router, this.navCtrl);
|
||||
this.hasStack = stack !== 'false' && stack !== false;
|
||||
this.stackCtrl = new StackController(this.hasStack, this.nativeEl, router, this.navCtrl, zone);
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
@@ -60,9 +75,16 @@ export class IonRouterOutlet implements OnDestroy, OnInit {
|
||||
this.activateWith(context.route, context.resolver || null);
|
||||
}
|
||||
}
|
||||
this.nativeEl.componentOnReady().then(() => {
|
||||
if (this._swipeGesture === undefined) {
|
||||
this.swipeGesture = this.nativeEl.mode === 'ios';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
get isActivated(): boolean { return !!this.activated; }
|
||||
get isActivated(): boolean {
|
||||
return !!this.activated;
|
||||
}
|
||||
|
||||
get component(): object {
|
||||
if (!this.activated) {
|
||||
@@ -151,9 +173,8 @@ export class IonRouterOutlet implements OnDestroy, OnInit {
|
||||
this.activatedView = enteringView;
|
||||
this.stackCtrl.setActive(enteringView, direction, animated).then(() => {
|
||||
this.activateEvents.emit(cmpRef.instance);
|
||||
emitEvent(this.elementRef.nativeElement);
|
||||
emitEvent(this.nativeEl);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
canGoBack(deep = 1) {
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
import { Directive, HostListener, Input } from '@angular/core';
|
||||
import { NavController, NavIntent } from '../../providers/nav-controller';
|
||||
|
||||
@Directive({
|
||||
selector: '[routerDirection]',
|
||||
})
|
||||
export class RouterDirection {
|
||||
|
||||
@Input() routerDirection: string;
|
||||
|
||||
constructor(
|
||||
private navCtrl: NavController,
|
||||
) {}
|
||||
|
||||
@HostListener('click')
|
||||
onClick() {
|
||||
this.navCtrl.setIntent(textToIntent(this.routerDirection));
|
||||
}
|
||||
}
|
||||
|
||||
function textToIntent(direction: string) {
|
||||
switch (direction) {
|
||||
case 'forward': return NavIntent.Forward;
|
||||
case 'back': return NavIntent.Back;
|
||||
case 'root': return NavIntent.Root;
|
||||
default: return NavIntent.Auto;
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ComponentRef } from '@angular/core';
|
||||
import { ComponentRef, NgZone } from '@angular/core';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
|
||||
import { NavController } from '../../providers/nav-controller';
|
||||
@@ -8,12 +8,15 @@ export class StackController {
|
||||
|
||||
private viewsSnapshot: RouteView[] = [];
|
||||
private views: RouteView[] = [];
|
||||
private runningTransition?: Promise<boolean>;
|
||||
private skipTransition = false;
|
||||
|
||||
constructor(
|
||||
private stack: boolean,
|
||||
private containerEl: HTMLIonRouterOutletElement,
|
||||
private router: Router,
|
||||
private navCtrl: NavController,
|
||||
private zone: NgZone,
|
||||
) {}
|
||||
|
||||
createView(enteringRef: ComponentRef<any>, route: ActivatedRoute): RouteView {
|
||||
@@ -22,7 +25,6 @@ export class StackController {
|
||||
element: (enteringRef && enteringRef.location && enteringRef.location.nativeElement) as HTMLElement,
|
||||
url: this.getUrl(route),
|
||||
fullpath: document.location!.pathname,
|
||||
deactivatedId: -1
|
||||
};
|
||||
}
|
||||
|
||||
@@ -31,22 +33,43 @@ export class StackController {
|
||||
return this.views.find(vw => vw.url === activatedUrlKey);
|
||||
}
|
||||
|
||||
async setActive(enteringView: RouteView, direction: number, animated: boolean) {
|
||||
const leavingView = this.getActive();
|
||||
this.insertView(enteringView, direction);
|
||||
await this.transition(enteringView, leavingView, direction, animated, this.canGoBack(1), false);
|
||||
this.cleanup();
|
||||
}
|
||||
|
||||
canGoBack(deep: number): boolean {
|
||||
return this.views.length > deep;
|
||||
}
|
||||
|
||||
async setActive(enteringView: RouteView, direction: number, animated: boolean) {
|
||||
const leavingView = this.getActive();
|
||||
this.insertView(enteringView, direction);
|
||||
await this.transition(enteringView, leavingView, direction, animated, this.canGoBack(1));
|
||||
this.cleanup();
|
||||
pop(deep: number) {
|
||||
this.zone.run(() => {
|
||||
const view = this.views[this.views.length - deep - 1];
|
||||
this.navCtrl.navigateBack(view.url);
|
||||
});
|
||||
}
|
||||
|
||||
pop(deep: number) {
|
||||
const view = this.views[this.views.length - deep - 1];
|
||||
this.navCtrl.navigateBack(view.url);
|
||||
startBackTransition() {
|
||||
this.transition(
|
||||
this.views[this.views.length - 2],
|
||||
this.views[this.views.length - 1],
|
||||
-1,
|
||||
true,
|
||||
true,
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
endBackTransition(shouldComplete: boolean) {
|
||||
if (shouldComplete) {
|
||||
this.skipTransition = true;
|
||||
this.pop(1);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private insertView(enteringView: RouteView, direction: number) {
|
||||
// no stack
|
||||
if (!this.stack) {
|
||||
@@ -100,8 +123,17 @@ export class StackController {
|
||||
leavingView: RouteView | undefined,
|
||||
direction: number,
|
||||
animated: boolean,
|
||||
showGoBack: boolean
|
||||
showGoBack: boolean,
|
||||
progressAnimation: boolean
|
||||
) {
|
||||
if (this.runningTransition) {
|
||||
await this.runningTransition;
|
||||
this.runningTransition = undefined;
|
||||
}
|
||||
if (this.skipTransition) {
|
||||
this.skipTransition = false;
|
||||
return;
|
||||
}
|
||||
const enteringEl = enteringView ? enteringView.element : undefined;
|
||||
const leavingEl = leavingView ? leavingView.element : undefined;
|
||||
const containerEl = this.containerEl;
|
||||
@@ -112,12 +144,14 @@ export class StackController {
|
||||
}
|
||||
|
||||
await containerEl.componentOnReady();
|
||||
await containerEl.commit(enteringEl, leavingEl, {
|
||||
this.runningTransition = containerEl.commit(enteringEl, leavingEl, {
|
||||
duration: !animated ? 0 : undefined,
|
||||
direction: direction === 1 ? 'forward' : 'back',
|
||||
deepWait: true,
|
||||
showGoBack
|
||||
showGoBack,
|
||||
progressAnimation
|
||||
});
|
||||
await this.runningTransition;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -125,33 +159,19 @@ export class StackController {
|
||||
const urlTree = this.router.createUrlTree(['.'], { relativeTo: activatedRoute });
|
||||
return this.router.serializeUrl(urlTree);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export function destroyView(view: RouteView) {
|
||||
function destroyView(view: RouteView) {
|
||||
if (view) {
|
||||
// TODO lifecycle event
|
||||
view.ref.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
export function getLastDeactivatedRef(views: RouteView[]) {
|
||||
if (views.length < 2) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return views.sort((a, b) => {
|
||||
if (a.deactivatedId > b.deactivatedId) return -1;
|
||||
if (a.deactivatedId < b.deactivatedId) return 1;
|
||||
return 0;
|
||||
})[0].ref;
|
||||
}
|
||||
|
||||
export interface RouteView {
|
||||
url: string;
|
||||
fullpath: string;
|
||||
element: HTMLElement;
|
||||
ref: ComponentRef<any>;
|
||||
deactivatedId: number;
|
||||
savedData?: any;
|
||||
}
|
||||
@@ -97,7 +97,6 @@ const DECLARATIONS = [
|
||||
// navigation
|
||||
c.IonBackButton,
|
||||
c.IonRouterOutlet,
|
||||
c.RouterDirection,
|
||||
c.NavDelegate,
|
||||
c.TabDelegate,
|
||||
c.TabsDelegate,
|
||||
|
||||
Reference in New Issue
Block a user