mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2025-08-18 03:00:58 +08:00
@ -162,11 +162,11 @@ export class IonRouterOutlet implements OnDestroy, OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function emitEvent(el: HTMLElement) {
|
function emitEvent(el: HTMLElement) {
|
||||||
const event = new CustomEvent('ionRouterOutletActivated', {
|
const ev = new CustomEvent('ionRouterOutletActivated', {
|
||||||
bubbles: true,
|
bubbles: true,
|
||||||
cancelable: true,
|
cancelable: true,
|
||||||
});
|
});
|
||||||
el.dispatchEvent(event);
|
el.dispatchEvent(ev);
|
||||||
}
|
}
|
||||||
|
|
||||||
class OutletInjector implements Injector {
|
class OutletInjector implements Injector {
|
||||||
|
@ -39,7 +39,6 @@ export class StackController {
|
|||||||
const leavingView = this.getActive();
|
const leavingView = this.getActive();
|
||||||
this.insertView(enteringView, direction);
|
this.insertView(enteringView, direction);
|
||||||
await this.transition(enteringView, leavingView, direction, animated, this.canGoBack(1));
|
await this.transition(enteringView, leavingView, direction, animated, this.canGoBack(1));
|
||||||
|
|
||||||
this.cleanup();
|
this.cleanup();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { APP_INITIALIZER, ModuleWithProviders, NgModule } from '@angular/core';
|
import { APP_INITIALIZER, ModuleWithProviders, NgModule } from '@angular/core';
|
||||||
|
import { IonicConfig } from '@ionic/core';
|
||||||
import { CommonModule } from '@angular/common';
|
import { CommonModule } from '@angular/common';
|
||||||
|
|
||||||
import { appInitialize } from './app-initialize';
|
import { appInitialize } from './app-initialize';
|
||||||
@ -133,7 +134,7 @@ const PROVIDERS = [
|
|||||||
imports: [CommonModule]
|
imports: [CommonModule]
|
||||||
})
|
})
|
||||||
export class IonicModule {
|
export class IonicModule {
|
||||||
static forRoot(config?: { [key: string]: any }): ModuleWithProviders {
|
static forRoot(config?: IonicConfig): ModuleWithProviders {
|
||||||
return {
|
return {
|
||||||
ngModule: IonicModule,
|
ngModule: IonicModule,
|
||||||
providers: [
|
providers: [
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
import { Config as CoreConfig } from '@ionic/core';
|
import { Config as CoreConfig, IonicConfig } from '@ionic/core';
|
||||||
import { InjectionToken } from '@angular/core';
|
import { InjectionToken } from '@angular/core';
|
||||||
import { IonicWindow } from '../types/interfaces';
|
import { IonicWindow } from '../types/interfaces';
|
||||||
|
|
||||||
|
|
||||||
export class Config {
|
export class Config {
|
||||||
|
|
||||||
get(key: string, fallback?: any): any {
|
get(key: keyof IonicConfig, fallback?: any): any {
|
||||||
const c = getConfig();
|
const c = getConfig();
|
||||||
if (c) {
|
if (c) {
|
||||||
return c.get(key, fallback);
|
return c.get(key, fallback);
|
||||||
@ -13,7 +13,7 @@ export class Config {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
getBoolean(key: string, fallback?: boolean): boolean {
|
getBoolean(key: keyof IonicConfig, fallback?: boolean): boolean {
|
||||||
const c = getConfig();
|
const c = getConfig();
|
||||||
if (c) {
|
if (c) {
|
||||||
return c.getBoolean(key, fallback);
|
return c.getBoolean(key, fallback);
|
||||||
@ -21,7 +21,7 @@ export class Config {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
getNumber(key: string, fallback?: number): number {
|
getNumber(key: keyof IonicConfig, fallback?: number): number {
|
||||||
const c = getConfig();
|
const c = getConfig();
|
||||||
if (c) {
|
if (c) {
|
||||||
return c.getNumber(key, fallback);
|
return c.getNumber(key, fallback);
|
||||||
@ -29,7 +29,7 @@ export class Config {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
set(key: string, value?: any) {
|
set(key: keyof IonicConfig, value?: any) {
|
||||||
const c = getConfig();
|
const c = getConfig();
|
||||||
if (c) {
|
if (c) {
|
||||||
c.set(key, value);
|
c.set(key, value);
|
||||||
|
@ -11,9 +11,9 @@ import { attachComponent, detachComponent } from '../../utils/framework-delegate
|
|||||||
})
|
})
|
||||||
export class RouterOutlet implements NavOutlet {
|
export class RouterOutlet implements NavOutlet {
|
||||||
|
|
||||||
private isTransitioning = false;
|
|
||||||
private activeEl: HTMLElement | undefined;
|
private activeEl: HTMLElement | undefined;
|
||||||
private activeComponent: any;
|
private activeComponent: any;
|
||||||
|
private waitPromise?: Promise<void>;
|
||||||
|
|
||||||
mode!: Mode;
|
mode!: Mode;
|
||||||
|
|
||||||
@ -36,7 +36,6 @@ export class RouterOutlet implements NavOutlet {
|
|||||||
if (this.animated === undefined) {
|
if (this.animated === undefined) {
|
||||||
this.animated = this.config.getBoolean('animate', true);
|
this.animated = this.config.getBoolean('animate', true);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.ionNavWillLoad.emit();
|
this.ionNavWillLoad.emit();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -49,20 +48,19 @@ export class RouterOutlet implements NavOutlet {
|
|||||||
*/
|
*/
|
||||||
@Method()
|
@Method()
|
||||||
async setRoot(component: ComponentRef, params?: ComponentProps, opts?: RouterOutletOptions): Promise<boolean> {
|
async setRoot(component: ComponentRef, params?: ComponentProps, opts?: RouterOutletOptions): Promise<boolean> {
|
||||||
if (this.isTransitioning || this.activeComponent === component) {
|
if (this.activeComponent === component) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
this.activeComponent = component;
|
|
||||||
|
|
||||||
// attach entering view to DOM
|
// attach entering view to DOM
|
||||||
const enteringEl = await attachComponent(this.delegate, this.el, component, ['ion-page', 'ion-page-invisible'], params);
|
|
||||||
const leavingEl = this.activeEl;
|
const leavingEl = this.activeEl;
|
||||||
|
const enteringEl = await attachComponent(this.delegate, this.el, component, ['ion-page', 'ion-page-invisible'], params);
|
||||||
|
|
||||||
|
this.activeComponent = component;
|
||||||
|
this.activeEl = enteringEl;
|
||||||
|
|
||||||
// commit animation
|
// commit animation
|
||||||
await this.commit(enteringEl, leavingEl, opts);
|
await this.commit(enteringEl, leavingEl, opts);
|
||||||
|
|
||||||
// remove leaving view
|
|
||||||
this.activeEl = enteringEl;
|
|
||||||
detachComponent(this.delegate, leavingEl);
|
detachComponent(this.delegate, leavingEl);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@ -71,35 +69,15 @@ export class RouterOutlet implements NavOutlet {
|
|||||||
/** @hidden */
|
/** @hidden */
|
||||||
@Method()
|
@Method()
|
||||||
async commit(enteringEl: HTMLElement, leavingEl: HTMLElement | undefined, opts?: RouterOutletOptions): Promise<boolean> {
|
async commit(enteringEl: HTMLElement, leavingEl: HTMLElement | undefined, opts?: RouterOutletOptions): Promise<boolean> {
|
||||||
// isTransitioning acts as a lock to prevent reentering
|
const unlock = await this.lock();
|
||||||
if (this.isTransitioning || leavingEl === enteringEl) {
|
let changed = false;
|
||||||
return false;
|
try {
|
||||||
|
changed = await this.transition(enteringEl, leavingEl, opts);
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
}
|
}
|
||||||
this.isTransitioning = true;
|
unlock();
|
||||||
|
return changed;
|
||||||
// emit nav will change event
|
|
||||||
this.ionNavWillChange.emit();
|
|
||||||
|
|
||||||
opts = opts || {};
|
|
||||||
|
|
||||||
const { mode, queue, animated, animationCtrl, win, el } = this;
|
|
||||||
await transition({
|
|
||||||
mode,
|
|
||||||
queue,
|
|
||||||
animated,
|
|
||||||
animationCtrl,
|
|
||||||
window: win,
|
|
||||||
enteringEl,
|
|
||||||
leavingEl,
|
|
||||||
baseEl: el,
|
|
||||||
|
|
||||||
...opts
|
|
||||||
});
|
|
||||||
this.isTransitioning = false;
|
|
||||||
|
|
||||||
// emit nav changed event
|
|
||||||
this.ionNavDidChange.emit();
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @hidden */
|
/** @hidden */
|
||||||
@ -125,6 +103,48 @@ export class RouterOutlet implements NavOutlet {
|
|||||||
} : undefined;
|
} : undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async lock() {
|
||||||
|
const p = this.waitPromise;
|
||||||
|
let resolve!: () => void;
|
||||||
|
this.waitPromise = new Promise(r => resolve = r);
|
||||||
|
|
||||||
|
if (p) {
|
||||||
|
await p;
|
||||||
|
}
|
||||||
|
return resolve;
|
||||||
|
}
|
||||||
|
|
||||||
|
async transition(enteringEl: HTMLElement, leavingEl: HTMLElement | undefined, opts?: RouterOutletOptions): Promise<boolean> {
|
||||||
|
// isTransitioning acts as a lock to prevent reentering
|
||||||
|
if (leavingEl === enteringEl) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// emit nav will change event
|
||||||
|
this.ionNavWillChange.emit();
|
||||||
|
|
||||||
|
opts = opts || {};
|
||||||
|
|
||||||
|
const { mode, queue, animated, animationCtrl, win, el } = this;
|
||||||
|
await transition({
|
||||||
|
mode,
|
||||||
|
queue,
|
||||||
|
animated,
|
||||||
|
animationCtrl,
|
||||||
|
window: win,
|
||||||
|
enteringEl,
|
||||||
|
leavingEl,
|
||||||
|
baseEl: el,
|
||||||
|
|
||||||
|
...opts
|
||||||
|
});
|
||||||
|
|
||||||
|
// emit nav changed event
|
||||||
|
this.ionNavDidChange.emit();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return [
|
return [
|
||||||
this.mode === 'ios' && <div class="nav-decor"/>,
|
this.mode === 'ios' && <div class="nav-decor"/>,
|
||||||
|
@ -75,7 +75,28 @@
|
|||||||
<ion-route url="/two" component="page-two"> </ion-route>
|
<ion-route url="/two" component="page-two"> </ion-route>
|
||||||
<ion-route url="/page-3" component="page-three"> </ion-route>
|
<ion-route url="/page-3" component="page-three"> </ion-route>
|
||||||
</ion-router>
|
</ion-router>
|
||||||
<ion-router-outlet></ion-router-outlet>
|
|
||||||
|
<ion-split-pane>
|
||||||
|
<ion-menu>
|
||||||
|
<ion-header>
|
||||||
|
<ion-toolbar>
|
||||||
|
<ion-title>Menu</ion-title>
|
||||||
|
</ion-toolbar>
|
||||||
|
</ion-header>
|
||||||
|
<ion-content>
|
||||||
|
<ion-item href="#/">
|
||||||
|
<ion-label>Page 1</ion-label>
|
||||||
|
</ion-item>
|
||||||
|
<ion-item href="#/two">
|
||||||
|
<ion-label>Page 2</ion-label>
|
||||||
|
</ion-item>
|
||||||
|
<ion-item href="#/page-3">
|
||||||
|
<ion-label>Page 3</ion-label>
|
||||||
|
</ion-item>
|
||||||
|
</ion-content>
|
||||||
|
</ion-menu>
|
||||||
|
<ion-router-outlet main></ion-router-outlet>
|
||||||
|
</ion-split-pane>
|
||||||
</ion-app>
|
</ion-app>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
|
@ -19,6 +19,7 @@ export class Router {
|
|||||||
private busy = false;
|
private busy = false;
|
||||||
private state = 0;
|
private state = 0;
|
||||||
private lastState = 0;
|
private lastState = 0;
|
||||||
|
private waitPromise?: Promise<void>;
|
||||||
|
|
||||||
@Element() el!: HTMLElement;
|
@Element() el!: HTMLElement;
|
||||||
|
|
||||||
@ -82,10 +83,10 @@ export class Router {
|
|||||||
/** Navigate to the specified URL */
|
/** Navigate to the specified URL */
|
||||||
@Method()
|
@Method()
|
||||||
push(url: string, direction: RouterDirection = 'forward') {
|
push(url: string, direction: RouterDirection = 'forward') {
|
||||||
const path = parsePath(url);
|
|
||||||
const intent = DIRECTION_TO_INTENT[direction];
|
|
||||||
console.debug('[ion-router] URL pushed -> updating nav', url, direction);
|
console.debug('[ion-router] URL pushed -> updating nav', url, direction);
|
||||||
|
|
||||||
|
const path = parsePath(url);
|
||||||
|
const intent = DIRECTION_TO_INTENT[direction];
|
||||||
this.setPath(path, intent);
|
this.setPath(path, intent);
|
||||||
return this.writeNavStateRoot(path, intent);
|
return this.writeNavStateRoot(path, intent);
|
||||||
}
|
}
|
||||||
@ -103,6 +104,7 @@ export class Router {
|
|||||||
@Method()
|
@Method()
|
||||||
async navChanged(intent: number): Promise<boolean> {
|
async navChanged(intent: number): Promise<boolean> {
|
||||||
if (this.busy) {
|
if (this.busy) {
|
||||||
|
console.warn('[ion-router] router is busy, navChanged was cancelled');
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
const { ids, outlet } = readNavState(this.win.document.body);
|
const { ids, outlet } = readNavState(this.win.document.body);
|
||||||
@ -122,7 +124,7 @@ export class Router {
|
|||||||
console.debug('[ion-router] nav changed -> update URL', ids, path);
|
console.debug('[ion-router] nav changed -> update URL', ids, path);
|
||||||
this.setPath(path, intent);
|
this.setPath(path, intent);
|
||||||
|
|
||||||
await this.writeNavState(outlet, chain, RouterIntent.None, path, null, ids.length);
|
await this.safeWriteNavState(outlet, chain, RouterIntent.None, path, null, ids.length);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -157,9 +159,6 @@ export class Router {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async writeNavStateRoot(path: string[] | null, intent: RouterIntent): Promise<boolean> {
|
private async writeNavStateRoot(path: string[] | null, intent: RouterIntent): Promise<boolean> {
|
||||||
if (this.busy) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (!path) {
|
if (!path) {
|
||||||
console.error('[ion-router] URL is not part of the routing set');
|
console.error('[ion-router] URL is not part of the routing set');
|
||||||
return false;
|
return false;
|
||||||
@ -184,7 +183,34 @@ export class Router {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// write DOM give
|
// write DOM give
|
||||||
return this.writeNavState(this.win.document.body, chain, intent, path, redirectFrom);
|
return this.safeWriteNavState(this.win.document.body, chain, intent, path, redirectFrom);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async safeWriteNavState(
|
||||||
|
node: HTMLElement | undefined, chain: RouteChain, intent: RouterIntent,
|
||||||
|
path: string[], redirectFrom: string[] | null,
|
||||||
|
index = 0
|
||||||
|
): Promise<boolean> {
|
||||||
|
const unlock = await this.lock();
|
||||||
|
let changed = false;
|
||||||
|
try {
|
||||||
|
changed = await this.writeNavState(node, chain, intent, path, redirectFrom, index);
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
}
|
||||||
|
unlock();
|
||||||
|
return changed;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async lock() {
|
||||||
|
const p = this.waitPromise;
|
||||||
|
let resolve!: () => void;
|
||||||
|
this.waitPromise = new Promise(r => resolve = r);
|
||||||
|
|
||||||
|
if (p) {
|
||||||
|
await p;
|
||||||
|
}
|
||||||
|
return resolve;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async writeNavState(
|
private async writeNavState(
|
||||||
@ -193,6 +219,7 @@ export class Router {
|
|||||||
index = 0
|
index = 0
|
||||||
): Promise<boolean> {
|
): Promise<boolean> {
|
||||||
if (this.busy) {
|
if (this.busy) {
|
||||||
|
console.warn('[ion-router] router is busy, transition was cancelled');
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
this.busy = true;
|
this.busy = true;
|
||||||
|
@ -1,5 +1,12 @@
|
|||||||
|
import { Mode } from '../interface';
|
||||||
|
|
||||||
export interface IonicConfig {
|
export interface IonicConfig {
|
||||||
|
/**
|
||||||
|
* The mode determines which platform styles to use.
|
||||||
|
* Possible values are: `"ios"` or `"md"`.
|
||||||
|
*/
|
||||||
|
mode?: Mode;
|
||||||
|
|
||||||
isDevice?: boolean;
|
isDevice?: boolean;
|
||||||
statusbarPadding?: boolean;
|
statusbarPadding?: boolean;
|
||||||
inputShims?: boolean;
|
inputShims?: boolean;
|
||||||
@ -12,7 +19,6 @@ export interface IonicConfig {
|
|||||||
pickerSpinner?: string;
|
pickerSpinner?: string;
|
||||||
refreshingIcon?: string;
|
refreshingIcon?: string;
|
||||||
refreshingSpinner?: string;
|
refreshingSpinner?: string;
|
||||||
mode?: string;
|
|
||||||
menuType?: string;
|
menuType?: string;
|
||||||
scrollPadding?: string;
|
scrollPadding?: string;
|
||||||
inputBlurring?: string;
|
inputBlurring?: string;
|
||||||
|
@ -70,6 +70,8 @@ async function animation(animationBuilder: AnimationBuilder, opts: TransitionOpt
|
|||||||
fireWillEvents(opts.window, opts.enteringEl, opts.leavingEl);
|
fireWillEvents(opts.window, opts.enteringEl, opts.leavingEl);
|
||||||
await playTransition(trns, opts);
|
await playTransition(trns, opts);
|
||||||
|
|
||||||
|
markVisible(opts);
|
||||||
|
|
||||||
if (trns.hasCompleted) {
|
if (trns.hasCompleted) {
|
||||||
fireDidEvents(opts.window, opts.enteringEl, opts.leavingEl);
|
fireDidEvents(opts.window, opts.enteringEl, opts.leavingEl);
|
||||||
}
|
}
|
||||||
@ -79,17 +81,25 @@ async function animation(animationBuilder: AnimationBuilder, opts: TransitionOpt
|
|||||||
async function noAnimation(opts: TransitionOptions): Promise<null> {
|
async function noAnimation(opts: TransitionOptions): Promise<null> {
|
||||||
const enteringEl = opts.enteringEl;
|
const enteringEl = opts.enteringEl;
|
||||||
const leavingEl = opts.leavingEl;
|
const leavingEl = opts.leavingEl;
|
||||||
|
|
||||||
|
await waitForReady(opts, false);
|
||||||
|
|
||||||
|
markVisible(opts);
|
||||||
|
|
||||||
|
fireWillEvents(opts.window, enteringEl, leavingEl);
|
||||||
|
fireDidEvents(opts.window, enteringEl, leavingEl);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function markVisible(opts: TransitionOptions) {
|
||||||
|
const enteringEl = opts.enteringEl;
|
||||||
|
const leavingEl = opts.leavingEl;
|
||||||
if (enteringEl) {
|
if (enteringEl) {
|
||||||
enteringEl.classList.remove('ion-page-invisible');
|
enteringEl.classList.remove('ion-page-invisible');
|
||||||
}
|
}
|
||||||
if (leavingEl) {
|
if (leavingEl) {
|
||||||
leavingEl.classList.remove('ion-page-invisible');
|
leavingEl.classList.remove('ion-page-invisible');
|
||||||
}
|
}
|
||||||
await waitForReady(opts, false);
|
|
||||||
|
|
||||||
fireWillEvents(opts.window, enteringEl, leavingEl);
|
|
||||||
fireDidEvents(opts.window, enteringEl, leavingEl);
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function waitForReady(opts: TransitionOptions, defaultDeep: boolean) {
|
async function waitForReady(opts: TransitionOptions, defaultDeep: boolean) {
|
||||||
|
Reference in New Issue
Block a user