mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2025-08-16 18:17:31 +08:00
174 lines
5.2 KiB
TypeScript
174 lines
5.2 KiB
TypeScript
import { ComponentRef, NgZone } from '@angular/core';
|
|
import { ActivatedRoute, Router } from '@angular/router';
|
|
import { RouterDirection } from '@ionic/core';
|
|
|
|
import { bindLifecycleEvents } from '../../providers/angular-delegate';
|
|
import { NavController } from '../../providers/nav-controller';
|
|
|
|
import { RouteView, computeStackId, destroyView, getUrl, insertView, isTabSwitch, toSegments } from './stack-utils';
|
|
|
|
export class StackController {
|
|
|
|
private viewsSnapshot: RouteView[] = [];
|
|
private views: RouteView[] = [];
|
|
private runningTransition?: Promise<boolean>;
|
|
private skipTransition = false;
|
|
private tabsPrefix: string[] | undefined;
|
|
private activeView: RouteView | undefined;
|
|
private nextId = 0;
|
|
|
|
constructor(
|
|
tabsPrefix: string | undefined,
|
|
private containerEl: HTMLIonRouterOutletElement,
|
|
private router: Router,
|
|
private navCtrl: NavController,
|
|
private zone: NgZone,
|
|
) {
|
|
this.tabsPrefix = tabsPrefix !== undefined ? toSegments(tabsPrefix) : undefined;
|
|
}
|
|
|
|
createView(ref: ComponentRef<any>, activatedRoute: ActivatedRoute): RouteView {
|
|
const url = getUrl(this.router, activatedRoute);
|
|
const element = (ref && ref.location && ref.location.nativeElement) as HTMLElement;
|
|
const unlistenEvents = bindLifecycleEvents(ref.instance, element);
|
|
return {
|
|
id: this.nextId++,
|
|
stackId: computeStackId(this.tabsPrefix, url),
|
|
unlistenEvents,
|
|
element,
|
|
ref,
|
|
url,
|
|
};
|
|
}
|
|
|
|
getExistingView(activatedRoute: ActivatedRoute): RouteView | undefined {
|
|
const activatedUrlKey = getUrl(this.router, activatedRoute);
|
|
return this.views.find(vw => vw.url === activatedUrlKey);
|
|
}
|
|
|
|
async setActive(enteringView: RouteView) {
|
|
let { direction, animation } = this.navCtrl.consumeTransition();
|
|
const leavingView = this.activeView;
|
|
if (isTabSwitch(enteringView, leavingView)) {
|
|
direction = 'back';
|
|
animation = undefined;
|
|
}
|
|
this.insertView(enteringView, direction);
|
|
await this.transition(enteringView, leavingView, animation, this.canGoBack(1), false);
|
|
this.cleanup();
|
|
}
|
|
|
|
canGoBack(deep: number, stackId = this.getActiveStackId()): boolean {
|
|
return this.getStack(stackId).length > deep;
|
|
}
|
|
|
|
pop(deep: number, stackId = this.getActiveStackId()) {
|
|
this.zone.run(() => {
|
|
const views = this.getStack(stackId);
|
|
const view = views[views.length - deep - 1];
|
|
this.navCtrl.navigateBack(view.url);
|
|
});
|
|
}
|
|
|
|
startBackTransition(stackId = this.getActiveStackId()) {
|
|
const views = this.getStack(stackId);
|
|
this.transition(
|
|
views[views.length - 2], // entering view
|
|
views[views.length - 1], // leaving view
|
|
'back',
|
|
true,
|
|
true
|
|
);
|
|
}
|
|
|
|
endBackTransition(shouldComplete: boolean) {
|
|
if (shouldComplete) {
|
|
this.skipTransition = true;
|
|
this.pop(1);
|
|
}
|
|
}
|
|
|
|
getLastUrl(stackId?: string) {
|
|
const views = this.getStack(stackId);
|
|
return views.length > 0 ? views[views.length - 1] : undefined;
|
|
}
|
|
|
|
getActiveStackId(): string | undefined {
|
|
return this.activeView ? this.activeView.stackId : undefined;
|
|
}
|
|
|
|
destroy() {
|
|
this.containerEl = undefined!;
|
|
this.views.forEach(destroyView);
|
|
this.activeView = undefined;
|
|
this.views = [];
|
|
}
|
|
|
|
private getStack(stackId: string | undefined) {
|
|
return this.views.filter(v => v.stackId === stackId);
|
|
}
|
|
|
|
private insertView(enteringView: RouteView, direction: RouterDirection) {
|
|
this.activeView = enteringView;
|
|
this.views = insertView(this.views, enteringView, direction);
|
|
}
|
|
|
|
private cleanup() {
|
|
const activeRoute = this.activeView;
|
|
const views = this.views;
|
|
this.viewsSnapshot
|
|
.filter(view => !views.includes(view))
|
|
.forEach(view => destroyView(view));
|
|
|
|
views.forEach(view => {
|
|
if (view !== activeRoute) {
|
|
const element = view.element;
|
|
element.setAttribute('aria-hidden', 'true');
|
|
element.classList.add('ion-page-hidden');
|
|
}
|
|
});
|
|
this.viewsSnapshot = views.slice();
|
|
}
|
|
|
|
private async transition(
|
|
enteringView: RouteView | undefined,
|
|
leavingView: RouteView | undefined,
|
|
direction: 'forward' | 'back' | undefined,
|
|
showGoBack: boolean,
|
|
progressAnimation: boolean
|
|
) {
|
|
if (this.runningTransition !== undefined) {
|
|
await this.runningTransition;
|
|
this.runningTransition = undefined;
|
|
}
|
|
if (this.skipTransition) {
|
|
this.skipTransition = false;
|
|
return;
|
|
}
|
|
// TODO
|
|
// if (enteringView) {
|
|
// enteringView.ref.changeDetectorRef.reattach();
|
|
// enteringView.ref.changeDetectorRef.markForCheck();
|
|
// }
|
|
const enteringEl = enteringView ? enteringView.element : undefined;
|
|
const leavingEl = leavingView ? leavingView.element : undefined;
|
|
const containerEl = this.containerEl;
|
|
if (enteringEl && enteringEl !== leavingEl) {
|
|
enteringEl.classList.add('ion-page', 'ion-page-invisible');
|
|
if (enteringEl.parentElement !== containerEl) {
|
|
containerEl.appendChild(enteringEl);
|
|
}
|
|
|
|
await containerEl.componentOnReady();
|
|
this.runningTransition = containerEl.commit(enteringEl, leavingEl, {
|
|
deepWait: true,
|
|
duration: direction === undefined ? 0 : undefined,
|
|
direction,
|
|
showGoBack,
|
|
progressAnimation
|
|
});
|
|
await this.runningTransition;
|
|
}
|
|
}
|
|
}
|